Skip to content

Repository files navigation

interceptorctl

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 on PD11/PSW5.
  • 0x003B: previous release. Keeps the 0x0039 close-switch homing and motor recovery behavior. The physical cover button opens to motor position -345000/0.1deg (-34500 motor-side degrees), approximately 90 degrees at the cover. API door open retains 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 original 0x0033 motor-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.

Files

  • 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 for cli.py.
  • run.sh: starts daemon.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 enables interceptorctl.service.
  • tools/flash_mcu.py: independent STM32 firmware flashing tool.
  • main.py: legacy HTTP debug service kept for reference.

Start

cd /home/orangepi/interceptorctl
sudo ./tools/install_service.sh
systemctl status interceptorctl.service

install_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

Logs

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.

Factory Web UI

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-headers

See factory_web/README.md for validation and the optional systemd template. The web service is not installed or enabled by the interceptorctl installer.

Firmware Flash

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.bin

Preview without flashing:

sudo /usr/bin/python3 /home/orangepi/interceptorctl/tools/flash_mcu.py --dry-run \
  /home/orangepi/interceptorctl/tools/sbdock_0x003F_unified_open_angle.bin

flash_mcu.py stops interceptorctl.service, drives BOOT0/RESET GPIO, runs stm32loader, then starts interceptorctl.service again. It does not start the old sbmcu.service.

CLI

Global options:

./interceptorctl --json <command>
./interceptorctl --socket /tmp/interceptorctl.sock <command>

System And Stop

./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 machines

Door Business Actions

The 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 degrees

The 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.

Low-Level Motor Debug

./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 20

home 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, timeout 120000 ms
  • collision detection speed 80 RPM, current 2000 mA, time 400 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.

GPpower3000 Power Supply

./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 20

power 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.

UPS

./interceptorctl ups status

Environment Sensor

./interceptorctl env status

LED Expander

./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 255

yellow turns on red and green together, same as both.

Switch Inputs

./interceptorctl switch status

These commands read the active-low PSW1/PSW2/PSW3/PSW4 GPIO inputs:

  • PSW1 / PD15: module_reached_switch
  • PSW2 / PD14: aircraft_position_switch
  • PSW3 / PD13: cover_button
  • PSW4 / 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.

Air Conditioner

./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 55

The 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.

Aircraft UART4 RS485 Passthrough

./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 status

The motor ... trap interface uses raw motor protocol units:

  • --pos: absolute target position in 0.1 degree.
  • --speed: max speed in 0.1 RPM.
  • --accel: accel and decel in RPM/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.

Environment Sensor

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 Expander

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 0x10

both 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.

Air Conditioner

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 55

Parameter meanings:

  • cool-temp: compressor cooling start temperature, protocol register 0x000A, valid range 20.0..50.0C.
  • cool-diff: compressor cooling hysteresis, protocol register 0x000C, valid range 1.0..10.0C.
  • heat-temp: heating start temperature, protocol register 0x001C, valid range -40.0..25.0C.
  • heat-diff: heating hysteresis, protocol register 0x001E, valid range 5.0..15.0C.
  • dehumid: dehumidification setpoint, protocol register 0x0028, valid range 10..90%.
  • humidity: monitor humidity downlink, protocol register 0x020B, valid range 0..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 Passthrough

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: 220 bytes.

  • Max RX payload: 220 bytes.

  • Passive RX ring buffer: 512 bytes. If the customer application reads too slowly, oldest bytes are dropped and reported by dropped.

  • 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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages