PC-side companion to the STM32 dynamometer firmware, which lives in this repo under
firmware/. It connects to the dynamometer's STM32 over USB-CDC,
decodes its telemetry/error streams, and can send framed commands back. The UI is Avalonia
(cross-platform .NET); the device logic lives in a UI-agnostic Dyno.Core library.
The USB wire protocol is not hand-maintained: the firmware defines it once as a YAML schema and the C# message types are generated from that same schema (see Regenerating message types), so the host and firmware can't drift.
| Path | What |
|---|---|
src/Dyno.Core/ |
Device layer: serial, protocol parser + frame builder, generated message types. See README. |
src/Dyno.App/ |
Avalonia desktop UI. See README. |
tests/Dyno.Core.Tests/ |
xUnit tests (struct sizes, CRC, error decode, parser). |
tools/message_gen/ |
YAML → C# codegen + drift guard. See README. |
firmware/ |
STM32 firmware — the wire-protocol source of truth. See README. |
| Tool | Purpose | Install (Fedora) | Install (Ubuntu/Debian) | Install (Windows) |
|---|---|---|---|---|
| .NET SDK 10 | Build / run / test | sudo dnf install dotnet-sdk-10.0 |
packages.microsoft.com | winget install Microsoft.DotNet.SDK.10 |
| Python 3 + venv | Only to regenerate message types | sudo dnf install python3 |
sudo apt install python3 python3-venv |
winget install Python.Python.3.13 |
On Linux, opening a serial port needs the user in the dialout group:
sudo usermod -aG dialout "$USER" (re-login afterwards).
On Windows no setup is needed: the board's USB-CDC interface enumerates as a
COMx port with the inbox usbser.sys driver.
The same commands work on Linux, macOS and Windows:
dotnet build Dyno.slnx # build everything (Debug)
dotnet test Dyno.slnx # run the unit tests
dotnet build Dyno.slnx -c Release # release build./Scripts/run.sh # Linux/macOS
Scripts\run.ps1 # Windows (PowerShell)The GUI needs a display (X11/Wayland on Linux) and a connected board to show live data. End users get native, per-RID self-contained builds:
dotnet publish src/Dyno.App/Dyno.App.csproj -c Release -r linux-x64 --self-contained
dotnet publish src/Dyno.App/Dyno.App.csproj -c Release -r win-x64 --self-containedThe committed src/Dyno.Core/Messages/Generated/Messages.cs is generated from the firmware's
messages_public.yaml. After changing the schema:
./Scripts/generate.sh # Linux/macOS
Scripts\generate.ps1 # Windows (PowerShell)CI fails if the committed file is out of sync (python tools/message_gen/check.py). Details:
tools/message_gen/README.md.
.github/workflows/build.yml:
- codegen — verifies
Messages.csmatches the schema (drift guard). - build & test —
dotnet build/teston Ubuntu and Windows, then publishes the app per RID (linux-x64,win-x64) as workflow artifacts.
.github/workflows/firmware.yml:
- generated-headers — verifies the firmware's committed headers match the schema.
- build — Docker-toolchain firmware builds (Debug and Release), uploaded as workflow artifacts.
- On connect the firmware announces itself and the host replies with a version-checked
USB_CMD_ACKhandshake; no telemetry streams until it completes, and aUSB_PROTOCOL_VERSIONmismatch refuses the link rather than mis-decoding the stream. - Sensor data streams only while the dyno is running a session. The device announces each session start/stop (and re-states it after every ack), so the app can show whether a session is on and hide the readouts when it is not — an idle board and a dead one otherwise look identical.
Dyno.Corehas no UI dependency, so it can be driven headlessly or from another front-end.- Target framework is
net10.0; the solution uses the.slnx(XML) solution format.