syssi / esphome-ant-bms

ESPHome component to monitor and control a ANT-BMS via UART
Apache License 2.0
54 stars 13 forks source link

Using entity from ANT BMS to control PWM signal ESP32 #46

Closed lumiror closed 1 year ago

lumiror commented 1 year ago

Hello, I want to ask, would it be possible to use an entity from ANT BMS in homeassistant, specifically information about the battery charge in % (sensor.antbms_soc) on pwm signal control from ESP32? For example, if the battery is 95% charged at the ESP32 output, the PWM signal will be 20%, if it is 96% at the output, it will be 35%, 97%=50%, 98%=65%, 99%=85%. 100%=100. Thank you

syssi commented 1 year ago

I don't understand you question. All BMS measurements are exposed as sensor entities and are available at Home Assistant if you like. I recommend to use the native api component to make the ESPHome node available/accessible at Home Assistant.

You could extend the YAML configuration to monitor the state of charge entity and control some LEDs to visualize the SoC.

I use something like this: https://de.aliexpress.com/item/4000877848238.html

The ESPHome code looks like this:

sensor:
  - platform: ant_bms
    soc:
      name: "${name} soc"
      id: soc

light:
  - platform: neopixelbus
    id: strip
    name: "LED strip"
    type: GRB
    pin: GPIO3
    num_leds: 24
    method: ESP8266_DMA
    variant: WS2812
    restore_mode: ALWAYS_ON
    internal: true

display:
  - platform: addressable_light
    id: gauge
    addressable_light_id: strip
    width: 24
    height: 1
    update_interval: 16ms
    lambda: |-
      float brightness = 0.40f;
      Color color = Color(
        brightness * 0,
        brightness * 255,
        brightness * 0
      );
      if(id(soc) && !isnan(id(soc).state)) {
        int width = std::floor((it.get_width() / 100.0f) * id(soc).state);
        it.line(0, 0, width, 0, color);
      }
lumiror commented 1 year ago

Thank you for your quick reply. To make it more precise, I would need to control the output PWM signal on the esp32 or ESP 8266 based on the value of the entity, i.e. the battery charge in % (sensor.antbms_soc). As I wrote, if the value of the entity is e.g. 95%, the battery is charged to 95% on the ESP 32 analog output will be 10% PWM signal, etc. I need to use this signal to control the PWM to volt converter from AliExpress. https://www.aliexpress.com/item/4000128019142.html

syssi commented 1 year ago

Is the ESP reading the ANT-BMS and the ESP with the PWM output the same device / ESP?

syssi commented 1 year ago

You could use these components to control the PWM of a ESP8266/ESP32:

https://esphome.io/components/output/esp8266_pwm.html https://esphome.io/components/output/ledc.html

lumiror commented 1 year ago

Is the ESP reading the ANT-BMS and the ESP with the PWM output the same device / ESP?

No ANT-BMS has a separate ESP32

syssi commented 1 year ago

Do you prefer a MQTT or a Home Assistant connection?

lumiror commented 1 year ago

home assistant I found something like this, but I don't know how I could replace that sensor with my own (sensor.antbms_soc)

output:
  - platform: ledc
    pin: GPIO14
    frequency: 25000 Hz
    id: fan_pwm

fan:
  - platform: speed
    output: fan_pwm
    name: "Network Rack Exhaust Fan Speed"

