How to power the Lynx distributor and read the fuse states with esphome

This is how to power the Victron Lynx distributor and how to read the fuses states with an esp32 d2 mini (just a few euros via AliExpress) and esphome:

lynx.yaml

#https://www.wemos.cc/en/latest/s2/s2_mini.html

#rj10
# pin 1 yellow = VBUS
# pin 2 green  = SDA = GPIO33
# pin 3 red    = SCL = GPIO35
# pin 4 black  = GND

esphome:
  name: lynx
  includes:
    - lynx.h
  libraries:
    - Wire

esp32:
  board: lolin_s2_mini
  variant: ESP32S2
  framework:
    type: arduino

logger:
  hardware_uart: USB_CDC

api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

light:
  - platform: status_led
    name: "Status LED"
    id: lynx_status_led
    icon: "mdi:alarm-light"
    restore_mode: ALWAYS_OFF
    pin:
      number: GPIO15
      inverted: false

#https://esphome.io/components/i2c.html
i2c:
  - id: bus_a
    sda: GPIO33
    scl: GPIO35
    frequency: 50kHz
    scan: true

#https://esphome.io/components/binary_sensor/custom.html
binary_sensor:
  - platform: custom
    lambda: |-
      lynx_fuses = new LynxSensor(0x08);
      App.register_component(lynx_fuses);
      return {lynx_fuses->fuse1, lynx_fuses->fuse2, lynx_fuses->fuse3, lynx_fuses->fuse4};
    binary_sensors:
      - name: "lynx fuse 1 broken"
        icon: mdi:fuse-alert
        id: lynx_fuse_1_broken
      - name: "lynx fuse 2 broken"
        icon: mdi:fuse-alert
        id: lynx_fuse_2_broken
      - name: "lynx fuse 3 broken"
        icon: mdi:fuse-alert
        id: lynx_fuse_3_broken
      - name: "lynx fuse 4 broken"
        icon: mdi:fuse-alert
        id: lynx_fuse_4_broken

  - platform: gpio
    pin:
      number: GPIO0
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on_off: 100ms
    name: "lynx button 0"
    on_click:
    - logger.log: "Button 0 pressed"
    - lambda: |-
        lynx_fuses->update();
    - light.turn_on: lynx_status_led
    - delay: 100ms
    - light.turn_off: lynx_status_led

sensor:
  - platform: wifi_signal
    name: "lynx Wi-Fi Signal"
    update_interval: 120s

  - platform: internal_temperature
    name: "lynx tcore"
    update_interval: 300s

text_sensor:
  - platform: version
    name: "lynx ESPHome Version"
    hide_timestamp: true

lynx.h

#include "esphome.h"

// https://diysolarforum.com/threads/anyone-connected-a-lynx-distributor-directly-to-a-rpi-yet.30173/#post-1182368

// The red wire from the RJ10 is SCL, the green one is SDA.
// One needs pull up resistors on the bus.
// The Distributors are I2C slaves.
// The dip switch vary the I2C device address.
// A is 0x08, B is 0x09, C is 0x0a and D is 0x0b.

// When reading one of the connected Distributor I2C devices they return the fuse states encoded in a byte.
// Left fuse (in regular mounting position) is fuse 1.
// Then from left to right: fuse 1=0x10, fuse 2=0x20, fuse 3=0x40, fuse 4=0x80.

// An existing fuse is represented by 0. Any missing or blown fuse is represented by 1.
// So if all 4 fuses are existing and ok then the Distributor returns 0x00.
// If e.g. fuse 1 and 4 are missing, then it would return 0x60.
// If all fuses are missing it is 0xf0.

class LynxSensor : public PollingComponent {
private:
	int _address;
public:
	BinarySensor *fuse1 = new BinarySensor();
	BinarySensor *fuse2 = new BinarySensor();
	BinarySensor *fuse3 = new BinarySensor();
	BinarySensor *fuse4 = new BinarySensor();

	LynxSensor(int address) : PollingComponent(60 * 1000) {
		_address = address;
	}

	void setup() override {
		ESP_LOGI("lynx", "Setup");
		Wire.begin();
	}

	void update() override {
		byte count = Wire.requestFrom(_address, 1);
		if (count == 1) {
			byte data = Wire.read();
			ESP_LOGI("lynx", "Read %x=%x", _address, data);
			fuse1->publish_state((data & 0x10) != 0); // left
			fuse2->publish_state((data & 0x20) != 0);
			fuse3->publish_state((data & 0x40) != 0);
			fuse4->publish_state((data & 0x80) != 0); // right
		} else {
			ESP_LOGE("lynx", "Read %x failed bytes=%d", _address, count);
		}
	}
};

