Getting a Victron solar system into Home Assistant was easier than I expected

I have a small off-grid Victron solar system, just a fun science project. I wanted data like the current battery level in Home Assistant, so I could trigger alerts and turn things on when the battery level is high. I figured getting the data in would mean running something new, like an exporter polling the gear or something over Bluetooth. Venus OS already runs a Modbus/TCP server, though, and Home Assistant’s built-in modbus integration reads it with one config block.

The hardware is a BMV-712 shunt, SmartSolar MPPT chargers, and a Phoenix inverter, all reporting to Venus OS on a Raspberry Pi Zero 2 W. Venus OS is the same firmware that runs on Victron’s Cerbo, and it works fine on the Pi with a powered USB hub for the VE.Direct-to-USB cables.

Only one of the Pi’s two micro-USB ports carries data, the one next to the mini-HDMI, and the corner port is power-only. The Pi also has no Ethernet, and its one data port can’t both power it and run devices. A SMAYS “Micro USB OTG to Ethernet Adapter with Powered Hub” handles all of that. It plugs into the data port, back-powers the Pi, and splits the single USB controller into a 10/100 Ethernet jack and three USB-A ports. Its limit is three ports on a tight power budget, so mine runs three VE.Direct-to-USB cables plus Ethernet and drops the fourth device. I’m switching to a DuPPa USB Quadport VE.Direct instead of adding a second hub, since it puts all four isolated ports on one USB device.

Keep the Pi on that Ethernet link. Venus OS on the Pi won’t join a WiFi network with PMF (802.11w) enabled, even in optional mode, because it builds wpa_supplicant without PMF support. The handshake never completes, and the access point logs a password mismatch even though the password is right. Turning PMF off on the access point fixes it, but Ethernet avoids the problem.

Turn on Modbus/TCP in Venus OS under Settings > Services > Modbus/TCP. None of this needs internet, so the Pi can stay offline if you don’t mind losing VRM, Victron’s cloud dashboard. Mine has no internet access, and Home Assistant reads it over the LAN.

Every unit id and register in my configuration.yaml comes from Victron’s attributes.csv:

modbus:
  - name: victron
    type: tcp
    host: 192.0.2.21     # Venus OS on the Pi, isolated IoT network
    port: 502
    sensors:
      - name: "Battery SOC"
        slave: 100         # system service, follows the battery monitor selected in Venus OS
        address: 843       # /Dc/Battery/Soc
        input_type: holding
        unit_of_measurement: "%"
        device_class: battery
        state_class: measurement
        precision: 0
        scan_interval: 30
      - name: "Battery Voltage"
        slave: 100
        address: 840       # /Dc/Battery/Voltage
        input_type: holding
        unit_of_measurement: "V"
        device_class: voltage
        state_class: measurement
        scale: 0.1
        precision: 1
        scan_interval: 30
      - name: "Battery Power"
        slave: 100
        address: 842       # /Dc/Battery/Power, signed
        input_type: holding
        data_type: int16
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        scan_interval: 30
      - name: "Solar PV Power"
        slave: 100
        address: 850       # /Dc/Pv/Power
        input_type: holding
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        scan_interval: 30
      - name: "MPPT State"
        slave: 238         # solarcharger, one per MPPT
        address: 775       # /State: 3=bulk, 4=absorption, 5=float
        input_type: holding
        scan_interval: 30

Here are the Home Assistant automations that go with this:

# automations.yaml
- alias: Victron Battery Low
  trigger:
    - platform: numeric_state
      entity_id: sensor.battery_soc
      below: 30            # fires once on the downward crossing, not every poll
  action:
    - service: notify.mobile_app_phone
      data:
        title: "Battery low"
        message: "Solar battery at {{ states('sensor.battery_soc') }}%"

- alias: Victron Battery Critical
  trigger:
    - platform: numeric_state
      entity_id: sensor.battery_soc
      below: 20
  action:
    - service: notify.mobile_app_phone
      data:
        title: "Battery critical"
        message: "Solar battery at {{ states('sensor.battery_soc') }}%"
        data:
          channel: alarm_stream_max   # bypass Do Not Disturb (Android)
          ttl: 0
          priority: high

The HACS integration hass-victron auto-discovers every register as an entity. It also pins a newer pymodbus that disables Home Assistant’s built-in modbus, and that’s more to maintain than five sensors need. Victron’s first-party Bluetooth integration reads a shunt’s Instant Readout advertisements straight into Home Assistant, and it’s the better choice for a standalone shunt with no Venus OS device. Modbus added the least here, since the Pi already runs Venus OS.