sensor:
  - platform: bmp280
    i2c_id: bus_b
    address: 0x77
    temperature:
      name: "Network Rack Temperature"
      id: rack_temp
      oversampling: 16x
      on_value:
        then:
          lambda: |-
            if (id(rack_temp).state < 23.0) {
                id(fan_pwm).set_level(0.00);
                ESP_LOGD("PWM", "PWM: 0%");
            }
            else if ((id(rack_temp).state >= 23.0) and (id(rack_temp).state <= 24.0)) {
                id(fan_pwm).set_level(0.05);
                ESP_LOGD("PWM", "PWM: 5%");
            }
            else if ((id(rack_temp).state > 24.0) and (id(rack_temp).state <= 25.0)) {
                id(fan_pwm).set_level(0.1);
                ESP_LOGD("PWM", "PWM: 10%");
            }
            else if ((id(rack_temp).state > 25.0) and (id(rack_temp).state <= 26.0)) {
                id(fan_pwm).set_level(0.2);
                ESP_LOGD("PWM", "PWM: 20%");
            }
            else if ((id(rack_temp).state > 26.0) and (id(rack_temp).state <= 27.0)) {
                id(fan_pwm).set_level(0.3);
                ESP_LOGD("PWM", "PWM: 30%");
            }
            else if ((id(rack_temp).state > 27.0) and (id(rack_temp).state <= 28.0)) {
                id(fan_pwm).set_level(0.4);
                ESP_LOGD("PWM", "PWM: 40%");
            }
            else if ((id(rack_temp).state > 28.0) and (id(rack_temp).state <= 29.0)) {
                id(fan_pwm).set_level(0.5);
                ESP_LOGD("PWM", "PWM: 50%");
            }
            else if ((id(rack_temp).state > 29.0) and (id(rack_temp).state <= 30.0)) {
                id(fan_pwm).set_level(0.6);
                ESP_LOGD("PWM", "PWM: 60%");
            }
            else if ((id(rack_temp).state > 30.0) and (id(rack_temp).state <= 32.0)) {
                id(fan_pwm).set_level(0.7);
                ESP_LOGD("PWM", "PWM: 70%");
            }
            else if ((id(rack_temp).state > 32.0) and (id(rack_temp).state <= 35.0)) {
                id(fan_pwm).set_level(0.8);
                ESP_LOGD("PWM", "PWM: 80%");
            }
            else {
                id(fan_pwm).set_level(1.0);
                ESP_LOGD("ALERT", "OVER 35C! Setting fan to 100%");
            }

    update_interval: 2s
syssi commented 1 year ago

Do you prefer a MQTT or a Home Assistant connection between the ESPs?

lumiror commented 1 year ago

Home Assistant

syssi commented 1 year ago

You can subscribe to any Home Assistant sensor using the homeassistant component. It should look like this:

api:

output:
  - platform: ledc
    pin: GPIO14
    frequency: 25000 Hz
    id: output_pwm

sensor:
  - platform: homeassistant
    internal: true
    id: soc
    name: "${name} soc"
    entity_id: sensor.ant-bms_soc
    on_value:
      then:
        lambda: |-
          if (id(soc).state < 23.0) {
              id(output_pwm).set_level(0.00);
              ESP_LOGD("PWM", "PWM: 0%");
          }
          else if ((id(soc).state >= 23.0) and (id(soc).state <= 24.0)) {
              id(output_pwm).set_level(0.05);
              ESP_LOGD("PWM", "PWM: 5%");
          }
          else if ((id(soc).state > 24.0) and (id(soc).state <= 25.0)) {
              id(output_pwm).set_level(0.1);
              ESP_LOGD("PWM", "PWM: 10%");
          }
          else if ((id(soc).state > 25.0) and (id(soc).state <= 26.0)) {
              id(output_pwm).set_level(0.2);
              ESP_LOGD("PWM", "PWM: 20%");
          }
          else if ((id(soc).state > 26.0) and (id(soc).state <= 27.0)) {
              id(output_pwm).set_level(0.3);
              ESP_LOGD("PWM", "PWM: 30%");
          }
          else if ((id(soc).state > 27.0) and (id(soc).state <= 28.0)) {
              id(output_pwm).set_level(0.4);
              ESP_LOGD("PWM", "PWM: 40%");
          }
          else if ((id(soc).state > 28.0) and (id(soc).state <= 29.0)) {
              id(output_pwm).set_level(0.5);
              ESP_LOGD("PWM", "PWM: 50%");
          }
          else if ((id(soc).state > 29.0) and (id(soc).state <= 30.0)) {
              id(output_pwm).set_level(0.6);
              ESP_LOGD("PWM", "PWM: 60%");
          }
          else if ((id(soc).state > 30.0) and (id(soc).state <= 32.0)) {
              id(output_pwm).set_level(0.7);
              ESP_LOGD("PWM", "PWM: 70%");
          }
          else if ((id(soc).state > 32.0) and (id(soc).state <= 35.0)) {
              id(output_pwm).set_level(0.8);
              ESP_LOGD("PWM", "PWM: 80%");
          }
          else {
              id(output_pwm).set_level(1.0);
              ESP_LOGD("ALERT", "OVER 35C! Setting fan to 100%");
          }
