c970272361
增加只读启动和可调用采集 API,保留单路数据协议。通过 28 项模拟回归测试、Ruff 和文档检查;实机限制见 docs/uart.md。
142 lines
6.7 KiB
Markdown
142 lines
6.7 KiB
Markdown
# UART finger service
|
||
|
||
`serial/example_uart.py` processes 1–4 finger cameras or recorded videos and
|
||
publishes the SDK's normal and shear values over a separate data UART. Python
|
||
3.10+, the board's installed `orisys` SDK and `pyserial` are required. Run inside
|
||
the board environment prepared by `setup/sdk_setup.sh`; install `pyserial` there
|
||
with `python -m pip install pyserial` if needed.
|
||
|
||
## Launch from the login console
|
||
|
||
The TTL login console and data UART must use **different device ports**. Start
|
||
the script in the login shell; connect the data receiver to the port selected
|
||
by `--port`. A shell/getty or another serial program must not own that data port.
|
||
The default is `/dev/ttyHS1`, 115200 baud, 8N1, with no flow control. This service
|
||
does not configure pin multiplexing or enable the board's UART hardware.
|
||
|
||
From the board's `dragon-q6a` checkout:
|
||
|
||
```bash
|
||
# Host commands control connection and streaming (default: one camera).
|
||
python serial/example_uart.py --port /dev/ttyHS1 --config finger
|
||
|
||
# Four configured sources; use 1, 2, 3 or 4 entries as required.
|
||
python serial/example_uart.py --port /dev/ttyHS1 --sources 0 2 4 6
|
||
|
||
# A passive receiver needs only to read: connection and streaming start automatically.
|
||
python serial/example_uart.py --port /dev/ttyHS1 --sources 0 2 --read-only
|
||
|
||
# Start streaming immediately, while still accepting UART commands.
|
||
python serial/example_uart.py --port /dev/ttyHS1 --sources 0 2 --autostart
|
||
```
|
||
|
||
Use actual camera indices/device paths discovered on the board; `0 2 4 6` is
|
||
only an example. Slot IDs are fixed by `--sources` order, not camera index.
|
||
Repeat `--config` once per source for different sensor configurations, or supply
|
||
one shared configuration. Existing `--camera-id` and `--video` remain available
|
||
for a single source. `--sources` replaces those options. `--show` requires a local
|
||
GUI and is off by default. Logs go to the login console, replies to the data UART.
|
||
|
||
In `--read-only` mode, all incoming UART commands are ignored. End the process
|
||
with Ctrl-C in the login shell. Startup still emits `OK`/`ERR` records; the receiver
|
||
should select `force,` lines. Without `--autostart` or `--read-only`, a passive
|
||
reader receives only `OK ready` until a host sends `connect` and `run`.
|
||
|
||
## Command protocol
|
||
|
||
Commands are UTF-8, case-insensitive, terminated by LF, CR or CRLF. Incomplete
|
||
lines remain buffered across serial timeouts. Lines exceeding 1024 bytes are
|
||
discarded through their terminator and produce `ERR command ...` in control mode.
|
||
Quote file paths containing spaces. Linux device paths are recommended on Q6A.
|
||
|
||
| Command | Behavior |
|
||
|---|---|
|
||
| `search` | List cameras, then `OK search N`; use before connecting. Rejected while streaming. |
|
||
| `connect` | Connect slot 1 using its configured source; in single-source mode, prefer the latest search result for a camera. |
|
||
| `connect SOURCE` | Legacy form: connect slot 1 to this camera index, device path or file. |
|
||
| `connect ID SOURCE` | Connect/replace one slot; stop streaming that slot. Other slots keep their connections. |
|
||
| `connect all` | Connect all configured slots; report each success/failure independently. |
|
||
| `run [ID\|all]` | Stream the selected slot, or all connected slots when omitted. Requires a connection. |
|
||
| `read [ID\|all]` | Publish one newly processed measurement per selected slot; defaults to all configured slots. Does not start streaming. |
|
||
| `stop [ID\|all]` | Stop selected streaming, or all when omitted; keep cameras connected. |
|
||
| `quit` / `exit` | Exit and release all camera and UART resources. |
|
||
|
||
`connect 2` means **camera index 2 on slot 1**, preserving the old protocol.
|
||
To connect slot 2, send `connect 2 /dev/video2` or use `connect all`.
|
||
There are no motor-control commands; these commands control acquisition only.
|
||
|
||
Single-source measurements retain the original format:
|
||
|
||
```text
|
||
force,1.250000,-2.500000,3.750000
|
||
```
|
||
|
||
When configured with multiple sources, measurements include the slot ID even if
|
||
only one source connected successfully:
|
||
|
||
```text
|
||
OK connect 2 /dev/video2 (camera)
|
||
OK run 1 2
|
||
force,1,1.250000,-2.500000,3.750000
|
||
force,2,0.500000,0.100000,-0.200000
|
||
ERR run 2 processing failed
|
||
OK eof 1
|
||
```
|
||
|
||
Force fields are `FNORMAL`, `FSHEARX`, `FSHEARY` in SDK axes. They may be raw
|
||
or calibrated; **do not assume Newtons**. The Python API exposes SDK units per
|
||
reading, using `unknown` if an older SDK does not expose them. CSV preserves
|
||
the original fields and carries no units, timestamps or cross-sensor synchronization.
|
||
|
||
Each processing failure or EOF stops only the affected slot. Processing exceptions
|
||
produce `ERR`, never fabricated zero force. `--no-loop-video` stops files at EOF;
|
||
the default tries one rewind, resets the tracker and retries one frame. Failed
|
||
preparation releases the newly opened runtime; replacing a connection releases
|
||
the old one before opening the replacement. A failed replacement requires retrying
|
||
`connect`. Startup continues with any successfully connected slots and fails if
|
||
none connect. Reconnect and run explicitly after a camera failure.
|
||
|
||
## Callable acquisition API
|
||
|
||
`serial/finger_api.py` owns acquisition and runtime cleanup; importing it opens
|
||
no UART or camera. Place that directory on the caller's module search path:
|
||
|
||
```python
|
||
from finger_api import FingerSensorBank
|
||
|
||
bank = FingerSensorBank([0, 2], configs=["finger"])
|
||
try:
|
||
bank.connect(1)
|
||
bank.connect(2)
|
||
reading = bank.read(1)
|
||
if reading is not None:
|
||
print(reading.normal, reading.shear_x, reading.shear_y, reading.units)
|
||
finally:
|
||
bank.close()
|
||
```
|
||
|
||
The caller owns one thread and must close the bank. `read()` returns `None` on
|
||
capture failure/EOF and raises on processing failure. It returns an image and
|
||
three scalar forces from the same processed frame. One file or camera is read
|
||
at a time; the API introduces no workers beyond those owned by the SDK.
|
||
|
||
## Verification and limits
|
||
|
||
Hardware-free regression tests cover command fragmentation, CR/LF, legacy output,
|
||
1–4 slot identities, selective control, failure isolation, read-only startup and
|
||
cleanup. They use in-memory serial and runtime fakes, not physical devices:
|
||
|
||
```bash
|
||
python -m unittest discover -s tests -v
|
||
```
|
||
|
||
Q6A UART wiring, installed ARM SDK compatibility, camera bandwidth, sustained
|
||
frame rates and latency remain unverified. The shipped ARM wheel is compiled
|
||
`orisys 0.5.0`; local tests do not execute it. SDK capture and processing remain
|
||
synchronous from this service's perspective. Commands are checked before each
|
||
round; a slow grab, processing call, connection or camera scan can delay them.
|
||
Serial writes have a finite timeout (default 1 second), and transport failure
|
||
terminates the service with cleanup. At 115200 baud/8N1, the data budget is
|
||
11,520 bytes/s, shared by all measurements and replies. Measure actual frame
|
||
rates and stop latency on the board before choosing a higher baud rate.
|