Skip to content

Getting started ​

Prerequisites ​

On the Brain:

  • An MQTT broker on 127.0.0.1:1883. The daemon connects to it at startup and exits if the connection fails — it does not wait for a broker that is not there yet. The unit orders itself After=mosquitto.service and restarts every 30 s, so a broker that comes up late is tolerated, but a broker that never comes up leaves the daemon in a restart loop.
  • can0 up, unless you run the daemon in --mqtt mode. The daemon binds a raw CAN socket to the interface named by --interface and fails to start if that interface does not exist.
  • A boat configuration file, by default /etc/muxen/deploy.json. This package does not install one; it is the boat's deployment file, shared with the other MUXEN daemons. Without it the daemon exits at startup.
  • mosquitto-clients if you want to watch the output by hand.

Nothing else. The daemon holds no state of its own, writes no files, and opens no sockets other than the CAN socket and the MQTT connection.

Install ​

sh
sudo apt install muxen-energy

The package carries the muxen-energy daemon, its systemd unit and its bash completion. It depends on muxen-systemd, which provides the muxen.target / muxen-deploy.target pair the unit hangs off, and on muxen-sensors (>= 5.0.0), whose topic prefix it reads. muxen-boat is only recommended: a boat can be entirely CAN-fed and needs no daemon translating CAN frames onto device/….

Start it ​

The unit is WantedBy=muxen-deploy.target, so it comes up with the rest of the MUXEN stack and needs no enabling by hand after install:

sh
systemctl status muxen-energy

The unit runs:

/usr/bin/muxen-energy --interface can0 --energy ${MUXEN_DEPLOY}

with MUXEN_DEPLOY=/etc/muxen/deploy.json as its built-in default, overridable with a drop-in (systemctl edit muxen-energy).

The interface is not an environment variable any more. It appears literally in ExecStart and in the unit's BindsTo=/WantedBy= device dependency, which systemd resolves before any environment exists. Changing it means a drop-in that updates both:

ini
[Unit]
BindsTo=
BindsTo=sys-subsystem-net-devices-can1.device
After=sys-subsystem-net-devices-can1.device

[Service]
ExecStart=
ExecStart=/usr/bin/muxen-energy --interface can1 --energy ${MUXEN_DEPLOY}

The empty BindsTo= and ExecStart= are required: without them systemd appends rather than replaces.

The unit is also PartOf=muxen-deploy.target, so systemctl restart muxen-deploy.target restarts the daemon along with every other daemon that reads the deployment file. The muxen-systemd package watches /etc/muxen/deploy.json and restarts that target when the file changes — which is how a configuration change reaches the daemon. The configuration is read once, at startup, and never re-read.

Verify it is running ​

The daemon logs its whole startup to the journal, and that log is the main commissioning tool. Four things are worth reading, in order:

sh
journalctl -u muxen-energy -n 60

1. The configuration it actually used.

config: verbose = 0
config: sensors = /etc/muxen/deploy.json
config: data source = can
config: can interface = can0
config: filter period = 30

2. The zones it built. One line per zone, with the name it resolved:

zones: 4 zones
zones: 48V BD
zones: 48V TD
zones: 24V
zones: AC

A zone shown as Zone 3 rather than a real name means no EnergyZoneName3 setting was found — see Energy zones.

3. What it subscribed to. The dispatcher tables are printed unconditionally, one row per subscription:

can: dispatcher table size: 20
can: canId   	canMask 	canDlc  	name
can: 0x001140	0xFFFFFF	8       	battery
can: 0x001141	0xFFFFFF	8       	battery
...
mqtt: dispatcher table size: 0

The MQTT table holds one regex per configured sensor variable — none, on the boat above. In --mqtt mode the two tables swap: the devices move into the MQTT table as one regex each, and the CAN table is empty.

A table size of 0 on the side you expect means no device was matched — usually because nothing in the configuration carries a BindToParc, or because every device in it is of a type this daemon does not consume.

4. The broker connection.

mqtt: connected

See the output ​

sh
mosquitto_sub -h 127.0.0.1 -t 'app/energy/#' -v

One retained message per zone, republished every second. The daemon sends it compact, on one line; it is shown indented here, as --json-pretty would publish it:

json
{
  "id": 1,
  "name": "48V BD",
  "data": {
    "capacity": 280.0,
    "voltage": 54.2,
    "temperature": 41.0,
    "soc": 80.0,
    "socValid": true,
    "powerUsed": 6937.6,
    "autonomy": 2.24,
    "chargingTime": 0.0,
    "charge": 28.0,
    "discharge": 128.0,
    "chargeBattery": 0.0,
    "dischargeBattery": 100.0,
    "chargeGroup": 0.0,
    "chargeMotor": 0.0,
    "dischargeMotor": 80.1,
    "chargeSolar": 28.0,
    "chargeWindTurbine": 0.0,
    "chargeHydro": 0.0,
    "chargeConverter": 0.0,
    "dischargeConverter": 20.0,
    "chargeOther": 0.0,
    "dischargeOther": 27.9
  },
  "metadata": {
    "rxdate": "2024-01-16T15:35:56.000Z",
    "rxTimestamp": 1705419356,
    "expireAfterSec": 5
  }
}

Currents are amperes, voltage volts, temperature degrees Celsius, soc percent, capacity amp-hours, powerUsed watts, autonomy and chargingTime hours. Every field is explained in The energy balance.

Messages are retained. The last payload of each zone stays on the broker after the daemon stops, so a screen showing plausible-looking numbers is not by itself proof that the daemon is alive. Compare metadata.rxdate against the clock, or watch the topic for a second.

The minimum configuration ​

The daemon reads three things out of the deployment file, and publishes nothing useful without them.

1. Bind each device to a zone. Every entry in devices[] whose parameters[] carry a BindToParc joins that zone; anything else falls into zone 1.

json
{
  "function": 5,
  "instance": 0,
  "parameters": [
    { "name": "Capacity",   "value": "280" },
    { "name": "BindToParc", "value": "1" }
  ]
}

2. Give each battery a Capacity, in amp-hours. The zone capacity is the sum over its batteries, and without it autonomy and chargingTime stay at zero — the arithmetic divides a capacity that is zero.

3. Name the zones. A setting called EnergyZoneName<N> supplies the name published for zone N:

json
{ "name": "EnergyZoneName1", "value": "48V BD" }

That is enough for a zone to publish. Converters that straddle two zones and currents that come from an analogue sensor need the rest of Energy zones.

Running it by hand ​

Useful while commissioning, on a Brain where the service is stopped:

sh
sudo systemctl stop muxen-energy
muxen-energy --interface can0 --energy /etc/muxen/deploy.json -v

-v adds the zone tree — every zone with its variables and devices, each with its resolved function name and, for converters, which side of the converter this zone is on. -vv additionally dumps every CAN frame and every MQTT message the daemon receives, which is a lot of output on a live bus.

Without a CAN bus at all, take the data from MQTT instead:

sh
muxen-energy --mqtt --energy /etc/muxen/deploy.json -v

In that mode the daemon opens no CAN socket and subscribes to the device/<function>/<instance>/… topics instead.

Where to go next ​

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