lumiror commented 1 year ago

Thank you very much for your help, I'm going to try it

syssi commented 1 year ago

You are welcome! I've fixed a typo. Please use the most recent version of the example above.

lumiror commented 1 year ago

Can you please look at it again, why it marked line 45 as an error. I didn't put the API: there, I already have it in the compilation, is that correct?

foto

syssi commented 1 year ago

Please goto your Home Assistant instance, open the "Developer Tools -> States" page (/developer-tools/state) and search for "soc". Please copy the full entity name of your state of charge entity. Use this entity name at the YAML. May be you have to add quotation marks.

lumiror commented 1 year ago

Yes, there is a mistake, I have already fixed it. Thanks image

lumiror commented 1 year ago

Oh no, I just destroyed my ESP32. I'm an idiot. I also have an ESP 8266, I can use it or the Output would have to be reconfigured

syssi commented 1 year ago

How did you destroy the ESP?

syssi commented 1 year ago

If you use a ESP8266 you have to use the esp8266_pwm component:

output:
  - platform: esp8266_pwm
    pin: GPIO4
    frequency: 1000 Hz
    id: output_pwm
lumiror commented 1 year ago

I connected +a- the other way around

syssi commented 1 year ago

Okay. ;-) In this case, I also think it is dead.

lumiror commented 1 year ago

Thank you very much, I'm going to try it again.

lumiror commented 1 year ago

So it works perfectly, I just have one more request for you. Would it be possible to create an entity for the state of the PWM signal so that I can see it in homeassistant? I would be very grateful. And I did not destroy the ESP32, :) only the diode was destroyed.

syssi commented 1 year ago

Hmm... it looks like we cannot ask the output for the current PWM. I assume you have to implement a workaround like this:

sensor:
  - platform: template
    name: "PWM"
    id: pwm_sensor
  - platform: homeassistant
    internal: true
    id: soc
    name: "${name} soc"
    entity_id: sensor.ant-bms_soc
    on_value:
      then:
        lambda: |-
          if (id(soc).state < 23.0) {
              id(output_pwm).set_level(0.00);
              id(pwm_sensor).publish_state(0.00);
          }
          else if ((id(soc).state >= 23.0) and (id(soc).state <= 24.0)) {
              id(output_pwm).set_level(0.05);
              id(pwm_sensor).publish_state(0.05);
          }
          else if ((id(soc).state > 24.0) and (id(soc).state <= 25.0)) {
              id(output_pwm).set_level(0.1);
              id(pwm_sensor).publish_state(0.1);
          }
          else if ((id(soc).state > 25.0) and (id(soc).state <= 26.0)) {
              id(output_pwm).set_level(0.2);
              id(pwm_sensor).publish_state(0.2);
          }
          else if ((id(soc).state > 26.0) and (id(soc).state <= 27.0)) {
              id(output_pwm).set_level(0.3);
              id(pwm_sensor).publish_state(0.3);
          }
          else {
              id(output_pwm).set_level(1.0);
              id(pwm_sensor).publish_state(1.0);
          }
lumiror commented 1 year ago

Thank you, it works perfectly

syssi commented 1 year ago

Can you explain the purpose of your setup in a few words? What do you drive with the output voltage of the converter? Thanks in advance!

lumiror commented 1 year ago

Yes, of course I can. I need to control the water heating in the tank. That is, if the batteries are charged, the water will start to heat up. And in order to use as much energy as possible from the sun, the water heating will start when the battery is charged to approx. 95% and will gradually increase.

lumiror commented 1 year ago

Can I ask one more question? Would it be possible to control the PWM signal linearly? That is, on the basis of some value, e.g. 0-20 from the entity and the Pwm signal output would be 0-100%?

syssi commented 1 year ago

Could you provide your current configuration / lambda function? I would like to understand which (SoC) value(s) should be translated into another one (PWM percentage).

lumiror commented 1 year ago

