firmware
OpenSkyhawk firmware — STM32 (PanelBridge, PanelGroup) and RP2040 (SimGateway). Use when writing or changing firmware, PlatformIO projects, CAN protocol, NODE_ID assignment, DCS-BIOS integration, HID, panel sketches, shared libraries or their test sketches, or interacting with MCP23017/ADS1115 throu
Install
mkdir -p .claude/skills/firmware && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16694" && unzip -o skill.zip -d .claude/skills/firmware && rm skill.zipInstalls to .claude/skills/firmware
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
OpenSkyhawk firmware — STM32 (PanelBridge, PanelGroup) and RP2040 (SimGateway). Use when writing or changing firmware, PlatformIO projects, CAN protocol, NODE_ID assignment, DCS-BIOS integration, HID, panel sketches, shared libraries or their test sketches, or interacting with MCP23017/ADS1115 through the firmware classes (PanelGroup input/output classes). PanelGroup work is the most common interaction.About this skill
Firmware
Three-tier architecture:
PC ──USB HID + USB CDC(250k)── RP2040 SimGateway ──UART 250k── STM32 PanelBridge (NODE 0, CAN master)
└─CAN 500k─ PanelGroup nodes (NODE 1–63)
Authoritative spec — route here, don't bulk-load
Firmware/ScratchPad/FirmwarePlan/ is the authoritative firmware specification (the what:
contracts, data flows, decisions) and supersedes any firmware content in docs/_source/.
Firmware/ScratchPad/TechSpec/ holds the how (public API, class structure, method signatures).
Open the one relevant doc via the maps below rather than reading everything.
FirmwarePlan document map (read FirmwarePlan/README.md for the full index):
00-decisions.md— why each choice was made01-system-overview.md— topology, library roles, data-flow narrative02-can-protocol.md— CAN frame IDs,NODE_ID,ControlPacketwire format, TX queue03-uart-usb-hid-protocol.md— UART mux, HID frame format, USB identity04-dcs-bios-integration.md—controlIdspace,DCSIN_*, input-map generator rules05-panelgroup-api.md—PinRef, MCP23017 management, all input & output classes06-panelbridge-api.md— node tracking, export listener, input dispatch, SYNC_REQ07-simgateway-api.md—HIDAxis/HIDButton, relay contract, HID dispatch08-hardware-firmware-contracts.md— STM32 pin assignments, DRV8833 ~SLEEP, ADS111509-startup-resync-diagnostics.md— boot sequences, SYNC_REQ/READY, DIAG frames10-implementation-plan.md/11-open-issues.md— phased plan / deferred items
TechSpec: one file per library/class (CANProtocol, STM32Board, PanelBridge,
SimGateway, HIDControls, A4ECGenerator, and PanelGroup/{PinRef, PanelGroup, Inputs/*, Outputs/*}). Each cites its FirmwarePlan section. See TechSpec/README.md.
Availability guard: these specs are committed but may be absent in a partial checkout —
if Firmware/ScratchPad/FirmwarePlan/ is missing, rely on the conventions below plus the
committed code under Firmware/Libraries/.
Durable conventions (always apply)
- MCU variant:
STM32F103C8(64 KB) is the default for CAN avionics nodes; useSTM32F103CB(128 KB) only where flash demands it (PanelBridge runs the full DCS-BIOS input map). Use full part numbers in prose, never theCBT6ordering code orC8/CBshorthand. - NODE_ID: compile-time only —
build_flags = -DNODE_ID=Ninplatformio.ini, never a#defineinmain.cpp. 0 = PanelBridge; 1–63 = PanelGroup nodes. Claim the next free number inFirmware/NODE_IDS.mdin the same PR that starts the work; never reuse one.tools/check_node_ids.pyenforces this in CI. - Expander access is via classes, not registers: the
PanelGrouplibrary owns MCP23017 integration; ADS1115 viaADS1115.h. Instantiate input classes (Switch2Pos,Switch3Pos,RotarySwitch,SwitchMultiPos,AnalogInput,RotaryEncoder, …) and output classes (LED,IntegerOutput,SwitecX25Output, …) on aPinRef. The electrical wiring of those parts belongs to thepcb-designskill. - Timing: each class calls
millis()directly and keeps its ownuint32_ttimestamp — no shared clock. - Docblocks: Doxygen style on every public class/method/enum/non-obvious constant
(
@brief,@paramwith units,@return,@notefor ISR/timing constraints). Private members use plain//. - Library + test layout: each library is
Firmware/Libraries/<Name>/with alibrary.json; its tests are a standalone projectFirmware/Tests/<Name>/usingsrc_dir = tests, an[env_base]section, and onebuild_src_filterper scenario. SeeTechSpec/README.md. - Implementation order (dependencies flow down): HIDControls → A4EC → CANProtocol → STM32Board → PanelBridge / SimGateway → PanelGroup (PinRef → PanelGroup → Inputs/Outputs).
- Header ownership — keep shared headers single-purpose.
HIDControls.his HID controlId constants only;CANProtocol.howns CAN frame transport (incl.NodeHealthPayload). The cross-node status contract —NODE_STATUS_*(DCS-BIOS reporting),NodeHealthFlag,NodeFaultCode, andFaultSource— lives in the neutralNodeStatuslibrary, because every node type shares it (PanelGroup, PanelBridge, PDU) and it's broader than "health." Never shape a cross-node contract around one flavor (PanelGroup.h/OutputBase) or the HID namespace (D14). - Fault sources feed a node aggregator; no producer owns node health. A fault-producing object
(
DrumDisplay, a PDU rail monitor, a PanelBridge host-link watchdog) inheritsOpenSkyhawk::FaultSource(NodeStatus.h), self-registers, and reports cachedfaultCode()(aNodeFaultCode) + localfaultDetail()(DiagSerial-only string, never on the wire). The node-level aggregator walksFaultSource::head()→ setsHEALTH_n.faultId+ DEGRADED.OutputBasegets no fault API — an output is just one possible fault source, not special. - Includes: direct for what you use, but don't couple upward. Prefer a direct
#includeof a symbol's defining header over relying on a fragile transitive path — but if adding that include would drag a low-level/UI class into a heavier or conceptually-wrong layer, that's a signal the symbol is placed wrong (see the two rules above), not that you should hide it behind a transitive include. Don't add an include already reliably provided by a header the file must include anyway. - Spec-sync in the same PR. Any change to a public firmware API — struct/enum, class method,
wire format,
#definecontract — updates the authoritativeFirmwarePlan/+TechSpec/doc in the same PR, listed as a deliverable and self-checked before push. The spec is source of truth; drift defeats it. Grep the old value/claim before pushing. - Toolchain: PlatformIO. STM32 =
platform = ststm32,framework = arduino. RP2040 =earlephilhowercore with-DUSE_TINYUSB. - C++ standard: any project using
DrumDisplayneeds-std=gnu++20+build_unflags = -std=gnu++17— itsDrumReadoutdescriptors are C++20 designated initializers. Already baked into thePanelGroupscaffold template, so B6 sketches get it by default; add it explicitly to a standalone test project. The whole PanelGroup dep stack (U8g2 / MCP23017 / ADS1X15) is gnu++20-clean.
Verify
Build the affected project(s) with platformio run -d <project>; exercise each class in
isolation via its Firmware/Tests/<Name>/ sketch before integration.