LynxSensor *lynx_fuses;

Peak power usage seems to be < 200 mA. The esp32 can draw some power to scan for Wi-Fi networks, so better provide something like 1A.

I’m using this connected to the busbar of the distributor, bought via Amazon, but a decent USB power adapter will do too:

This is how it looks like in Home Assistant (Dutch, but I guess you’ll get the idea):

WhatsApp Image 2024-09-14 at 10.17.49

2 Likes

Nice but can it read more than one Lynx distro with the one esp home device?

Yes, that’s possible by connecting the lynx distributors with each other (up to 4) using the included RJ10 cable and configuring different addresses with the dip switches. The source code needs to be changed for this as well, but that’s not too difficult.

1 Like

Brilliant might have a look at this project

Hi @m66b , @Daza

Nice work!
Hope you won’t mind me for hijacking your topic, but here is another user that managed to do the same and also stored the read values into dbus, in order to have the Lynx distributor and its fuses statuses on Cerbo.
Maybe you exchange experiences.

1 Like

A driver is needed to store the values to dbus. It is possible, but less trivial because it requires software to be installed on the Cerbo to register the distributor(s) as devices (batteries in fact). esphone could publish values via mqtt after the distributor has been registered.

Please see below for the source code to check four Lynx distributors.
I can test one only, but I see no reasons why it wouldn’t work for four.
The power usage might be a bit more, so take care of a proper adapter/converter.
Note that the DIP switches should be used to give each distributor its own address.

lynx.yaml

#https://www.wemos.cc/en/latest/s2/s2_mini.html

#rj10
# pin 1 yellow = VBUS
# pin 2 green  = SDA = GPIO33
# pin 3 red    = SCL = GPIO35
# pin 4 black  = GND

esphome:
  name: lynx
  includes:
    - lynx.h
  libraries:
    - Wire

esp32:
  board: lolin_s2_mini
  variant: ESP32S2
  framework:
    type: arduino

logger:
  hardware_uart: USB_CDC

api:

ota:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

light:
  - platform: status_led
    name: "Status LED"
    id: lynx_status_led
    icon: "mdi:alarm-light"
    restore_mode: ALWAYS_OFF
    pin:
      number: GPIO15
      inverted: false

#https://esphome.io/components/i2c.html
i2c:
  - id: bus_a
    sda: GPIO33
    scl: GPIO35
    frequency: 50kHz
    scan: true

#https://esphome.io/components/binary_sensor/custom.html
binary_sensor:
  - platform: custom
    lambda: |-
      lynx_fuses[0] = new LynxSensor(0x08);
      lynx_fuses[1] = new LynxSensor(0x09);
      lynx_fuses[2] = new LynxSensor(0x0A);
      lynx_fuses[3] = new LynxSensor(0x0B);
      App.register_component(lynx_fuses[0]);
      App.register_component(lynx_fuses[1]);
      App.register_component(lynx_fuses[2]);
      App.register_component(lynx_fuses[3]);
      return {
        lynx_fuses[0]->fuse1, lynx_fuses[0]->fuse2, lynx_fuses[0]->fuse3, lynx_fuses[0]->fuse4,
        lynx_fuses[1]->fuse1, lynx_fuses[1]->fuse2, lynx_fuses[1]->fuse3, lynx_fuses[1]->fuse4,
        lynx_fuses[2]->fuse1, lynx_fuses[2]->fuse2, lynx_fuses[2]->fuse3, lynx_fuses[2]->fuse4,
        lynx_fuses[3]->fuse1, lynx_fuses[3]->fuse2, lynx_fuses[3]->fuse3, lynx_fuses[3]->fuse4,
      };
    binary_sensors:
      - name: "lynx A fuse 1 broken"
        icon: mdi:fuse-alert
        id: lynx_a_fuse_1_broken
      - name: "lynx A fuse 2 broken"
        icon: mdi:fuse-alert
        id: lynx_a_fuse_2_broken
      - name: "lynx A fuse 3 broken"
        icon: mdi:fuse-alert
        id: lynx_a_fuse_3_broken
      - name: "lynx A fuse 4 broken"
        icon: mdi:fuse-alert
        id: lynx_a_fuse_4_broken

      - name: "lynx B fuse 1 broken"
        icon: mdi:fuse-alert
        id: lynx_b_fuse_1_broken
      - name: "lynx B fuse 2 broken"
        icon: mdi:fuse-alert
        id: lynx_b_fuse_2_broken
      - name: "lynx B fuse 3 broken"
        icon: mdi:fuse-alert
        id: lynx_b_fuse_3_broken
      - name: "lynx B fuse 4 broken"
        icon: mdi:fuse-alert
        id: lynx_b_fuse_4_broken

      - name: "lynx C fuse 1 broken"
        icon: mdi:fuse-alert
        id: lynx_c_fuse_1_broken
      - name: "lynx C fuse 2 broken"
        icon: mdi:fuse-alert
        id: lynx_c_fuse_2_broken
      - name: "lynx C fuse 3 broken"
        icon: mdi:fuse-alert
        id: lynx_c_fuse_3_broken
      - name: "lynx C fuse 4 broken"
        icon: mdi:fuse-alert
        id: lynx_c_fuse_4_broken

      - name: "lynx D fuse 1 broken"
        icon: mdi:fuse-alert
        id: lynx_d_fuse_1_broken
      - name: "lynx D fuse 2 broken"
        icon: mdi:fuse-alert
        id: lynx_d_fuse_2_broken
      - name: "lynx D fuse 3 broken"
        icon: mdi:fuse-alert
        id: lynx_d_fuse_3_broken
      - name: "lynx D fuse 4 broken"
        icon: mdi:fuse-alert
        id: lynx_d_fuse_4_broken

  - platform: gpio
    pin:
      number: GPIO0
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on_off: 100ms
    name: "lynx button 0"
    on_click:
    - logger.log: "Button 0 pressed"
    - lambda: |-
        lynx_fuses[0]->update();
        lynx_fuses[1]->update();
        lynx_fuses[2]->update();
        lynx_fuses[3]->update();
    - light.turn_on: lynx_status_led
    - delay: 100ms
    - light.turn_off: lynx_status_led

