Skip to content

Commit 5b3b924

Browse files
committed
darkmode, ui cleanup, control-data logic
1 parent fe51ce2 commit 5b3b924

8 files changed

Lines changed: 364 additions & 165 deletions

File tree

README.md

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
# Ser2tcp
22

3-
Simple proxy for connecting over TCP, TELNET, SSL or Unix socket to serial port
3+
Simple proxy for connecting over TCP, TELNET, SSL, WebSocket or Unix socket to serial port
44

55
https://github.com/cortexm/ser2tcp
66

77
## Features
88

99
- can serve multiple serial ports using pyserial library
1010
- each serial port can have multiple servers
11-
- server can use TCP, TELNET, SSL or SOCKET protocol
11+
- server can use TCP, TELNET, SSL, WebSocket or SOCKET protocol
1212
- TCP protocol just bridge whole RAW serial stream to TCP
1313
- TELNET protocol will send every character immediately and not wait for ENTER, it is useful to use standard `telnet` as serial terminal
1414
- SSL protocol provides encrypted TCP connection with optional mutual TLS (mTLS) client certificate verification
15+
- WebSocket protocol connects through the HTTP server with binary frames for data and JSON text frames for signal control
1516
- SOCKET protocol uses Unix domain socket for local IPC
1617
- servers accepts multiple connections at one time
1718
- each connected client can sent to serial port
1819
- serial port send received data to all connected clients
1920
- non-blocking send with configurable timeout and buffer limit
21+
- serial signal control (RTS, DTR, CTS, DSR, RI, CD) via escape protocol or WebSocket JSON
2022
- built-in HTTP server with REST API for status monitoring
2123
- web interface for viewing configured ports and connections
24+
- web terminal clients (xterm.js VT100 terminal and raw colored view)
2225
- authentication with session management and API tokens
26+
- light/dark mode web UI (follows system preference)
2327

2428
## Installation
2529

@@ -137,13 +141,42 @@ Match attributes: `vid`, `pid`, `serial_number`, `manufacturer`, `product`, `loc
137141

138142
| Parameter | Description | Default |
139143
|-----------|-------------|---------|
140-
| `address` | Bind address (IP for tcp/telnet/ssl, path for socket) | required |
141-
| `port` | TCP port (not used for socket) | required |
142-
| `protocol` | `tcp`, `telnet`, `ssl` or `socket` | required |
144+
| `address` | Bind address (IP for tcp/telnet/ssl, path for socket) | required* |
145+
| `port` | TCP port (not used for socket/websocket) | required* |
146+
| `protocol` | `tcp`, `telnet`, `ssl`, `websocket` or `socket` | required |
147+
| `endpoint` | WebSocket URL path (websocket only), must be unique | required* |
148+
| `token` | Per-server auth token (websocket only) | - |
143149
| `ssl` | SSL configuration (required for `ssl` protocol) | - |
150+
| `data` | Forward serial data (default true), `false` = control-only | true |
151+
| `control` | Signal control configuration | - |
144152
| `send_timeout` | Disconnect client if data cannot be sent within this time (seconds) | 5.0 |
145153
| `buffer_limit` | Maximum send buffer size per client (bytes), `null` for unlimited | null |
146154

155+
\* `address`/`port` required for tcp/telnet/ssl; `address` for socket; `endpoint` for websocket
156+
157+
#### WebSocket configuration
158+
159+
WebSocket connections go through the HTTP server — no separate listening port needed:
160+
161+
```json
162+
{
163+
"protocol": "websocket",
164+
"endpoint": "my-device",
165+
"control": {
166+
"rts": true,
167+
"signals": ["rts", "dtr", "cts", "dsr"]
168+
}
169+
}
170+
```
171+
172+
- Accessible at `ws://host:port/ws/my-device` (or `wss://` for HTTPS)
173+
- Available on all configured HTTP servers
174+
- Binary frames carry raw serial data (bidirectional)
175+
- Text frames carry JSON control messages: `{"rts": true}`, `{"signals": {...}}`
176+
- Signal state sent automatically on connect, then only on change
177+
- Auth: per-server `token`, global user session, or both accepted
178+
- Web terminals available at `/xterm/<endpoint>` (VT100) and `/raw/<endpoint>` (colored hex)
179+
147180
#### Socket configuration
148181

