How do I set multiple bits on modbus at once?

Suppose I want to have a switch which would be:

  • on in bit 1 on on hold register 20 and bits 1 and 229 are on on register 11 and 4 are on on register 229, and off otherwise

  • when turning it on, I want to set all the bits I mentioned previously, and no other bits (e.g.: read/set/write pattern)

  • on esphome load, I don’t want anything changed, e.g. on_turn_on/on_turn_off should not trigger without UI interaction

Here’s my initial definition, which I want to fix. During load, events trigger, which is already a no-go for me. Can someone suggest how?

  - platform: modbus_controller
    modbus_controller_id: modbus_conroller1
    name: Smart Load 1
    id: smartLoad1On
    icon: mdi:ev-station
    register_type: holding
    address: 20
    bitmask: 0x0001
    on_turn_off: 
      then:
        - switch.turn_off: smartLoad1GridAlwaysOn
    on_turn_on: 
      then:
        - switch.turn_on: smartLoad1GridAlwaysOn

  - platform: modbus_controller
    modbus_controller_id: modbus_conroller1
    name: Smart Load 1 Grid Always On
    id: smartLoad1GridAlwaysOn
    internal: true
    icon: mdi:ev-station
    register_type: holding
    address: 229
    bitmask: 0x0011

In general… Is there a recommended way to just read/write registers in lambda?

So what the switch state would be ON/OFF in binary or hex?
One register has only 16bits…

My bad. Read:

on in bit 1 on on hold register 20 and bits 1 and 4 are on on register 229, and off otherwise

Only those bits, so all others are 0?
ON>> register 20 is 0x0001 and register 229 is 0x0009?
OFF>> all zero?

Here’s what I ended up doing:

define internal sensors:

number:
  - platform: modbus_controller
    modbus_controller_id: modbus_conroller1
    id: smartLoadOnOff
    internal: true
    register_type: holding
    address: 20
    value_type: U_WORD
    min_value: 0x0000
    max_value: 0xFFFF
    step: 1
    on_value: 
      then:
        - lambda: sync_smart_loads();

  - platform: modbus_controller
    modbus_controller_id: modbus_conroller1
    id: smartLoadExtraSettings
    internal: true
    register_type: holding
    address: 229
    value_type: U_WORD
    min_value: 0x0000
    max_value: 0xFFFF
    step: 1
    on_value: 
      then:
        - lambda: sync_smart_loads();


switch:
  - platform: template
    name: Smart Load 1
    id: smartLoad1On
    icon: mdi:ev-station
    optimistic: True
    turn_on_action: 
      then:
        - lambda: |-
            set_bits(id(smartLoadOnOff), 0x0003 << 0, 0x0001 << 0);
            set_bits(id(smartLoadExtraSettings), 0x0011 << 0, 0x0011 << 0);
    turn_off_action:
      then:
        - lambda: |-
            set_bits(id(smartLoadOnOff), 0x0003 << 0, 0);
...

and code:

#pragma once

void sync_smart_loads() {
    if (id(smartLoadOnOff).has_state() && id(smartLoadExtraSettings).has_state()) {
        uint16_t oo = id(smartLoadOnOff).state;
        uint16_t es = id(smartLoadExtraSettings).state;
        ESP_LOGD("smart_loads", "smartLoadOnOff has state: 0x%04X, smartLoadExtraSettings: 0x%04X", oo, es);
        id(smartLoad1On).publish_state((oo & 0x0003) == 0x0001 && (es & 0x0011) == 0x0011);
...
    }
}

void set_bits(esphome::number::Number *num, uint16_t mask, uint16_t value) {
    if (id(smartLoadOnOff).has_state() && id(smartLoadExtraSettings).has_state()) {
        uint16_t state = num->state;
        uint16_t desired = state & ~mask | value;
        ESP_LOGD("smart_loads", "%s has state: 0x%04X, wanted: 0x%04X", num->get_name(), state, desired);
        if (state != desired) {
            ESP_LOGI("smart_loads", "Updating state of %s to: 0x%04X", num->get_name(), desired);
            auto call = num->make_call();
            call.set_value(desired);
            call.perform();
        }
    }
}