65860948b4
End-to-end implementation of Sprint 1 per docs/sprint-1-plan.md.
Builds: pio run -e esp32-dev SUCCESS, RAM 6.7%, Flash 26.5% (347 KB).
Tests: pytest 110/110 green; pio test -e native deferred (needs host
C++ compiler -- none on this Windows machine).
Firmware (firmware/ar_autopilot_v1/):
- platformio.ini: 4 envs (esp32-dev release, esp32-debug, native unity
tests, check static analysis). NMEA2000-library@4.22, NMEA2000_esp32@
1.0, eModbus@1.7.4 pinned.
- main.cpp: boot in STANDBY, FreeRTOS task spawn, returns to scheduler.
- system/: ar_log.h facade, task_config.h (priorities/stacks/cores
central table), heartbeat (1 Hz LED + uptime).
- modes/: STANDBY-only state machine; non-STANDBY rejected.
- hal/: di_do.cpp (5 DI + 10 DO with debounce + last-state cache),
rudder_sensor.cpp (100 Hz ADC + 5-sample median filter, Core 1),
rudder_actuator.cpp (DO1/DO2/DO3 with three safety interlocks:
power-off, STANDBY mode, limit switch).
- safety/: TWDT @ 2 s panic-on-expire; 50 Hz safety task on Core 1
enforcing DI1 physical disengage button, DI4 external alarm,
both-limit-switch interlock.
- protocols/modbus_slave.cpp: eModbus RTU server on UART2 @ 38400 8N1,
slave ID 1. 17 inputs + 19 discretes + 5 holdings + 4 coils. Reads
pull live telemetry; writes validate range and route to handlers.
- protocols/nmea2000_consumer.cpp: stack open with CAN TX=GPIO3
RX=GPIO1, subscribed to PGN 127250 (Heading) + PGN 127251 (Rate of
Turn). 5 s staleness flag built in for Sprint 6 alarm wiring.
- filters/median.h: templated MedianFilter<T,N> (host testable).
Cross-cutting:
- modbus_registers.yaml: single source of truth for the Modbus register
map. 45 entries.
- tools/gen_modbus_registers.py: YAML -> C++ header + Python module
generator with --check for drift detection.
- arautopilot/shared/modbus_register_map.py: generated Python mirror,
imported by Studio + tools.
- arautopilot/tests/test_modbus_register_map.py: 30 tests covering
schema, address uniqueness, range, spot-checks, and drift detection
(fails if YAML edited without regenerating).
- firmware/ar_autopilot_v1/tools/modbus_client_test.py: manual Modbus
client for poking the slave from a PC with USB-RS485 dongle.
- firmware/ar_autopilot_v1/test/test_median_filter/test_median.cpp:
8 Unity tests of the median filter (host-side, no Arduino dependency).
- docs/firmware.md: full operator + integrator guide (toolchain, build,
flash, expected boot log, troubleshooting, Sprint 1 capability matrix).
Architecture note: opted for Arduino-on-ESP32 only instead of the
proposed dual Arduino-as-ESP-IDF-component setup. Rationale documented
in CHANGELOG and docs/firmware.md -- Arduino-on-ESP32 already provides
the FreeRTOS primitives we need; dual framework adds fragility without
benefit at Sprint 1 scope. Reconsider in Sprint 8 (OTA + secure boot).
NOT in Sprint 1 (intentional per brief sec. 12):
- PID loops (inner/outer)
- True Course / Track Keeping
- Full alarm catalogue beyond DI1/DI4
- Knob driver
- Studio GUI / dedicated display
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
107 lines
7.3 KiB
YAML
107 lines
7.3 KiB
YAML
# =============================================================================
|
|
# AR-Autopilot -- Modbus RTU register map (single source of truth)
|
|
# =============================================================================
|
|
#
|
|
# This file is read by tools/gen_modbus_registers.py to produce:
|
|
#
|
|
# firmware/ar_autopilot_v1/src/protocols/modbus_registers.h
|
|
# -> compile-time constants used by the firmware
|
|
#
|
|
# arautopilot/shared/modbus_register_map.py
|
|
# -> runtime constants used by the Studio simulator, the dedicated
|
|
# display (via the Flutter side mirror), and the Python tools
|
|
#
|
|
# Do NOT edit the generated files by hand. Always edit this YAML and run:
|
|
#
|
|
# python tools/gen_modbus_registers.py
|
|
#
|
|
# Register types (Modbus RTU standard):
|
|
# discrete -- read-only single bit (function code 0x02) addr 10001+
|
|
# coil -- read-write single bit (function codes 0x01/0x05/0x0F) addr 1+
|
|
# input -- read-only 16-bit register (function code 0x04) addr 30001+
|
|
# holding -- read-write 16-bit register (function codes 0x03/0x06/0x10) addr 40001+
|
|
#
|
|
# Addresses below are zero-based offsets within their type. Modbus over the
|
|
# wire adds the type base (10001 / 1 / 30001 / 40001).
|
|
#
|
|
# Scale factor: physical value = raw * scale + offset
|
|
# e.g. heading_deg with scale=0.01 means raw 4500 -> 45.00 deg.
|
|
# =============================================================================
|
|
|
|
schema_version: "0.1.0"
|
|
slave_id: 1 # Modbus slave (server) address
|
|
baudrate: 38400 # bps; 8N1 framing assumed
|
|
parity: "N"
|
|
data_bits: 8
|
|
stop_bits: 1
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Discrete inputs (read-only bits) -- status flags published by the firmware
|
|
# -----------------------------------------------------------------------------
|
|
discretes:
|
|
- { addr: 0, name: PILOT_ENGAGED, desc: "1 if the pilot is currently engaged (any mode other than STANDBY)" }
|
|
- { addr: 1, name: DI_DISENGAGE_BUTTON, desc: "Live state of the physical Engage/Disengage push-button" }
|
|
- { addr: 2, name: DI_LIMIT_PORT, desc: "Port-side rudder mechanical end-stop reached" }
|
|
- { addr: 3, name: DI_LIMIT_STBD, desc: "Starboard-side rudder mechanical end-stop reached" }
|
|
- { addr: 4, name: DI_EXTERNAL_ALARM, desc: "External critical alarm (VMS / genset) asserted" }
|
|
- { addr: 5, name: DI_MANUAL_CONFIRM, desc: "Manual confirmation switch asserted (emergency override)" }
|
|
- { addr: 8, name: ACTUATOR_POWER, desc: "Master actuator power relay (DO3) commanded ON" }
|
|
- { addr: 9, name: ACTUATOR_DRIVING_PORT, desc: "Actuator direction output: driving to port" }
|
|
- { addr: 10, name: ACTUATOR_DRIVING_STBD, desc: "Actuator direction output: driving to starboard" }
|
|
- { addr: 16, name: ALARM_OFF_COURSE, desc: "Off-course warning active" }
|
|
- { addr: 17, name: ALARM_OFF_COURSE_SEVERE, desc: "Severe off-course alarm (auto-disengage)" }
|
|
- { addr: 18, name: ALARM_RUDDER_NOT_RESP, desc: "Rudder command sent but no feedback motion" }
|
|
- { addr: 19, name: ALARM_HEADING_LOST, desc: "NMEA 2000 heading PGN not received >5 s" }
|
|
- { addr: 20, name: ALARM_ACTUATOR_OVERCURR, desc: "Actuator current over threshold" }
|
|
- { addr: 21, name: ALARM_VOLTAGE_LOW, desc: "Supply voltage below safe threshold" }
|
|
- { addr: 22, name: ALARM_LIMIT_REACHED, desc: "Rudder reached mechanical end-stop" }
|
|
- { addr: 23, name: ALARM_WATCHDOG_TRIPPED, desc: "Firmware watchdog fired -- controller reset" }
|
|
- { addr: 24, name: ALARM_VMS_CRITICAL, desc: "VMS reported blackout or electrical fault" }
|
|
- { addr: 25, name: ANY_ALARM_ACTIVE, desc: "OR of all alarm bits (convenience)" }
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Coils (read-write bits) -- commands written by the display / Studio
|
|
# -----------------------------------------------------------------------------
|
|
coils:
|
|
- { addr: 0, name: CMD_ENGAGE_REQUEST, desc: "Rising edge requests pilot engagement (subject to interlocks)" }
|
|
- { addr: 1, name: CMD_DISENGAGE_REQUEST, desc: "Rising edge forces pilot to STANDBY" }
|
|
- { addr: 2, name: CMD_ACK_ALL_ALARMS, desc: "Rising edge acknowledges every active alarm" }
|
|
- { addr: 3, name: CMD_KNOB_ARM, desc: "Knob arming request (Sprint 7+)" }
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Input registers (read-only 16-bit words) -- telemetry
|
|
# -----------------------------------------------------------------------------
|
|
# Two-register values (e.g. timestamps, floats) occupy consecutive addresses.
|
|
# -----------------------------------------------------------------------------
|
|
inputs:
|
|
- { addr: 0, name: FW_VERSION_MAJOR, desc: "Firmware major version", unit: "" }
|
|
- { addr: 1, name: FW_VERSION_MINOR, desc: "Firmware minor version", unit: "" }
|
|
- { addr: 2, name: FW_VERSION_PATCH, desc: "Firmware patch version", unit: "" }
|
|
- { addr: 3, name: SCHEMA_VERSION, desc: "Modbus map schema version (0=v0.1.0)", unit: "" }
|
|
- { addr: 4, name: UPTIME_SECONDS_LO, desc: "Uptime seconds, low 16 bits", unit: "s" }
|
|
- { addr: 5, name: UPTIME_SECONDS_HI, desc: "Uptime seconds, high 16 bits", unit: "s" }
|
|
- { addr: 6, name: CURRENT_MODE, desc: "Current AutopilotMode (0=STANDBY,1=HH,2=TC,3=TK,4=DODGE)", unit: "" }
|
|
- { addr: 7, name: FREE_HEAP_KB, desc: "Current free heap, KiB", unit: "KiB" }
|
|
- { addr: 8, name: MIN_FREE_HEAP_KB, desc: "Minimum free heap since boot", unit: "KiB" }
|
|
|
|
- { addr: 16, name: RUDDER_ANGLE_DEG_X100, desc: "Filtered rudder angle, deg * 100 (-3500..+3500)", unit: "deg", scale: 0.01 }
|
|
- { addr: 17, name: RUDDER_RAW_ADC, desc: "Raw ADC reading after median filter (0..4095)", unit: "counts" }
|
|
- { addr: 18, name: RUDDER_VALID, desc: "1 if median filter has filled (>=5 samples)", unit: "" }
|
|
|
|
- { addr: 24, name: HEADING_DEG_X100, desc: "Current heading from NMEA 2000 PGN 127250, deg*100 (0..35999)", unit: "deg", scale: 0.01 }
|
|
- { addr: 25, name: ROT_DPS_X100, desc: "Rate of turn from PGN 127251, deg/s*100 (signed int16)", unit: "deg/s", scale: 0.01 }
|
|
- { addr: 26, name: HEADING_AGE_MS, desc: "Milliseconds since the last heading update (0..60000)", unit: "ms" }
|
|
|
|
- { addr: 32, name: BATTERY_VOLTAGE_X100, desc: "System battery voltage, V*100", unit: "V", scale: 0.01 }
|
|
- { addr: 33, name: ACTUATOR_CURRENT_X100, desc: "Actuator current, A*100", unit: "A", scale: 0.01 }
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Holding registers (read-write 16-bit words) -- setpoints and config
|
|
# -----------------------------------------------------------------------------
|
|
holdings:
|
|
- { addr: 0, name: MODE_REQUEST, desc: "Mode requested by operator (0=STANDBY,1=HH,2=TC,3=TK,4=DODGE)", unit: "" }
|
|
- { addr: 1, name: HEADING_SETPOINT_X100, desc: "Desired heading, deg*100", unit: "deg", scale: 0.01 }
|
|
- { addr: 2, name: BRIGHTNESS_PCT, desc: "Display brightness 0..100", unit: "%" }
|
|
- { addr: 3, name: ALARM_VOLUME_PCT, desc: "Alarm volume 0..100", unit: "%" }
|
|
- { addr: 8, name: DODGE_OFFSET_DEG_X100, desc: "Dodge mode heading offset, deg*100 (signed int16)", unit: "deg", scale: 0.01 }
|