Skip to content

Reference ​

Everything the daemon exposes, in tables.

Command line ​

muxen-energy [OPTION]
ShortLongArgumentDefaultMeaning
-h--help——print the usage block on stdout and exit 0
-v--verbose—offmore output; repeatable
-V--version——print the version and exit 0
-i--interfacecanXnoneCAN interface to bind to
-m--mqtt—offtake device data from MQTT instead of CAN
-e--energypathnonethe configuration file. Required
-f--filter-periodseconds30window of the battery-current filter
—--json-pretty—offindent the published JSON, for debugging; compact otherwise

Rules enforced at startup:

  • --energy is mandatory. Without it: config: configuration file is missing, usage on stderr, exit 1.
  • Either --interface or --mqtt is mandatory. Without either: config: select a CAN interface, or the MQTT source, exit 1.
  • --filter-period is parsed with strtol and must consume the whole argument; a bad value gives config: invalid filter period '<value>' and exit 1. A valid value outside 20…60 is clamped, with config: filter period set to <n> seconds on stdout.
  • --interface is truncated to the platform interface-name length.

Verbosity levels:

LevelEffect
noneconfiguration summary, zone list, dispatcher tables, connection state
-vadds the zone tree: variables and devices per zone, with function names and converter side
-vvadds a dump of every CAN frame and every MQTT message received

--version is accepted but is not listed in the usage block, and is not offered by the bash completion.

Exit codes ​

CodeMeaning
0normal termination (SIGINT, SIGTERM), or --help / --version
1command-line error
255startup failure after the command line was accepted: configuration file unreadable or not a JSON object, dispatcher construction failed, MQTT connection failed, CAN interface not found

The daemon exits on SIGINT and SIGTERM by leaving its main loop cleanly; it closes the CAN socket and disconnects from the broker.

Environment ​

Read by the systemd unit, not by the binary:

VariableDefault in the unitUsed as
MUXEN_DEPLOY/etc/muxen/deploy.json--energy

MUXEN_DEPLOY is overridable with a drop-in. The CAN interface is literal in ExecStart and in the unit's device dependency — see Getting started.

Files ​

PathContent
/usr/bin/muxen-energythe daemon
/usr/lib/systemd/system/muxen-energy.servicethe systemd unit
/usr/share/bash-completion/completions/muxen-energybash completion
/etc/muxen/deploy.jsonthe boat configuration, read once at startup. Not shipped by this package

The daemon creates no files, no sockets and no runtime directory.

systemd ​

Unit muxen-energy.service:

DirectiveValue
PartOfmuxen-deploy.target
WantedBymuxen-deploy.target
Aftermosquitto.service
User / Groupmuxen
Restartalways, RestartSec=30
ConfigurationDirectorymuxen, mode 0755

Hardening: PrivateTmp, PrivateDevices, ProtectSystem=strict, ProtectKernelModules, ProtectKernelTunables, ProtectControlGroups, NoNewPrivileges; CAP_SYS_ADMIN, CAP_SYS_TIME and CAP_NET_ADMIN dropped from the bounding set; the @clock, @debug, @module, @mount, @raw-io, @reboot, @swap, @privileged and @resources system-call sets denied with EPERM.

Restarting muxen-deploy.target restarts the daemon. That is the mechanism by which a change to the deployment file is picked up.

Packaging ​

FieldValue
Packagemuxen-energy
Architectureany, Multi-Arch: foreign
Dependsmuxen-systemd (>= 1.2.0), muxen-sensors (>= 5.0.0)
Recommendsmuxen-boat (>= 5.0.0)
Triggeractivates muxen-restart-target

The muxen-restart-target trigger is what makes dependent services restart when the package is upgraded.

MQTT ​

Connection ​

ParameterValue
Host127.0.0.1
Port1883
Client IDmuxen-energy
Keepalive5 s
Clean sessionyes
Authenticationnone
Subscription# — every topic, filtered by regular expression in the daemon

The connection is made at startup and the daemon exits if it fails. Once established, a lost connection is retried every second.

Published ​

TopicQoSRetainPeriod
app/energy/<zone>0yes1 s per zone
app/energy/info1yeson each connection; online: false as last will and on clean exit

<zone> is the integer zone number. Payload:

json
{
  "id": 1,
  "name": "48V BD",
  "data": { },
  "metadata": { "rxdate": "", "rxTimestamp": 0, "expireAfterSec": 5 }
}
data keyUnitMeaning
capacityAhsum of the configured battery capacities in the zone
voltageVmaximum battery voltage in the zone
temperature°Cmaximum battery temperature in the zone
soc%maximum battery state of charge in the zone
socValidboolfalse if any battery reports 255, or if no battery reported
powerUsedW(charge - battery) * voltage, floored at 0
autonomyhtime to empty, from the filtered battery current. 0 when charging
chargingTimehtime to full, from the filtered battery current. 0 when discharging
chargeAtotal current in, residual included
dischargeAtotal current out, residual included
chargeBatteryAbattery current when positive
dischargeBatteryAmagnitude of the battery current when negative
chargeGroupAgenerator sets
chargeMotorAmotor current when positive
dischargeMotorAmagnitude of the motor current when negative
chargeSolarAsolar
chargeWindTurbineAwind turbines
chargeHydroAhydrogenerators
chargeConverterAconverters charging the zone
dischargeConverterAconverters drawing from the zone
chargeOtherAunexplained generation
dischargeOtherAunexplained consumption