sensor:
  - platform: wifi_signal
    name: "lynx Wi-Fi Signal"
    update_interval: 120s

  - platform: internal_temperature
    name: "lynx tcore"
    update_interval: 300s

text_sensor:
  - platform: version
    name: "lynx ESPHome Version"
    hide_timestamp: true

lynx.h

#include "esphome.h"

// https://diysolarforum.com/threads/anyone-connected-a-lynx-distributor-directly-to-a-rpi-yet.30173/#post-1182368

// The red wire from the RJ10 is SCL, the green one is SDA.
// One needs pull up resistors on the bus.
// The Distributors are I2C slaves.
// The dip switch vary the I2C device address.
// A is 0x08, B is 0x09, C is 0x0a and D is 0x0b.

// When reading one of the connected Distributor I2C devices they return the fuse states encoded in a byte.
// Left fuse (in regular mounting position) is fuse 1.
// Then from left to right: fuse 1=0x10, fuse 2=0x20, fuse 3=0x40, fuse 4=0x80.

// An existing fuse is represented by 0. Any missing or blown fuse is represented by 1.
// So if all 4 fuses are existing and ok then the Distributor returns 0x00.
// If e.g. fuse 1 and 4 are missing, then it would return 0x60.
// If all fuses are missing it is 0xf0.

class LynxSensor : public PollingComponent {
private:
	int _address;
public:
	BinarySensor *fuse1 = new BinarySensor();
	BinarySensor *fuse2 = new BinarySensor();
	BinarySensor *fuse3 = new BinarySensor();
	BinarySensor *fuse4 = new BinarySensor();

	LynxSensor(int address) : PollingComponent(60 * 1000) {
		_address = address;
	}

	void setup() override {
		ESP_LOGI("lynx", "Setup");
		Wire.begin();
	}

	void update() override {
		byte count = Wire.requestFrom(_address, 1);
		if (count == 1) {
			byte data = Wire.read();
			ESP_LOGI("lynx", "Read %x=%x", _address, data);
			fuse1->publish_state((data & 0x10) != 0); // left
			fuse2->publish_state((data & 0x20) != 0);
			fuse3->publish_state((data & 0x40) != 0);
			fuse4->publish_state((data & 0x80) != 0); // right
		} else {
			ESP_LOGE("lynx", "Read %x failed bytes=%d", _address, count);
		}
	}
};

LynxSensor *lynx_fuses[4];
2 Likes

Thank you so much for sharing !

BinarySensor ?
PollingComponent ?
Wire ?

That’s how esphome works …

Indeed, but I’ve asked, if possible, for those components definitions.
For example, BinarySensor is a class somewhere, right?
Because later there is a function called from that class (publish_state)…
Thanks!

LE: Sorry, nevermind, found out …

thank you both for now its the Home Assistant element im trying to move everything to that platform so all of my house is all in one and just have the specific app’s / interfaces if i need to drill down on issues @m66b @alexpescaru thanks