RK3588-side control program for the interceptor dock.
The primary path is now:
CLI / future virtual device
-> interceptorctl daemon over /tmp/interceptorctl.sock
-> single owned /dev/mcu
-> STM32 USART1 Package protocol
Only the daemon opens /dev/mcu. Do not let other programs open /dev/mcu
directly, otherwise ACK packets, status pushes, OTA traffic, and logs can be
consumed by the wrong process.
The current STM32 interceptor firmware runs USART1 in silent request-response mode: debug, error, status, and motor-position push packets are suppressed. Only command ACK/data responses are expected during normal operation.
Current released STM32 firmware version: 0x003F.
Current RK3588 interceptorctl release branch: main.
Firmware 0x003F keeps the field-verified fan, runtime 90/120-degree setting,
and dedicated MCU command 23 readback. RK/API door open, emergency-stop
release auto-open, and the physical cover button now share one configured target.
Firmware selection:
0x003F: default release; unifies RK/API, emergency-stop release, and physical-button open actions on the configured 90/120-degree target.0x003E: previous release; adds dedicated physical-button angle readback and verifies every setting with a separate MCU read after command 22.0x003D: previous release; adds the runtime physical-button open-angle setting and supports legacy readback through an empty command-22 request.0x003C: previous release; adds field-verified automatic aircraft fan control onPD11/PSW5.0x003B: previous release. Keeps the0x0039close-switch homing and motor recovery behavior. The physical cover button opens to motor position-345000/0.1deg(-34500motor-side degrees), approximately 90 degrees at the cover. APIdoor openretains the full-open target-427000/0.1deg.0x003A: withdrawn because its button target used an incorrect 0.1-degree conversion. Do not flash this version.0x0039: previous release. Uses PSW1 close-direction homing and automatic recovery from transient motor state-machine/CAN transaction failures.0x0038: compatibility release. Keeps the original0x0033motor-driver homing behavior and adds the same automatic recovery mechanism.
All listed recovery versions keep motor communication polling inside the MCU motor module at a 1-second interval. A failed transaction no longer leaves the MCU permanently stuck in its motor error state. Recovery never resumes the interrupted target automatically and does not automatically clear motor stall protection.
mcu.py: STM32 Package protocol and high-level MCU commands.daemon.py: single-owner daemon for/dev/mcu; exposes a local Unix socket.cli.py: simple command-line client.cpp_client/: C++17 typed client library and closed-loop example.GUIDANCE.md: customer integration guide for Python and C/C++ applications.GUIDANCE_CPP.md: detailed C++ customer integration guide with JSON examples.interceptorctl: shell wrapper forcli.py.run.sh: startsdaemon.py.systemd/interceptorctl.service: boot-time systemd service.systemd/interceptorctl.default: optional boot-time angle/settings overrides.factory_web/: production/debug browser interface and optional service template.tools/install_service.sh: installs and enablesinterceptorctl.service.tools/flash_mcu.py: independent STM32 firmware flashing tool.main.py: legacy HTTP debug service kept for reference.
cd /home/orangepi/interceptorctl
sudo ./tools/install_service.sh
systemctl status interceptorctl.serviceinstall_service.sh creates /etc/default/interceptorctl only when it does not
already exist, so upgrades preserve administrator overrides. The unified open
angle defaults to 90 degrees. A setting made with
interceptorctl door angle 90|120 is stored atomically in
/home/orangepi/.config/interceptorctl/settings.json and is restored after a
reboot. To force an administrator-managed value instead, uncomment
INTERCEPTOR_MANUAL_OPEN_ANGLE=90 (or 120) in
/etc/default/interceptorctl and restart the service.
The daemon applies the selected angle after startup in a background worker, so
the local socket is not delayed when the MCU is still booting. It performs a
separate MCU readback after every setting, periodically verifies the value, and
reapplies it after an MCU reset or serial reconnect. Firmware 0x003E and
later use dedicated command 23; 0x003D remains compatible through command 22.
Unified RK/API door open requires firmware 0x003F or later and fails closed
without sending a motion command on older firmware. Unaffected legacy commands
remain available.
The daemon creates:
/tmp/interceptorctl.sock
The systemd service writes daemon logs to:
/home/orangepi/interceptorctl/logs/interceptorctl.log
The log file is rotated by the daemon:
- Max size per file:
10 MB. - Max retained files:
50. - Rotated files use Python logging names such as
interceptorctl.log.1,interceptorctl.log.2, and so on.
Useful commands:
tail -f /home/orangepi/interceptorctl/logs/interceptorctl.log
ls -lh /home/orangepi/interceptorctl/logs/MCU traffic logs are decoded for humans. Example:
mcu tx name=motor_enable cmd=interceptor.motor_enable(set=12,id=14) payload=target=door(0) enabled=true raw=0001
mcu tx name=motor_trapezoid cmd=interceptor.motor_trapezoid(set=12,id=9) payload=target=door(0) position=181900/0.1deg(18190.0deg) speed=10000/0.1rpm(1000.0rpm) accel=1000rpm/s raw=008cc602001027e803
set=12 is the interceptor command set. id=14 is motor_enable,
id=9 is motor_trapezoid, and id=12 is ups_status. The raw hex is kept
at the end so low-level packet problems can still be checked.
Power polling is handled inside the STM32 firmware by MOD_Power485_Exec().
The MCU keeps a power status cache and advances one USART3 Modbus transaction
at a time without blocking the main loop while waiting for the power supply
reply.
Power control commands are accepted by the MCU and applied by the power polling
state machine. power status returns the latest MCU cache including
temperature; power set/on/off show the requested target rather than cached
measurements.
The production/debug web interface is tracked in factory_web/. It exposes the
same JSON CLI operations, shows live system/emergency-stop/switch status, and
provides the unified 90/120-degree open-angle selector. The low-level motor page
can also discover the single motor on can0, normalize a non-production CAN
ID to ID 1, read its homing configuration, and apply the production homing
configuration with readback verification. The raw JSON command log remains
visible for every operation.
cd /home/orangepi/interceptorctl/factory_web
python3 -m uvicorn app:app --host 0.0.0.0 --port 8080 --no-proxy-headersSee factory_web/README.md for validation and the optional systemd template.
The web service is not installed or enabled by the interceptorctl installer.
On the RK3588 board, MCU firmware is stored under:
/home/orangepi/interceptorctl/tools/
Current artifact: sbdock_0x003F_unified_open_angle.bin (74700 bytes,
SHA256 6c2dcaaeee4b99548970519a2296e1662d413686d59379f64bd83742be53c77f).
Flash command:
sudo /usr/bin/python3 /home/orangepi/interceptorctl/tools/flash_mcu.py \
/home/orangepi/interceptorctl/tools/sbdock_0x003F_unified_open_angle.binPreview without flashing:
sudo /usr/bin/python3 /home/orangepi/interceptorctl/tools/flash_mcu.py --dry-run \
/home/orangepi/interceptorctl/tools/sbdock_0x003F_unified_open_angle.binflash_mcu.py stops interceptorctl.service, drives BOOT0/RESET GPIO, runs
stm32loader, then starts interceptorctl.service again. It does not start the
old sbmcu.service.
Global options:
./interceptorctl --json <command>
./interceptorctl --socket /tmp/interceptorctl.sock <command>./interceptorctl version # read STM32 firmware version
./interceptorctl status # read combined motor and power status
./interceptorctl estop # read hardware/software emergency-stop status
./interceptorctl stop # set MCU software motor stop
./interceptorctl release-stop # clear software stop and reset motor state machinesThe new linked mechanics use one motor only. door open first synchronizes the
configured angle, then sends MCU door-open command 1; the MCU, physical button,
and emergency-stop release therefore use the same 90°/120° target. door close
continues to send a low-level trapezoid target at the calibrated zero position.
By default the CLI returns after the MCU accepts the target. Add --wait to wait
for motion completion, or --timeout <seconds> to change the wait timeout.
./interceptorctl door open
./interceptorctl door close
./interceptorctl door open --wait
./interceptorctl door close --wait --timeout 20
./interceptorctl door angle # query the unified open angle
./interceptorctl door angle 90 # set and persist 90 degrees
./interceptorctl door angle 120 # set and persist 120 degreesThe angle setting controls RK/API door open, physical cover-button open
actions, and the automatic open after emergency-stop release.
The JSON socket equivalents are:
{"cmd":"manual_open_angle_get","args":{}}
{"cmd":"manual_open_angle_set","args":{"angle":120}}Responses include button_open_angle_deg, configured_angle_deg,
applied_angle_deg, mcu_readback_command_id, applied, supported,
firmware_version, status, and persisted. On 0x003E and later, a
successful set is reported only after dedicated MCU command 23 reads back the requested value.
The setting command is accepted only for 90 or 120 degrees.
./interceptorctl motor status
./interceptorctl motor scan
./interceptorctl motor config read
./interceptorctl motor config read --id 1
./interceptorctl motor config auto
./interceptorctl motor config auto --id 1
./interceptorctl motor door enable
./interceptorctl motor door disable
./interceptorctl motor door home
./interceptorctl motor door home --wait --timeout 60
./interceptorctl motor door home-stop
./interceptorctl motor door trap --pos 181900 --speed 3000 --accel 100
./interceptorctl motor door trap --pos 181900 --speed 3000 --accel 100 --wait
./interceptorctl motor door trap --pos 181900 --speed 3000 --accel 100 --wait --timeout 20home starts the close-side homing test. The motor driver must already contain
the required homing direction and motion parameters; the MCU does not read or
rewrite those parameters. Before enabling the motor, the MCU debounces PSW1 and
reads the motor status. A live stall or latched stall protection terminates the
request without enabling, homing, or automatically clearing the protection. If
PSW1 is already active, the MCU does not start homing and only disables, clears,
and verifies position zero. Otherwise it starts homing, waits for PSW1, stops
homing, allows the driver to settle, clears and verifies position zero, and
finally disables the motor. A successful asynchronous event uses
reason=homing_switch_zeroed. home-stop cancels this sequence.
After successful homing, the configured close coordinate is 0. The unified
open coordinate is -345000 at 90 degrees or -427000 at 120 degrees, in
motor-side 0.1 degree units. trap means
absolute trapezoid motion in raw motor protocol units. door, motor, and
motor1 select the linked motor.
motor scan and motor config ... are production-commissioning operations
implemented by the RK daemon directly on SocketCAN. Scanning is read-only: it
sends the version query 1F 6B directly to candidate motor IDs 1 through 32,
one ID at a time. It does not use a CAN broadcast request. config read uses
the same directed scan when --id is omitted, then reads the driver's current
homing parameters. config auto also scans IDs 1 through 32 and continues only
when exactly one motor is found; an optional --id is treated as an assertion
against that scan result. If the discovered ID is not 1, the daemon first
reads and records the driver status, persistently changes it to ID 1, and
repeats the directed 1-through-32 scan, which must find only ID 1. It then writes the
following fixed production values and reads them back field by field:
- sensorless/collision homing, clockwise direction
- homing speed
300 RPM, timeout120000 ms - collision detection speed
80 RPM, current2000 mA, time400 ms - power-on automatic homing disabled; non-volatile storage requested
These commands do not enable or disable the driver, start homing, clear position, or issue any movement command. The production motor normally powers up enabled, so that observed state is reported but does not block ID or homing parameter configuration. Before changing a non-1 ID, the daemon actively reads its status to confirm the addressed motor is responsive. It also waits for the MCU's periodic CAN traffic to finish before sending the multi-frame configuration. Do not press the physical cover button while commissioning. The ID and homing storage requests are asynchronous and cannot be independently proved by their readback commands; the live ID is re-scanned, all readable homing parameters are compared strictly, and JSON distinguishes requested storage from verified live values.
./interceptorctl power status
./interceptorctl power fault # detailed input/PFC/thermal/fan/alarm diagnosis
./interceptorctl power temp # alias of power status
./interceptorctl power set 24.00 1.00 # voltage/current in V/A
./interceptorctl power on
./interceptorctl power off
./interceptorctl power raw --hex "01 03 00 1c 00 01 e4 0d"
./interceptorctl power raw --hex "01 03 00 1c 00 01 e4 0d" --timeout-ms 1000 --idle-ms 20power raw is a debug command that sends exact raw bytes to the MCU USART3
power RS485 path. It does not auto-fill CRC. This example reads a GPpower3000
power supply at Modbus address 0x01.
power fault is read-only. It uses the same raw passthrough internally and
decodes the AZ-series diagnostic registers documented by the power supply
manufacturer. The output includes AC and BUS voltage, PFC self-test status,
control mode, IOA function, local/remote measurements, five temperatures, both
fan speeds, every manufacturer-defined alarm bit within bits 0 through 18,
diagnosis messages, and the
raw Modbus request/response frames. Use ./interceptorctl --json power fault
for the corresponding structured response.
./interceptorctl ups status./interceptorctl env status./interceptorctl led status
./interceptorctl led jc off
./interceptorctl led jc red
./interceptorctl led jc green
./interceptorctl led jc both
./interceptorctl led jc yellow
./interceptorctl led cd off
./interceptorctl led cd red
./interceptorctl led cd green
./interceptorctl led cd both
./interceptorctl led cd yellow
./interceptorctl led wz off
./interceptorctl led wz red
./interceptorctl led wz green
./interceptorctl led wz both
./interceptorctl led wz yellow
./interceptorctl led dp off
./interceptorctl led dp red
./interceptorctl led dp green
./interceptorctl led dp both
./interceptorctl led dp yellow
./interceptorctl led all off
./interceptorctl led all red
./interceptorctl led all green
./interceptorctl led all both
./interceptorctl led all yellow
./interceptorctl led mask 0x10
./interceptorctl led mask 255yellow turns on red and green together, same as both.
./interceptorctl switch statusThese commands read the active-low PSW1/PSW2/PSW3/PSW4 GPIO inputs:
PSW1/PD15:module_reached_switchPSW2/PD14:aircraft_position_switchPSW3/PD13:cover_buttonPSW4/PD12:aircraft_present_switch
The semantic fields are true when the switch pulls the input to GND.
raw_level_mask keeps the raw GPIO level before active-low conversion:
bit0=PSW1, bit1=PSW2, bit2=PSW3, bit3=PSW4, and bit=1 means high level.
manual_action_name is none, manual_opening, or manual_closing and
reports MCU-side cover-button handling.
When the cover button requests a manual close, the MCU accepts the request
only when aircraft_position_switch (PSW2) and
aircraft_present_switch (PSW4) have the same state: both active or both
inactive. A request is blocked when exactly one input is active. The inputs
are sampled only when the button triggers the close; changing them after
motion starts does not stop the motion. module_reached_switch (PSW1) is not
part of this close interlock, but the close-side homing test uses it as the
zero-position trigger.
./interceptorctl ac status
./interceptorctl ac settings
./interceptorctl ac power on
./interceptorctl ac power off
./interceptorctl ac power on --no-wait
./interceptorctl ac power off --timeout 3
./interceptorctl ac cool on
./interceptorctl ac cool off
./interceptorctl ac heat on
./interceptorctl ac heat off
./interceptorctl ac mode normal
./interceptorctl ac mode silent
./interceptorctl ac cool-temp 30.0
./interceptorctl ac cool-diff 3.0
./interceptorctl ac heat-temp 5.0
./interceptorctl ac heat-diff 8.0
./interceptorctl ac dehumid 60
./interceptorctl ac humidity 55The AC CLI waits for the MCU UART5 Modbus reply by default. Add --no-wait to
return after queue acceptance, or --timeout <seconds> to change the wait time.
Use ac settings after changing parameters to confirm the values read back by
the MCU from the air-conditioner registers.
./interceptorctl aircraft read
./interceptorctl aircraft read --timeout-ms 500
./interceptorctl aircraft read --timeout-ms 500 --max-len 80
./interceptorctl aircraft xfer --text ping
./interceptorctl aircraft xfer --text ping --append-cr
./interceptorctl aircraft xfer --text ping --append-lf
./interceptorctl aircraft xfer --text ping --append-cr --append-lf --timeout-ms 1500 --idle-ms 30
./interceptorctl aircraft xfer --hex "01 02 03 0d" --timeout-ms 1500 --idle-ms 30
./interceptorctl aircraft xfer --hex "01,02,03,0d"
./interceptorctl aircraft xfer --hex "01:02:03:0d"Use --json to print the raw daemon response:
./interceptorctl --json statusThe motor ... trap interface uses raw motor protocol units:
--pos: absolute target position in0.1 degree.--speed: max speed in0.1 RPM.--accel: accel and decel inRPM/s.
status and motor status return motor positions in 0.1 degree plus driver
flags such as enabled, stall, and reached. The communicated field is
maintained by the MCU from valid motor replies. The separate RK SocketCAN
observation is diagnostic data used to assist asynchronous motion monitoring.
env status reads the GXHT30 temperature/humidity status cached by the MCU.
The STM32 polls the I2C1 sensor at a low rate, using the GXHT30 0.5 mps
periodic mode, so this data is intended for display and logs rather than hard
real-time control.
- Temperature unit:
0.01C. - Humidity unit:
0.01%RH. last_error=0(ok)means the latest accepted sample is valid.- Typical address is
0x44.
led status reads the TCA9554 LED expander on I2C1. The device address is
0x20 because A0/A1/A2 are tied to GND. All P0..P7 pins are configured as
outputs; output bit 1 turns the corresponding LED channel on through the
external S8050 low-side driver.
Bit mapping:
- bit0:
JC_R, bit1:JC_G - bit2:
CD_R, bit3:CD_G - bit4:
WZ_R, bit5:WZ_G - bit6:
DP_R, bit7:DP_G
Examples:
./interceptorctl led status
./interceptorctl led wz red
./interceptorctl led wz green
./interceptorctl led wz both
./interceptorctl led all off
./interceptorctl led mask 0x10both turns on the red and green channels of the selected group. mask
writes the raw 8-bit output register and then reads the TCA9554 registers back
for closed-loop verification.
ac status reads the HCNC4A air-conditioner status cached by the MCU UART5
RS485 state machine. The field AC protocol is plaintext Modbus RTU: the MCU
builds a normal Modbus frame, appends CRC16, sends that frame over UART5, and
validates the plaintext Modbus response CRC.
Important units:
- Temperature registers:
0.1C. - DC input voltage/current:
0.1V/0.1A. - Fan speed:
rpm. - Cooling capacity:
W. - Cooling/heating parameter commands take human-readable Celsius values in the
CLI, then send protocol values in
0.1C.
Supported control commands:
./interceptorctl ac status
./interceptorctl ac settings
./interceptorctl ac power on
./interceptorctl ac power off
./interceptorctl ac cool on
./interceptorctl ac cool off
./interceptorctl ac heat on
./interceptorctl ac heat off
./interceptorctl ac mode normal
./interceptorctl ac mode silent
./interceptorctl ac cool-temp 30.0
./interceptorctl ac cool-diff 3.0
./interceptorctl ac heat-temp 5.0
./interceptorctl ac heat-diff 8.0
./interceptorctl ac dehumid 60
./interceptorctl ac humidity 55Parameter meanings:
cool-temp: compressor cooling start temperature, protocol register0x000A, valid range20.0..50.0C.cool-diff: compressor cooling hysteresis, protocol register0x000C, valid range1.0..10.0C.heat-temp: heating start temperature, protocol register0x001C, valid range-40.0..25.0C.heat-diff: heating hysteresis, protocol register0x001E, valid range5.0..15.0C.dehumid: dehumidification setpoint, protocol register0x0028, valid range10..90%.humidity: monitor humidity downlink, protocol register0x020B, valid range0..100%; this is not the dehumidification setpoint.
For example, cool-temp 30.0 plus cool-diff 3.0 means the controller is
expected to start cooling around 30.0C return-air temperature and stop around
27.0C, subject to the air-conditioner's internal protection logic and field
test results. After any write, run ./interceptorctl ac settings to confirm
the actual register readback.
The MCU accepts one AC control request into its lightweight queue, sends it from
the UART5 state machine, then records last_control_result. The CLI waits for
that completion by default; use --no-wait to return immediately after queue
acceptance.
aircraft read passively reads raw bytes already received from STM32 UART4
RS485. It does not split protocol frames; customer code should parse the
aircraft protocol.
aircraft xfer sends one payload through STM32 UART4 RS485 and waits for one
response. The STM32 uses idle-time framing because the aircraft protocol is not
known yet.
Current MCU-side UART4 RS485 settings:
-
115200 8N1, no parity, no hardware flow control. -
Max TX payload:
220bytes. -
Max RX payload:
220bytes. -
Passive RX ring buffer:
512bytes. If the customer application reads too slowly, oldest bytes are dropped and reported bydropped. -
RS485 direction is controlled by STM32 GPIO: transmit before sending, back to receive after UART4 transmission-complete interrupt.
-
aircraft read --timeout-ms: waits for at least one buffered byte. -
aircraft read --max-len: maximum bytes returned in one call. -
--hex: raw bytes, for binary protocols. -
--text: UTF-8 text, useful for simple loopback tests. -
--append-cr/--append-lf: append line endings when needed. -
--timeout-ms: total UART4 response wait time. -
--idle-ms: response is complete after this many idle milliseconds.
The current implementation exposes both passive raw-byte receive and
request-response transfer. A future virtual serial device can be built on top
of the same daemon and MCU commands without letting customer programs open
/dev/mcu directly.
For detailed PC serial-assistant and optional USB-RS485 responder tests, see
TESTING.md.