From c68ef8c21d8d5ed8c90e9643ad5436b0b96fa8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=81=E9=94=A4?= Date: Wed, 29 Jul 2026 17:47:08 +0800 Subject: [PATCH 1/2] feat: add SF_CONTROLLER_DEBUG env var for WS lifecycle debug logging - Add _debug() helper controlled by SF_CONTROLLER_DEBUG=1 - Log connection, recv (content+bytes), send (payload+bytes), parse, errors, close - Fix stale tmp reference across recv timeout by resetting tmp=None each iteration --- .../sunfounder_controller.py | 68 +++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/sunfounder_controller/sunfounder_controller.py b/sunfounder_controller/sunfounder_controller.py index 3b665c8..89bb8e0 100644 --- a/sunfounder_controller/sunfounder_controller.py +++ b/sunfounder_controller/sunfounder_controller.py @@ -3,6 +3,16 @@ import json import time import threading +import os + +# Debug logging controlled by env var SF_CONTROLLER_DEBUG=1 +_DEBUG = os.environ.get('SF_CONTROLLER_DEBUG', '0') == '1' + +def _debug(msg): + """Print debug message with timestamp if debug mode is on.""" + if _DEBUG: + print(f'[SF_CONTROLLER_DEBUG] {time.strftime("%H:%M:%S")} {msg}') + class SunFounderController(): PORT = 8765 @@ -73,47 +83,67 @@ async def handler(self, websocket): self.client_num += 1 self.client[str(_client_num)] = _client_ip print(f'client {_client_num, _client_ip} conneted') - # print(websocket.remote_address) + _debug(f'client {_client_num, _client_ip} connected, total clients: {self.client_num}') + _debug(f'websocket headers: {dict(websocket.request_headers)}') + msg_count = 0 while self.work_flag: try: # recv + tmp = None + recv_bytes = 0 try: tmp = await asyncio.wait_for(websocket.recv(), timeout=0.001) - # print("websocket.recv() temp: %s" % tmp) - except asyncio.TimeoutError as e: - # print('asyncio.TimeoutError : %s'%e) + recv_bytes = len(tmp.encode('utf-8')) if isinstance(tmp, str) else len(tmp) + _debug(f'[RECV] client={_client_num} bytes={recv_bytes} raw={repr(tmp)[:500]}') + except asyncio.TimeoutError: pass + except Exception as e: + _debug(f'[RECV ERROR] client={_client_num} exception={type(e).__name__}: {e}') + raise # send + send_payload = json.dumps(self.send_dict) + send_bytes = len(send_payload.encode('utf-8')) try: - # print(json.dumps(self.send_dict)) - await websocket.send(json.dumps(self.send_dict)) + await websocket.send(send_payload) + if msg_count == 0 or _DEBUG: + _debug(f'[SEND] client={_client_num} bytes={send_bytes} payload={repr(send_payload)[:200]}') except Exception as e: + _debug(f'[SEND ERROR] client={_client_num} exception={type(e).__name__}: {e}') print('send Exception: %s'%e) - # - try: - tmp = json.loads(tmp) - if isinstance(tmp, dict): - self.recv_dict = tmp - self.is_received = True - self.data_processing() - else: + # parse recv data + if tmp is not None: + try: + tmp = json.loads(tmp) + if isinstance(tmp, dict): + self.recv_dict = tmp + self.is_received = True + self.data_processing() + _debug(f'[PARSED] client={_client_num} keys={list(tmp.keys())} heart={tmp.get("Heart","N/A")}') + else: + _debug(f'[PARSE ERROR] client={_client_num} not a dict: {type(tmp).__name__}') + print("JSONDecodeError") + except json.decoder.JSONDecodeError: + self.is_received = False + _debug(f'[PARSE ERROR] client={_client_num} JSONDecodeError') print("JSONDecodeError") - except json.decoder.JSONDecodeError: - self.is_received = False - print("JSONDecodeError") - except Exception as e: - pass + except Exception as e: + _debug(f'[PARSE ERROR] client={_client_num} {type(e).__name__}: {e}') + msg_count += 1 await asyncio.sleep(0.01) except websockets.exceptions.ConnectionClosed as connection_code: # disconneted flag + _debug(f'[CLOSE] client={_client_num} code={connection_code} total_msgs={msg_count}') print(f'{_client_num}: {connection_code}') print(f'client {_client_num, _client_ip} disconneted') break + except Exception as e: + _debug(f'[HANDLER ERROR] client={_client_num} {type(e).__name__}: {e}') + raise self.client.pop(str(_client_num)) self.is_closed = True From 3940e96dcb494173c3e6252ba90d076391c82faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=93=81=E9=94=A4?= Date: Thu, 30 Jul 2026 11:14:22 +0800 Subject: [PATCH 2/2] chore: bump version to 0.0.3 + README update --- CHANGELOG.md | 12 ++++ README.md | 101 ++++++++++++++----------------- sunfounder_controller/version.py | 2 +- 3 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..01cd15c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.0.3] - 2026-07-30 + +- Add debug logging mode via `SF_CONTROLLER_DEBUG` environment variable +- WebSocket message tracing with timestamps for troubleshooting + +## [0.0.2] - 2020-06-12 + +- New Release diff --git a/README.md b/README.md index 58b53dc..44a860c 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,83 @@ - # SunFounder Controller -## SunFounder Controller examples +SunFounder Controller provides a WebSocket-based controller for Raspberry Pi robots. It enables real-time bidirectional communication between a web/mobile client and your robot over the local network. - - start() - start webserver - - get(key='A_region') - get the returned value - - set(key='A_region', value=None) - send value +## Quick Start +Install from source: +```shell +git clone https://github.com/sunfounder/sunfounder-controller.git +cd sunfounder-controller +sudo python3 setup.py install +``` -Quick Links: +## Usage + +```python +from sunfounder_controller import SunFounderController +sc = SunFounderController() -## About this kit +# Start the WebSocket server +sc.start() -We are happy to see your issus and pull request. Feel free to be apart. +# Send a value to connected clients +sc.set('A_region', value) -## About SunFounder Controller : +# Read a value from clients +value = sc.get('A') +``` -## Download +## Debug Mode -Download this repository to your Raspberry Pi: +Enable debug logging to troubleshoot WebSocket connections and message flow: ```shell -git clone https://github.com/sunfounder/sunfounder-controller.git +SF_CONTROLLER_DEBUG=1 python3 your_script.py ``` -## Usage - -Before running the example, stop ezblock sercive +Or set it in your script before importing: ```python -sudo service ezblock stop +import os +os.environ['SF_CONTROLLER_DEBUG'] = '1' ``` -Then run the example +Debug output is prefixed with `[SF_CONTROLLER_DEBUG]` and includes timestamps, connection/disconnection events, raw message dumps, and parse errors. + +## Examples + +See the `examples/` directory. Run with: ```bash cd examples sudo python3 xxx.py ``` -Stop running the example by using Ctrl+C - +Press Ctrl+C to stop. +## API Reference - -## Update - -- 2020-6-12: New Release - -## Trouble Shootings - -## About SunFounder -SunFounder is a technology company focused on Raspberry Pi and Arduino open source community development. Committed to the promotion of open source culture, we strives to bring the fun of electronics making to people all around the world and enable everyone to be a maker. Our products include learning kits, development boards, robots, sensor modules and development tools. In addition to high quality products, SunFounder also offers video tutorials to help you make your own project. If you have interest in open source or making something cool, welcome to join us! - -## About Ezblock - -Ezblock is a technology company focused on Raspberry Pi and Arduino open source community development. Committed to the promotion of open source culture, we strives to bring the fun of electronics making to people all around the world and enable everyone to be a maker. Our products include learning kits, development boards, robots, sensor modules and development tools. In addition to high quality products, Ezblock also offers video tutorials to help you make your own project. If you have interest in open source or making something cool, welcome to join us! +| Method | Description | +|--------|-------------| +| `start()` | Start the WebSocket server on port 8765 | +| `get(key='A', default=None)` | Get a value from `recv_dict` by key | +| `getall()` | Return the full `recv_dict` | +| `set(key='A_region', value=None)` | Send a value to clients via `send_dict` | +| `set_name(name)` | Set the controller name sent to clients | +| `set_type(type)` | Set the controller type sent to clients | +| `close()` | Shut down the WebSocket server | ## License -This is the code for PiCrawler. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied wa rranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -PiCrawler examples comes with ABSOLUTELY NO WARRANTY; for details checkout [LICENCE](LICENCE). This is free software, and you are welcome to redistribute it under certain conditions; checkout [LICENCE](LICENCE) for details. - -SunFounder, Inc., hereby disclaims all copyright interest in the program 'PiCrawler examples' (which makes passes at compilers). - -Mike Huang, 21 August 2015 - -Mike Huang, Chief Executive Officer - -Email: service@sunfounder.com +## About SunFounder -## Contact us +SunFounder is a technology company focused on Raspberry Pi and Arduino open source community development. Committed to the promotion of open source culture, we strive to bring the fun of electronics making to people all around the world and enable everyone to be a maker. Our products include learning kits, development boards, robots, sensor modules and development tools. In addition to high quality products, SunFounder also offers video tutorials to help you make your own project. -website: - ezblock.cc +## Contact -E-mail: - service@sunfounder.com \ No newline at end of file +- Website: [sunfounder.com](https://www.sunfounder.com) +- Email: service@sunfounder.com diff --git a/sunfounder_controller/version.py b/sunfounder_controller/version.py index a0235ce..e344246 100644 --- a/sunfounder_controller/version.py +++ b/sunfounder_controller/version.py @@ -1 +1 @@ -__version__ = "0.0.2" \ No newline at end of file +__version__ = "0.0.3" \ No newline at end of file