charge - discharge == chargeBattery - dischargeBattery holds on every payload. The JSON is compact, on one line; --json-pretty indents it for reading by hand.

app/energy/info ​

The daemon identity, the same shape every MUXEN daemon publishes on app/<daemon>/info. Published retained at QoS 1 on every (re)connection with online: true; the same payload with online: false is the last will and is also published on a clean exit, so a subscriber always gets the daemon's last known state.

json
{
  "name": "muxen-energy",
  "version": "v6.1.0",
  "hostname": "brain-3",
  "features": ["energy-zones", "source-can"],
  "online": true,
  "metadata": { "rxdate": "2026-09-22T07:55:22.552Z", "rxTimestamp": 1790063722, "expireAfterSec": 3124137600 }
}

version is the git describe of the build and is for display only; clients test features and ignore the ones they do not know. expireAfterSec is about 99 years: the info never goes stale, online carries liveness.

FeatureMeaning
energy-zonesapp/energy/<zone> is published
source-candevice data is read from the CAN interface (--interface)
source-mqttdevice data is read from the device/… MQTT topics (--mqtt)

Subscribed ​

Matched by anchored regular expression against every topic on the broker.

Topic patternWhenRead
app/sensor/<name>alwaysdata.value
device/1/<instance>/life--mqttBloc 8 total current
device/3/<instance>/life--mqttinterconnection output current
device/4/<instance>/state--mqttpower source genset current
device/5/<instance>/life--mqttbattery voltage, current, state of charge, temperature
device/6/<instance>/life--mqttconverter output current
device/6/<instance>/state--mqttconverter input current
device/7/<instance>/life--mqttmotor current
device/10/<instance>/life--mqttsolar current
device/11/<instance>/life--mqttwind turbine current

<name> is the sensor's name from the configuration; <instance> is the device instance as written in the configuration, zero-based.

Receive time is taken from metadata.rxdate in the payload when present, and from the local clock otherwise.

app/sensor/<name> payloads are read as:

json
{ "data": { "value": 12.5 }, "metadata": { "rxdate": "…" } }

Only integer and floating-point value fields are accepted.

CAN ​

Used when --mqtt is not given. The daemon binds a raw AF_CAN socket to the interface, subscribes to one broadcast frame per configured device, and never transmits.

FunctionFrameBroadcast IDDLCField consumed
1 Bloc 8life15currentTotal
3 Interconnectionlife18outputCurrent
4 Power source gensetstate38current
5 Batterylife18voltage, current, soc, temperature
6 Converterlife17current (output side)
6 Converterstate37current (input side)
7 Motorlife18current
10 Solarlife16current
11 Wind turbinelife18current

The matched CAN identifier is composed as

canId = (broadcastId << 12) | (function << 6) | instance

with the mask 0xFFFFFF, so a battery at instance 0 is matched at 0x001140. The dispatcher table printed at startup lists the exact identifier of every subscription.

There is no hydro (function 2) CAN or MQTT device handler; a hydrogenerator is counted only as a variable.

Configuration keys ​

Read from the file given to --energy. Everything not listed here is ignored.

LocationKeyTypeMeaning
settings[]EnergyZoneName<N>object with value stringdisplay name of zone N. Default Zone <N>
devices[]functionintMUXEN function code. Required
devices[]instanceintdevice instance, zero-based. Required
devices[].parameters[]BindToParcstringzone number. Default 1
devices[].parameters[]InputBindToParcstringfor converters: zone of the input side. Only honoured when > 0
devices[].parameters[]Capacitynumber or stringbattery capacity in Ah. Default 0
sensors[]namestringthe variable name, and the MQTT topic suffix. Required
sensors[].energyenableboolmust be true
sensors[].energytypeintMUXEN function code the value represents
sensors[].energyzoneintzone number, must be ≥ 0

Constants ​

ConstantValueMeaning
publication period1 sone payload per zone per tick
staleness window5 sa device or variable older than this is dropped from the sum
expireAfterSec5published in the payload metadata
filter period20…60 s, default 30number of battery-current samples used by the estimator
filter trim5 samples each enddropped before weighting
unavailable state of charge255makes socValid false
fallback zone1for a device with no usable BindToParc

Building from source ​

sh
meson setup build
meson compile -C build
meson test -C build

The makefile wraps the common cases: make deb builds the Debian package, make dev a sanitiser build with the tests, make lint and make lint-check run clang-format.

Dependencies: GLib, libmosquitto, json-c, and the MUXEN libcanmqtt, libstdmuxen and libmuxenfile, which are resolved as installed packages if present and as Meson subprojects otherwise.

Integration of multiplexed solutions
MUXEN and the MUXEN logo are trademarks of MUXEN SAS.