Skip to content

About

A Go service providing a unified interface for electric scooter ECUs via CAN bus, managing real-time vehicle data and state in Redis.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

151 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Librescoot ECU Service

Part of the Librescoot open-source platform.

ecu-service bridges a supported Bosch motor controller from SocketCAN to the Librescoot Redis IPC surface. It is intended to run on the vehicle, alongside Redis and the CAN interface connected to the controller. On the unu Scooter Pro, the Bosch ECU is a licensed, white-labeled Lingbo LBMC controller with CAN support.

Capabilities

  • Decodes ECU status, configuration, gear, and diagnostic frames.
  • Publishes motor telemetry, controller-reported configuration, and fault state.
  • Applies boost and regenerative-braking (KERS) settings from Redis.
  • Gates KERS on vehicle readiness, vehicle speed, and the temperature/state of active batteries.
  • Reconnects its CAN socket after it becomes stale, such as across suspend and resume.

Operation and Redis interface

The service reads the Bosch ECU from the configured CAN interface and maintains the engine-ecu hash. Its fields include motor voltage/current, speed, RPM, power, energy counters, temperature, odometer, brake and throttle states, controller KERS/boost state, gear information, firmware details, and decoded configuration when the ECU reports it.

The energy:consumed and energy:recovered fields are cumulative integer mWh totals integrated from motor voltage and current. At process startup the service restores existing totals asynchronously from Redis; a missing field starts at zero. While reads fail or either value is invalid, the service logs and retries, continues processing CAN frames, and defers both energy fields without overwriting retained values. Energy integrated while waiting is added once to the restored totals. Sub-mWh remainders and energy while the service is stopped are not retained. The existing Redis connection health monitor restarts the service on a detected disconnect. Continuity across a Redis restart or power loss depends on Redis persistence; the service does not checkpoint energy to disk.

The engine-ecu pub/sub channel carries field names for selected events, including throttle, odometer, kers, kers-reason-off, regen-available, and fault. Consumers should read the hash after a notification rather than treating the notification as a value payload.

Fault presence is stored in engine-ecu:fault; changes are also appended to events:faults with group engine-ecu. The service watches vehicle.state, battery:0, and battery:1 to determine KERS eligibility. Battery state is considered active only when its state field is active.

Fresh motion and asynchronous CAN I/O

The additive engine-ecu:motion channel publishes complete direct-value JSON observations on every motion frame, including unchanged values. See the versioned contract and isolated integration test. The existing hash/change-notification interface remains unchanged.

One timestamp-aware CAN receive pump feeds a 64-entry ordered dispatcher queue. Legacy Redis publication cannot block physical reception; an overflow discards queued work and invalidates motion continuity. Receive timestamps, rather than callback time, establish controller liveness. Expired buffered traffic cannot refresh it.

One asynchronous TX worker shares that same socket. Control code submits bounded batches without performing socket writes under the ECU state mutex or in a receive callback. Batches expire after 100 ms; power/connection generation and current transmit permission are checked before writes. Power-off/reconnect invalidate pending commands. Queue-full and write failures leave assertions eligible for reconciliation; startup gear sequences retry after incomplete transmission. Sent flags update only after successful batch completion. A completed socket write is distinct from the controller-reported KERS/boost/configuration values. Other services' CAN access is unchanged.

Ride-start status requests

While vehicle[state] is parked, drive-start-status values waiting-ecu, release-throttle, and stop-vehicle request full ECU status snapshots at most once per second. This runs after the eight-second power-on grace, requires both power rails, and pauses during a communication fault. A recent watchdog probe also counts toward the request cooldown. No ride-start probes run in ready-to-drive or after a start timeout.

These requests do not establish freshness by themselves. Vehicle-service waits for actual engine-ecu:motion reports before allowing drive entry. Other CAN status traffic can keep communication healthy without providing a current throttle sample, so the start requests are independent of silence detection.

Timed boost telemetry

Compatible TUNU native guards expose accepted/active state, pack eligibility, remaining allowance, cooldown and inhibition reasons separately from the legacy boost acknowledgement. The engine-ecu:boost JSON key/channel has a 500 ms observation-based expiry. See the versioned contract. Stock control IDs and the existing engine-ecu hash fields remain unchanged.

Controller power-on behavior

The service does not immediately assert KERS and boost state when ECU power is commanded on. It waits for the controller's initial boot period, then retries that assertion from the watchdog until an incoming CAN frame confirms the controller is listening. This prevents unacknowledged control frames from being retransmitted while the controller is still booting. State changes made while ECU power is off are not queued; the current commanded state is asserted when the controller becomes reachable.

Configuration

Runtime configuration is provided by command-line flags. Run bin/ecu-service -help after building for the authoritative flag list. The main deployment choices are the Redis address and port, the CAN interface (default can0), log level, and optional Bosch gear ratios.

The following fields in the Redis settings hash are watched at startup and on change:

  • engine-ecu.boost
  • engine-ecu.kers
  • engine-ecu.kers-power
  • engine-ecu.kers-power-dual
  • engine-ecu.kers-voltage

KERS treats disabled (and the legacy value false) as off; an unset value is enabled. Current and voltage values are passed to the controller, so restrict write access to these settings to trusted vehicle-management components.

Build and test

The Makefile uses Go 1.25.7 for its build and test targets.

make build        # Linux ARMv7 binary: bin/ecu-service
make build-host   # local-development binary: bin/ecu-service
make test
make lint         # requires golangci-lint

Deployment and operations

The Yocto layer ships librescoot-ecu.service, which requires Valkey and starts after the vehicle and battery services. The runtime requires a reachable Redis-compatible datastore and permission to open the configured SocketCAN interface.

The service handles SIGINT and SIGTERM. Loss of ECU communication while the controller is powered is reported as the synthetic E20 fault; investigate the CAN path and controller power state before clearing or acting on that report. After eight seconds of power-on grace, one second of silence starts a liveness check regardless of cached speed. The watchdog sends at most two status requests per uninterrupted outage, at least three seconds apart. E20 requires more than three seconds of silence and 1.5 seconds without a reply after the second request (normally about 5.5 seconds total). Failed sends count as attempts, not replies. A known unavailable receive socket or a recent CAN bus-off/stopped/sleeping sample raises E20 directly after startup grace, with the transport cause logged separately.

An isolated frame does not clear E20: recovery requires traffic spanning one second without a receive gap exceeding one second, or a reply to a separate, rate-limited recovery probe. No requests are sent while unpowered or during startup grace. Power-off ends monitoring rather than establishing recovery. Configuration replies prove communication only; they do not refresh speed or other telemetry fields.

E20 invalidates cached ECU faults and pending diagnostic transitions. After communication recovers, ECU faults require a fresh Status2 report and normal debouncing; unrelated CAN traffic cannot restore a cached fault.

License

This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Made with ❤️ by the Librescoot community

About

A Go service providing a unified interface for electric scooter ECUs via CAN bus, managing real-time vehicle data and state in Redis.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages