Skip to content

Add MQTTCAN4VESC: CAN to MQTT telemetry bridge for VESC Express - #98

Open
gowrav wants to merge 1 commit into
vedderb:mainfrom
gowrav:add-mqttcan4vesc
Open

Add MQTTCAN4VESC: CAN to MQTT telemetry bridge for VESC Express#98
gowrav wants to merge 1 commit into
vedderb:mainfrom
gowrav:add-mqttcan4vesc

Conversation

@gowrav

@gowrav gowrav commented Jul 10, 2026

Copy link
Copy Markdown

Adds a new application package, mqttcan4vesc.

What it does

Runs on a VESC Express and publishes the motor controller's CAN status messages (statuses 1–6) to an MQTT broker over WiFi — either as one JSON document on <prefix>telemetry, or as one topic per signal. It is read-only with respect to the vehicle: it decodes CAN traffic and never commands the controller.

Everything is configured from the package's QML panel in VESC Tool over BLE or USB — broker host/port/credentials, topic prefix, publish mode and interval, MQTT keepalive, CAN mode, controller id, and the two motor constants. Settings persist in EEPROM and survive reboots and package updates.

WiFi is deliberately not configured in the panel. The Express firmware owns the station credentials and its own auto-reconnect; configuring it in two places lets the two disagree. The script only reads (wifi-status), and never calls wifi-connect (which would override the stored station config at runtime) or wifi-disconnect (which sets wifi_reconnect_disabled, killing auto-reconnect until reboot).

Published data

Nineteen fields go out every interval regardless of which status messages the controller has enabled, so the payload shape is stable — fields that are never decoded carry 0:

erpm, current_motor, motor_speed, motor_torque, duty, amp_hours, amp_hours_charged, watt_hours, watt_hours_charged, temp_fet, temp_motor, current_in, pid_pos, tachometer, v_in, adc1, adc2, adc3, ppm

Both the raw signals (erpm, current_motor) and the values derived from them are sent, so a wrong pole-pair count or torque constant can be corrected downstream instead of re-flashing vehicles.

CAN modes

  • vesc (default) — decodes statuses 1–6 from event-can-eid while leaving the VESC protocol enabled, so VESC Tool still reaches the controller over CAN. Falls back to canget-* polling if no status frames arrive.
  • raw — same decode with the VESC protocol disabled.
  • sim — generates simulated telemetry with no controller attached, for bench-testing the MQTT chain.

Panel

Live status (WiFi/MQTT state, a CAN-frame counter, speed, torque, voltage, temperatures), plus where the running config came from (EEPROM vs defaults) and the result of the last save — so a silent storage failure cannot look like success. Buttons: Save & Apply, Test Connection, Reload, Restore Defaults, Reboot, and Detect for the controller id.

Commands are paced rather than burst, because lbm_event() silently drops custom app data when the handler mailbox is fuller than the event queue, and that mailbox also carries the event-can-eid stream. The panel re-sends SAVE if it goes unacknowledged.

Notes

  • Requires VESC Express firmware 6.05+.
  • MQTT is plain TCP — LispBM has no TLS — so a private broker on a trusted network is recommended. CONNECT username/password auth is supported.
  • isCompatible accepts hardware names containing express or exp (custom Express variants report VESC Exp <name>).
  • Registered in the root Makefile and in res_all.qrc.

Testing

Built with vesc_tool --buildPkgFromDesc and exercised on a VESC Express (XIAO ESP32-C3) against both a real controller and a simulated bus: telemetry verified end to end at broker.emqx.io, and settings verified to persist across power cycles.

🤖 Generated with Claude Code

Runs on a VESC Express and publishes the motor controller's CAN status
messages (statuses 1-6) to an MQTT broker over WiFi, as one JSON document or
as one topic per signal. Read-only with respect to the vehicle: it decodes
CAN traffic and never commands the controller.

Configuration is done from the package's QML panel in VESC Tool over BLE or
USB -- broker host/port/credentials, topic prefix, publish mode and interval,
MQTT keepalive, CAN mode, controller id, and the two motor constants. Settings
persist in EEPROM and survive reboots and package updates. WiFi is deliberately
NOT configured here: the Express firmware owns the station credentials and its
auto-reconnect, and configuring it in two places lets the two disagree.

Nineteen fields are published every interval regardless of which status
messages the controller has enabled, so the payload shape is stable; undecoded
fields carry 0. Both the raw signals (erpm, current_motor) and the values
derived from them (motor_speed, motor_torque) are sent, so a wrong pole-pair
count or torque constant can be corrected downstream rather than by
re-flashing vehicles.

Three CAN modes: "vesc" (default) decodes the status frames while leaving the
VESC protocol enabled, so VESC Tool still reaches the controller over CAN, and
falls back to canget-* polling if no status frames arrive; "raw" decodes with
the protocol disabled; "sim" generates simulated telemetry for bench-testing
the MQTT chain with no controller attached.

The panel shows live status -- WiFi and MQTT state, a CAN-frame counter, speed,
torque, voltage, temperatures -- plus where the running config came from
(EEPROM or defaults) and the result of the last save, so a silent storage
failure cannot look like success.

Requires VESC Express firmware 6.05+. MQTT is plain TCP; LispBM has no TLS, so
a private broker on a trusted network is recommended.

Tested on a VESC Express (XIAO ESP32-C3) against a real controller and a
simulated bus: telemetry verified end to end at broker.emqx.io, and settings
verified to persist across power cycles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vedderb

vedderb commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Nice work!

I think you want the isCompatible-function to look something like this instead as "custom module" pretty much always is an ESP32 and there are many ESP32s that don't have express in their name.

function isCompatible(fwRxParams) {
    var hwType = fwRxParams.hwTypeStr().toLowerCase()

    if (hwType != "custom module") {
        return false
    }

    return true
}

@techfoundrynz

Copy link
Copy Markdown
Contributor

@gowrav this is awesome! Once you get this merged, I'd love to start using it in a couple of other packages I work on. I might have a follow-up PR to split the core functionality out into a library if you're open to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants