# UART finger service Start the service with an explicit list of 1-4 camera sources. It opens and warms up **every source first**, then opens the data UART and starts publishing. If any source fails to prepare, all opened sources are closed and the process exits with status 1. No partial camera set is published. ## Connect the cameras and start once Plug the USB cameras into the Q6A. In its TTL login shell, identify their actual capture device paths before launching; one USB camera can expose more than one `/dev/video*` node, so do not assume every node represents a different camera. For example, `v4l2-ctl --list-devices` shows device groups. Select one usable capture node per camera. Use the board environment prepared by `setup/sdk_setup.sh`; it needs Python 3.10+, the `orisys` SDK and `pyserial`. Run from the board's `dragon-q6a` directory. These paths are examples, not detected hardware identities: ```bash # One camera. python serial/example_uart.py --port /dev/ttyHS1 --sources /dev/video0 # Two cameras. python serial/example_uart.py --port /dev/ttyHS1 --sources /dev/video0 /dev/video2 # Three cameras. python serial/example_uart.py --port /dev/ttyHS1 --sources /dev/video0 /dev/video2 /dev/video4 # Four cameras. python serial/example_uart.py --port /dev/ttyHS1 --sources /dev/video0 /dev/video2 /dev/video4 /dev/video6 ``` The first source is sensor 1, the second sensor 2, and so on. The number of listed sources determines the sensor count. Camera indices such as `--sources 0 2` are also accepted. Sources must be distinct. A shared `--config finger` is the default; repeat `--config` once per source to use different configurations. The login console prints each sensor's source/config mapping during preparation. There is no UART connection command: physical connection and source selection happen before startup, and software connection happens automatically. The TTL login console and data UART must use **different ports**. The data UART defaults to `/dev/ttyHS1`, 115200 baud, 8N1 without flow control. It must not be occupied by getty or another serial program. This script does not enable UART pins or configure the board's hardware. Logs stay in the login console; protocol records go to the data UART. `--show` is optional desktop debugging. For a passive receiver, append `--read-only` to the same startup command. Startup and output are identical, but incoming UART commands are discarded. Exit using Ctrl-C in the login shell. The receiver should filter `force,` records because startup and failures also produce status records. ## One protocol for every sensor count After all sources are ready, the data UART reports `OK ready N`, then immediately streams each complete processing round in source order. Every measurement has five CSV fields, including a sensor ID even when only one camera is configured: ```text OK ready 2 force,1,1.250000,-2.500000,3.750000 force,2,0.500000,0.100000,-0.200000 ``` The fields are `force,sensor_id,FNORMAL,FSHEARX,FSHEARY`. Axes follow the SDK; units depend on calibration and must not be assumed to be Newtons. CSV carries no timestamp or units. Frames are processed sequentially, not synchronized across cameras. All inputs for a round must succeed before that round is published. A transport failure can still interrupt transmission between lines. Control mode accepts only four commands, each acting on the entire camera set: | Command | Result | |---|---| | `stop` | Pause continuous output and reply `OK stop`; keep cameras open. | | `start` | Resume continuous output and reply `OK start`. | | `read` | Process and publish one complete round; preserve streaming state on success. | | `quit` | Reply `OK quit`, close all cameras and UART, then exit. | Commands take no arguments. They are case-insensitive UTF-8 lines terminated by LF, CR or CRLF. Partial lines survive read timeouts; commands exceeding 1024 bytes are discarded through their terminator. Unknown commands or arguments produce `ERR`. The former protocol and source-selection options have been removed; receivers must use this single CSV format and explicit `--sources`. A capture or processing failure produces `ERR read sensor ID: ...` and pauses output for the whole set. Measurements are never replaced with zeros. Sources remain open; `start` retries processing, or restart the service to reopen them. For recorded files, `--no-loop-video` reports `OK eof sensor ID: video ended` and pauses the whole set at EOF. Otherwise, one rewind and tracker reset is attempted per read. Ctrl-C always ends the process from the login console. ## Callable acquisition API `serial/finger_api.py` has no UART dependency and opens no resources on import. Put its directory on the caller's module search path. Use the same connection-first lifecycle as the service: ```python from finger_api import FingerSensorBank bank = FingerSensorBank([0, 2], configs=["finger"]) try: bank.connect_all() for reading in bank.read_all(): print(reading.sensor_id, reading.normal, reading.shear_x, reading.shear_y, reading.units) finally: bank.close() ``` One thread owns the bank. `connect_all()` rolls back every opened runtime on failure. `read_all()` returns a tuple only when every source succeeds; it raises `EOFError` for file EOF and `RuntimeError` for capture/processing failure. Each reading contains the unrolled image, three scalar forces and SDK units from its own runtime. Older SDKs without a units property report `unknown`. Reconnection requires closing the bank before calling `connect_all()` again. ## Backend evidence and remaining limits The SDK's `FingerRuntime.open()` constructs a separate capture object, `FrameUnwarper`, `TactileProcessor` and `Sensor` for each source. Live sources have separate SDK capture workers. The service holds all runtimes open together and reads them sequentially. SDK implementation is unchanged. Verification on 2026-10-14 used local SDK revision `3953747f1726b197a3f327a38c65d9878e333e12`: real SDK instances processed 1, 2, 3 and 4 distinct recorded inputs, three complete rounds for each count. Capture handles and sensor objects were distinct; optical-flow arrays did not share memory. All capture resources were released after each case. Run the same check with a recording containing at least 12 frames; put generated clips and caches in an isolated task scratch directory: ```bash python tests/check_backend.py --recording /path/to/recording.avi --scratch .scratch/uart-connect-first/backend-run python -m unittest discover -s tests -v ``` Unit tests separately verify startup ordering, full rollback, a uniform protocol, removed commands/options, command framing and failure behavior. These tests do not prove Q6A USB bandwidth or the shipped compiled ARM `orisys 0.5.0` wheel. Run the backend check in the board's installed environment to verify that SDK. Four live cameras, serial wiring, sustained frame rates and command latency still require hardware validation. A slow capture/processing call delays command handling. Serial writes time out after a configurable interval (default 1 second). At 115200 baud/8N1, all data and replies share 11,520 bytes/s.