The value is in Amp, it is the voltage on the solar panels. But it is not an installation on ESP... but on a raspberry and transferred via mqtt to home assistant. It's data from my inverter image image

syssi commented 1 year ago

I'm a bit confused. I need some more details to understand your goal. Yesterday we talked about the SoC and we tried to feed your heater depending on the state of charge in steps. You prefer a linear function instead of steps now. Right? How do you like to take the panel current into account?

lumiror commented 1 year ago

What we dealt with yesterday is OK. This is something else. This is a value from another entity in Amp. Its range is from 0-20A and I would need to linearly convert it to a PWM signal to the ESP 32/2866. so that 0-20A = 0-100% PWM, if that is possible.

syssi commented 1 year ago

What do you control with the PWM signal this time? It's the heater again and both solutions should be combined or it's another device? :-)

lumiror commented 1 year ago

Exactly, :) I would like to use them together and control the water heating according to them. I wanted to use a Hall current sensor, but this would be better.

syssi commented 1 year ago

It's pretty the same like last time. ;-) You can make the sensor state from HA available using this snippet:

  - platform: homeassistant
    internal: true
    id: pv_current
    name: "${name} pv current"
    entity_id: sensor.panely_amp

The combination of both values is a bit tricky. Could you try to describe the logic in words? Something like:

  1. If the PV current is >10A but the SoC <95%: Set PWM to 100%
  2. If the PV current is <10A and the SoC >95%: Set the PWM to 100% too
  3. If the PV current is <10A and the SoC <95%: ...
lumiror commented 1 year ago

This sensor will be separate, I don't need the values to be combined with the sensor that we solved yesterday. It would be better linearly, not in steps. So use the tenth place. If, for example, 10A=50%PWM, if 10.1A=50.5%PWM, if 10.2A = 50.1%PWM, etc. Just writing it like before was probably too long, isn't there a formula? I will compare the values from both sensors myself.

syssi commented 1 year ago

I assume you are looking for something like this:

sensor:
  - platform: template
    name: "PWM"
    id: pwm_sensor
  - platform: homeassistant
    internal: true
    id: pv_current
    name: "${name} pv current"
    entity_id: sensor.panely_amp
    on_value:
      then:
        lambda: |-
          if(id(pv_current) && !isnan(id(pv_current).state)) {
            float percentage = max(min(id(pv_current).state * 5.0f, 100.0f), 0.0f);

            id(output_pwm).set_level(percentage);
            id(pwm_sensor).publish_state(percentage);
          }

On each new "panely_amp" value the PWM is changed according the following formula:

lumiror commented 1 year ago

It looks great, I'll try it. I just found out that the testing is difficult, because waiting for the real value to change is difficult and I don't have any entities that I would use for the test and on which the value would be changed manually in homeassistant. But I'll manage somehow. Thanks

lumiror commented 1 year ago

I still want to ask what happens to the PWM signal if the value in the entity is lost? Because sometimes I lose the connection to the ESP32/2866 and the data is unavailable. The outage lasts approx. 4-6 seconds.

syssi commented 1 year ago

The if condition

         if(id(pv_current) && !isnan(id(pv_current).state)) {}

checks the sensor with the ID pv_current exists and provides a value unequal "not a number". In other words the code at the brackets ({}) gets executed only if the sensor value is numeric. At the ESPHome ecosystem the unavailable state is encoded as NaN. I assume the PWM doesn't get changed if your pv_current sensor gets unavailable.

lumiror commented 1 year ago

Well, it's not very good if the PWM remains at an unchanged value, especially in the case of permanent malfunctions. Is it not possible to insert some time condition that if the signal is not renewed after xy /sec., the signal will be switched off to 0?

syssi commented 1 year ago

What about:


sensor:
  - platform: template
    name: "PWM"
    id: pwm_sensor
  - platform: homeassistant
    internal: true
    id: pv_current
    name: "${name} pv current"
    entity_id: sensor.panely_amp
    on_value:
      then:
        lambda: |-
          if(id(pv_current) && !isnan(id(pv_current).state)) {
            float percentage = max(min(id(pv_current).state * 5.0f, 100.0f), 0.0f);

            id(output_pwm).set_level(percentage);
            id(pwm_sensor).publish_state(percentage);
          } else {
            id(output_pwm).set_level(0.0f);
            id(pwm_sensor).publish_state(0.0f);
          }
