Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
101 changes: 47 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <kbd>Ctrl</kbd>+<kbd>C</kbd>

Press <kbd>Ctrl</kbd>+<kbd>C</kbd> 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
- Website: [sunfounder.com](https://www.sunfounder.com)
- Email: service@sunfounder.com
68 changes: 49 additions & 19 deletions sunfounder_controller/sunfounder_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sunfounder_controller/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.2"
__version__ = "0.0.3"