149182
For `socket` protocol, `address` is the path to the Unix domain socket:
@@ -289,7 +322,19 @@ HTTPS with SSL:
289322
| POST | `/api/login` | no | Authenticate, returns session token |
290323
| POST | `/api/logout` | no | Invalidate session |
291324
| GET | `/api/status` | yes | Runtime status (serial ports, servers, connections) |
292-
| GET | `/api/ports` | yes | Available serial ports with USB/device attributes |
325+
| GET | `/api/detect` | yes | Available serial ports with USB/device attributes |
326+
| POST | `/api/ports` | admin | Add new port configuration |
327+
| PUT | `/api/ports/<index>` | admin | Update port configuration |
328+
| DELETE | `/api/ports/<index>` | admin | Delete port configuration |
329+
| DELETE | `/api/ports/<p>/connections/<s>/<c>` | admin | Disconnect client |
330+
| PUT | `/api/ports/<index>/signals` | admin | Set RTS/DTR signals |
331+
| GET | `/api/signals` | yes | Signal states for all ports |
332+
| GET | `/api/users` | yes | List users |
333+
| POST | `/api/users` | admin | Add user |
334+
| PUT | `/api/users/<login>` | admin | Update user |
335+
| DELETE | `/api/users/<login>` | admin | Delete user |
336+
| GET | `/xterm/<endpoint>` | no | WebSocket VT100 terminal |
337+
| GET | `/raw/<endpoint>` | no | WebSocket raw terminal |
293338

294339
Authentication: `Authorization: Bearer <token>` header or `?token=<token>` query parameter. Without `auth` configuration, all endpoints are accessible without authentication.
295340

@@ -374,7 +419,7 @@ For system service, use `sudo systemctl` instead of `systemctl --user`.
374419

375420
- Python 3.8+
376421
- pyserial 3.0+
377-
- uhttp-server 2.3.0+ (for HTTP/API)
422+
- uhttp-server 2.3.2+ (for HTTP/API and WebSocket)
378423

379424
### Running on
380425

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ classifiers = [
2121
]
2222
dependencies = [
2323
"pyserial>=3.0",
24-
"uhttp-server>=2.3.1",
24+
"uhttp-server>=2.3.2",
2525
]
2626

2727
[project.urls]

ser2tcp/connection_control.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@
2323
SIGNAL_BITS = {name: i for i, name in enumerate(SIGNAL_NAMES)}
2424

2525

26-
def wrap_control(connection_class, control_config):
26+
def wrap_control(connection_class, control_config, data_enabled=True):
2727
"""Wrap a connection class with control protocol handling.
2828
2929
Returns a new class that escapes 0xFF in outgoing data and parses
3030
escape sequences in incoming data for signal control commands.
31+
When data_enabled=False, only control commands are processed,
32+
0xFF escaping is skipped and data bytes are ignored.
3133
"""
3234
signals = control_config.get('signals', [])
3335
signal_set = set(s.lower() for s in signals)
3436
rts_enabled = bool(control_config.get('rts'))
3537
dtr_enabled = bool(control_config.get('dtr'))
38+
forward_data = data_enabled
3639

3740
class ControlConnection(connection_class):
3841

@@ -42,9 +45,12 @@ def __init__(self, *args, **kwargs):
4245
self._ctl_signals = signal_set
4346
self._ctl_rts = rts_enabled
4447
self._ctl_dtr = dtr_enabled
48+
self._ctl_data = forward_data
4549

4650
def send(self, data):
47-
"""Send data with 0xFF escaped"""
51+
"""Send data with 0xFF escaped (skipped when data disabled)"""
52+
if not self._ctl_data:
53+
return 0
4854
return super().send(data.replace(b'\xff', b'\xff\xff'))
4955

5056
def send_signal_report(self, bitmask):
@@ -70,12 +76,13 @@ def on_received(self, data):
7076
continue
7177
if ESCAPE in data:
7278
index = data.index(ESCAPE)
73-
if index > 0:
79+
if self._ctl_data and index > 0:
7480
clean.extend(data[:index])
7581
del data[:index + 1]
7682
self._ctl_escape = True
7783
else:
78-
clean.extend(data)
84+
if self._ctl_data:
85+
clean.extend(data)
7986
break
8087
if clean:
8188
self._serial.send(bytes(clean))

0 commit comments

Comments
 (0)