lumiror commented 1 year ago

Does this mean that if there is no value from the entity at the output, the PWM will be 0?

syssi commented 1 year ago

Yes!

lumiror commented 1 year ago

And this time condition could not be added there, so that the PWM signal is not turned off unnecessarily if the outage is short / one-time

syssi commented 1 year ago

I cannot provide more advanced solutions without investing too much time.

lumiror commented 1 year ago

I understand clearly, thank you very much for your help anyway. :)

syssi commented 1 year ago

Let's close this issue. It's solved! :-)

lumiror commented 1 year ago

Yes Thanks. image

lumiror commented 1 year ago

Hi, could you help me one more time? I don't know why, but I still have 100% voltage on the "PWM1" output1_pwm of GPIO27 in my test circuit. The formula seems to be fine 0-20V=0-100%, but in reality the PWM signal on the GPIO27 output does not change and is still as I wrote 100% . "PWM" on the GPIO25 output works correctly. Thank you

output:
  - platform: ledc
    pin: GPIO25
    frequency: 25000 Hz
    id: output_pwm
  - platform: ledc  
    pin: GPIO27
    frequency: 25000 Hz
    id: output1_pwm

sensor:
  - platform: adc
    pin: GPIO35
    name: "voltage sensor"
    id: voltage_sensor 
    update_interval: 2s
    filters:
      - multiply: 50

  - platform: adc
    pin: GPIO34
    name: "voltage sensor1"
    id: voltage_sensor1
    update_interval: 2s
    filters:
      - multiply: 50

  - platform: template
    name: "PWM"
    id: pwm_sensor
  - platform: homeassistant
    internal: true
    id: soc
    name: "${name} soc"
    entity_id: sensor.voltage_sensor
    on_value:
      then:
        lambda: |-
         if (id(soc).state < 23.0) {
              id(output_pwm).set_level(0.00);
              id(pwm_sensor).publish_state(0.00);
          }
          else if ((id(soc).state >= 23.0) and (id(soc).state <= 24.0)) {
              id(output_pwm).set_level(0.1);
              id(pwm_sensor).publish_state(0.05);
          }
          else if ((id(soc).state > 24.0) and (id(soc).state <= 25.0)) {
              id(output_pwm).set_level(0.2);
              id(pwm_sensor).publish_state(0.2);
          }
          else if ((id(soc).state > 25.0) and (id(soc).state <= 26.0)) {
              id(output_pwm).set_level(0.3);
              id(pwm_sensor).publish_state(0.3);
          }
          else if ((id(soc).state > 26.0) and (id(soc).state <= 27.0)) {
              id(output_pwm).set_level(0.4);
              id(pwm_sensor).publish_state(0.4);
          }
          else if ((id(soc).state > 27.0) and (id(soc).state <= 28.0)) {
              id(output_pwm).set_level(0.5);
              id(pwm_sensor).publish_state(0.5);
          }
          else if ((id(soc).state > 28.0) and (id(soc).state <= 29.0)) {
              id(output_pwm).set_level(0.6);
              id(pwm_sensor).publish_state(0.6);
          }
          else {
              id(output_pwm).set_level(1.0);
              id(pwm_sensor).publish_state(1.0);
          }

  - platform: template
    name: "PWM1"
    id: pwm1_sensor
  - platform: homeassistant
    internal: true
    id: pv_current
    name: "${name} pv current"
    entity_id: sensor.voltage_sensor1
    on_value:
      then:
        lambda: |-
          if(id(pv_current) && !isnan(id(pv_current).state)) {
            float percentage = max(min(id(pv_current).state * 5.0f, 100.0f), 0.0f);

            id(output1_pwm).set_level(percentage);
            id(pwm1_sensor).publish_state(percentage);
          }
lumiror commented 1 year ago

I measured it and it looks like there is probably no signal there, only the output is high. Because there is a frequency of 25KHz on the output of GPIO25. But there is no frequency on the output of GPIO27.