kirisakow
(Kiril Isakov)
April 6, 2024, 4:57pm
1
I need to enhance my YAML ESP32 EspHome configuration file bmssolar.yaml
(see the code below) with two functions (ideally, declared within the single existing YAML configuration file).
Context
My electricity provider only allows to feed in 4000W, and the inverter is also set to a feed-in power of 4000W. Therefore, I only want to charge the battery when the feed-in power reaches a value close to 4000W.
My battery charging current is a value read via Modbus „Netz-Leistung“ (“grid power”), and ranges from a minimum of 1A to a maximum of 25A. Currently, it is hardcoded in the YAML config file at 25.
Function 1
Variables:
Be a global constant feed_in_power_threshold
equal to 4000W * 0.975
.
I want the Modbus „Netz-Leistung“ value to be periodically (every 30 seconds) compared with a decimal value equal to feed_in_power_threshold
.
While the Modbus „Netz-Leistung“ value is greater than the threshold, the variable should be increased by a delta 0.2A as long as it hasn’t reached the max constant value of 25A.
While the Modbus „Netz-Leistung“ value is less than the threshold, the variable should be decreased by a delta 0.2A as long as it hasn’t reached the min constant value of 1A.
I would also like to see the Modbus „Netz-Leistung“ value to be logged or printed out under ESPHome.
Function 2
Variables:
Be a global constant battery_soc_threshold
equal to 80%
.
Be a global constant high_power_threshold
equal to 4000W * 0.9875
.
Be a global constant low_power_threshold
equal to 4000W * 0.875
.
The value read via Modbus „SOC“ should be periodically (every 30 seconds) compared with battery_soc_threshold
.
While the Modbus „SOC“ is greater than battery_soc_threshold
, then:
While the Modbus „Netz-Leistung“ is greater than a numeric high_power_threshold
, a GPIO should be set to high (a consumer should switch on) and remain high for at least 10 minutes. No new control should be performed within that sleep time.
While the Modbus „Netz-Leistung“ is less than a numeric low_power_threshold
, the GPIO should go low (the consumer should switch off) and remain low for at least 5 minutes. No new control should be performed within that sleep time.
Particular wishes
The code should contain no so-called magic numbers. Any value should be declared as a global constant or a global variable with a meaningful name.
Ideally, I want it all to hold within the single existing YAML configuration file.
Error messages I am getting
YAML config file
substitutions:
name: bmssolar
max_cycles: "6000.0"
tx_pin: GPIO1
rx_pin: GPIO3
feed_in_power_threshold: !lambda '4000 * 0.975'
battery_soc_threshold: !lambda '4000 * 0.8'
high_power_threshold: !lambda '4000 * 0.9875'
low_power_threshold: !lambda '4000 * 0.875'
delta_charging_current: 0.2
max_charging_current: 25
min_charging_current: 1
esphome:
name: bmssolar
friendly_name: bmssolar
esp32:
board: esp32dev
framework:
type: arduino
logger:
baud_rate: 0
globals:
- id: can_420_rx
type: int
restore_value: "no"
initial_value: "0"
- id: charge_status
type: "std::string"
restore_value: "no"
initial_value: '"Startup"'
- id: charging_current
type: float
restore_value: "no"
initial_value: "25"
button:
- platform: restart
name: Restart button
id: restart_button
internal: true
wifi:
ssid: null
password: null
ap:
ssid: BmsSolar Fallback Hotspot
password: null
api:
encryption:
key: null
ota:
password: null
uart:
id: uart_0
baud_rate: 9600
rx_buffer_size: 384
tx_pin: "${tx_pin}"
rx_pin: "${rx_pin}"
modbus:
id: modbus0
uart_id: uart_0
send_wait_time: 900ms
modbus_controller:
id: bms0
address: 247
modbus_id: modbus0
command_throttle: 900ms
update_interval: 30s
output:
- platform: gpio
pin: 2
id: led
inverted: false
switch:
- platform: gpio
pin: GPIO4
name: Test
id: Test
sensor:
- platform: modbus_controller
modbus_controller_id: bms0
id: power_sensor
name: Leistung
address: 35140
register_type: holding
value_type: S_WORD
unit_of_measurement: W
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
- platform: modbus_controller
modbus_controller_id: bms0
id: soc_sensor
name: SOC
address: 37007
register_type: holding
value_type: S_WORD
unit_of_measurement: "%"
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
- platform: modbus_controller
modbus_controller_id: bms0
name: Temperatur
address: 35174
register_type: holding
value_type: S_WORD
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
template:
- sensor:
- name: "Charging Current"
id: charging_current_sensor
unit_of_measurement: "A"
accuracy_decimals: 1
lambda: |-
float power = id(power_sensor).state;
float current = id(charging_current);
if (power > feed_in_power_threshold) {
if (current < max_charging_current) {
current += delta_charging_current;
}
} else if (power < feed_in_power_threshold) {
if (current > min_charging_current) {
current -= delta_charging_current;
}
}
id(charging_current) = current;
ESP_LOGD("charging_current", "Calculated charging current: %.1f", current);
return current;
automation:
- id: charge_control
alias: "Charge Control"
trigger:
- platform: time
interval: 30s
action:
- lambda: |-
float soc = id(soc_sensor).state;
float power = id(power_sensor).state;
if (soc > battery_soc_threshold && power > high_power_threshold) {
digitalWrite(GPIO_PIN, HIGH);
id(led).turn_on();
delay(600000); // 10 minutes in milliseconds
float new_power = id(power_sensor).state;
if (new_power < low_power_threshold) {
digitalWrite(GPIO_PIN, LOW);
id(led).turn_off();
delay(300000); // 5 minutes in milliseconds
}
} else {
digitalWrite(GPIO_PIN, LOW);
id(led).turn_off();
}
nickrout
(Nick Rout)
April 6, 2024, 9:34pm
2
kirisakow:
automation:
Probably because that isn’t a valid component. However automations are fully described here
Nor is that.
kirisakow
(Kiril Isakov)
April 12, 2024, 10:34pm
3
Hello Nick, I’m replying to you as you were the only person who has replied as of now. I followed your advice and read that documentation chapter. Now I’m back with the refactored YAML config. Can you please take a look at the refactored code below and tell me what you think?
substitutions:
name: bmssolar
max_cycles: "6000.0"
tx_pin: GPIO1
rx_pin: GPIO3
esphome:
name: bmssolar
friendly_name: bmssolar
esp32:
board: esp32dev
framework:
type: arduino
logger:
baud_rate: 0
globals:
- id: can_420_rx
type: int
restore_value: 'no'
initial_value: '0'
- id: charge_status
type: 'std::string'
restore_value: 'no'
initial_value: '"Startup"'
- id: battery_charge_current
type: float
restore_value: 'no'
initial_value: '1.0'
- id: feed_in_power_threshold
type: float
restore_value: 'no'
initial_value: '3900.0' # 4000W * 0.975
- id: battery_soc_threshold
type: float
restore_value: 'no'
initial_value: '80.0'
- id: high_power_threshold
type: float
restore_value: 'no'
initial_value: '3950.0' # 4000W * 0.9875
- id: low_power_threshold
type: float
restore_value: 'no'
initial_value: '3500.0' # 4000W * 0.875
- id: delta_charging_current
type: float
restore_value: 'no'
initial_value: '0.2'
- id: max_charging_current
type: int
restore_value: 'no'
initial_value: '25'
- id: min_charging_current
type: int
restore_value: 'no'
initial_value: '1'
button:
- platform: restart
name: Restart button
id: restart_button
internal: true
wifi:
ssid: null
password: null
ap:
ssid: BmsSolar Fallback Hotspot
password: null
api:
encryption:
key: null
ota:
password: null
uart:
id: uart_0
baud_rate: 9600
rx_buffer_size: 384
tx_pin: "${tx_pin}"
rx_pin: "${rx_pin}"
modbus:
id: modbus0
uart_id: uart_0
send_wait_time: 900ms
modbus_controller:
id: bms0
address: 247
modbus_id: modbus0
command_throttle: 900ms
update_interval: 3s
output:
- platform: gpio
pin: 2
id: led
inverted: false
switch:
- platform: gpio
pin: GPIO4
name: Test
id: Test
sensor:
- platform: modbus_controller
modbus_controller_id: bms0
id: power_sensor
name: Leistung
address: 35140
register_type: holding
value_type: S_WORD
unit_of_measurement: W
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
on_value:
then:
- lambda: |-
if (id(power_sensor).state > id(feed_in_power_threshold)) {
if (id(battery_charge_current) < id(max_charging_current)) {
id(battery_charge_current) += id(delta_charging_current);
}
} else if (id(power_sensor).state < id(feed_in_power_threshold)) {
if (id(battery_charge_current) > id(min_charging_current)) {
id(battery_charge_current) -= id(delta_charging_current);
}
}
- platform: modbus_controller
modbus_controller_id: bms0
id: soc_sensor
name: SOC
address: 37007
register_type: holding
value_type: S_WORD
unit_of_measurement: "%"
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
on_value:
then:
- lambda: |-
if (id(soc_sensor).state > id(battery_soc_threshold)) {
if (id(power_sensor).state > id(high_power_threshold)) {
digitalWrite(GPIO_PIN, HIGH);
id(led).turn_on();
delay(600000); // 10 minutes in milliseconds
if (id(power_sensor).state < id(low_power_threshold)) {
digitalWrite(GPIO_PIN, LOW);
id(led).turn_off();
delay(300000); // 5 minutes in milliseconds
}
} else {
digitalWrite(GPIO_PIN, LOW);
id(led).turn_off();
}
}
nickrout
(Nick Rout)
April 13, 2024, 2:48am
4
Well structurally it looks better, but I am certainly not an expert. Question is, does it work?
kirisakow
(Kiril Isakov)
April 13, 2024, 5:40pm
5
Right now I’m getting
In lambda function:
(…) error: 'GPIO4' was not declared in this scope
for each digitalWrite(GPIO_PIN, ...)
statement
Mikefila
(Mike Fila)
April 13, 2024, 9:26pm
6
GPIO_PIN
is a place holder. You would use the pin number. The only place I can find digitalWrite
in esphome is under the depreciated custom component. If you want to turn a switch on/off the lambda from the standard switch component should work.
kirisakow
(Kiril Isakov)
April 14, 2024, 9:55pm
7
Duly noted, thank you
I too have run a search through the documentation , and have come to the very same conclusion.
Duly noted, thank you
kirisakow
(Kiril Isakov)
May 4, 2024, 5:11pm
8
Thank you to everyone who contributed.
Here’s the solution:
substitutions:
name: bmssolar
max_cycles: "6000.0"
tx_pin: GPIO1
rx_pin: GPIO3
esphome:
name: bmssolar
friendly_name: bmssolar
esp32:
board: esp32dev
framework:
type: arduino
logger:
baud_rate: 0
globals:
- id: can_420_rx
type: int
restore_value: 'no'
initial_value: '0'
- id: charge_status
type: 'std::string'
restore_value: 'no'
initial_value: '"Startup"'
- id: battery_charge_current
type: float
restore_value: 'no'
initial_value: '1.0'
- id: feed_in_power_threshold
type: float
restore_value: 'no'
initial_value: '3900.0' # 4000W * 0.975
- id: battery_soc_threshold
type: float
restore_value: 'no'
initial_value: '60.0'
- id: high_power_threshold
type: float
restore_value: 'no'
initial_value: '3800.0'
- id: low_power_threshold
type: float
restore_value: 'no'
initial_value: '3000.0'
- id: delta_charging_current
type: float
restore_value: 'no'
initial_value: '0.2'
- id: max_charging_current
type: int
restore_value: 'no'
initial_value: '25'
- id: min_charging_current
type: int
restore_value: 'no'
initial_value: '1'
button:
- platform: restart
name: Restart button
id: restart_button
internal: true
wifi:
ssid: null
password: null
ap:
ssid: BmsSolar Fallback Hotspot
password: null
api:
encryption:
key: null
ota:
password: null
uart:
id: uart_0
baud_rate: 9600
rx_buffer_size: 384
tx_pin: "${tx_pin}"
rx_pin: "${rx_pin}"
modbus:
id: modbus0
uart_id: uart_0
send_wait_time: 900ms
modbus_controller:
id: bms0
address: 247
modbus_id: modbus0
command_throttle: 900ms
update_interval: 3s
switch:
- platform: gpio
id: relais
pin: 13
name: 'Ausgang'
sensor:
- platform: modbus_controller
modbus_controller_id: bms0
id: power_sensor
name: Leistung
address: 35140
register_type: holding
value_type: S_WORD
unit_of_measurement: W
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
on_value:
then:
- lambda: |-
if (id(power_sensor).state > id(feed_in_power_threshold)) {
if (id(battery_charge_current) < id(max_charging_current)) {
id(battery_charge_current) += id(delta_charging_current);
ESP_LOGI('power_sensor', 'battery_charge_current increased to %f A',
id(battery_charge_current));
}
} else if (id(power_sensor).state < id(feed_in_power_threshold)) {
if (id(battery_charge_current) > id(min_charging_current)) {
id(battery_charge_current) -= id(delta_charging_current);
ESP_LOGI('power_sensor', 'battery_charge_current decreased to %f A',
id(battery_charge_current));
}
}
- platform: modbus_controller
modbus_controller_id: bms0
id: soc_sensor
name: SOC
address: 37007
register_type: holding
value_type: S_WORD
unit_of_measurement: "%"
device_class: energy
state_class: measurement
accuracy_decimals: 0
filters:
- multiply: 1
on_value:
then:
- if:
condition:
lambda: |-
return id(soc_sensor).state > id(battery_soc_threshold);
then:
- if:
condition:
and:
- lambda: |-
return id(power_sensor).state > id(high_power_threshold);
- switch.is_off: relais
- not:
script.is_running: minimum_delay_with_relais_off
then:
- switch.turn_on: relais
- script.execute: minimum_delay_with_relais_on
- script.wait: minimum_delay_with_relais_on
- while:
condition:
not:
lambda: |-
return id(power_sensor).state < id(low_power_threshold);
then:
- delay: 1s
- if:
condition:
and:
- switch.is_on: relais
- not:
script.is_running: minimum_delay_with_relais_on
then:
- switch.turn_off: relais
- script.execute: minimum_delay_with_relais_off
- script.wait: minimum_delay_with_relais_off
script:
- id: minimum_delay_with_relais_on
then:
- delay: 40s
- id: minimum_delay_with_relais_off
then:
- delay: 200s
In order for the delay:
idle times to be observed, they must be implemented as 2 separate script:
directives, so that we can double check whether a script is done running before the main logic can proceed.
See also:
1 Like