esphome / issues

Issue Tracker for ESPHome
https://esphome.io/
290 stars 34 forks source link

Modbus multiple Switch resets entire register #3857

Open nurachese opened 1 year ago

nurachese commented 1 year ago

The problem

Hi all, I'm new in ESPhome, please be patient with me :-) I have an issue in Modbus Switch behaviour: I have a 16 inputs & 16 outputs modbus board, I configured the code with 16 Modbus Binary Sensors (perfectly working) and 16 Modbus Switches reading and writing on a 16 bit holding register (0x70). Each activation of any switch activates the right output but at the same time resets all other 15. It seems that the library is not taking into account the outputs state before sending the command.

Thank you in advance

Marcello

Which version of ESPHome has the issue?

2022.11.3

What type of installation are you using?

Home Assistant Add-on

Which version of Home Assistant has the issue?

2022.11.4

What platform are you using?

ESP32

Board

WirelessTag WT32-ETH01

Component causing the issue

Modbus Controller Switch

Example YAML snippet

uart:
  id: modbusio
  tx_pin: GPIO17
  rx_pin: GPIO5
  baud_rate: 19200
  stop_bits: 1
modbus:
  id: modbus1

modbus_controller:    ## I simplified with only 2 inputs and 3 outputs
  - id: moduloio1
    address: 0x1
    modbus_id: modbus1
    setup_priority: -10
    update_interval: 100ms
binary_sensor:
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    name: "Pulsanti luci ingresso"
    id: Pulsanti_luci_ingresso
    register_type: holding
    address: 0xC0
    bitmask: 1
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    name: "Pulsanti luci soggiorno"
    id: Pulsanti_luci_soggiorno
    register_type: holding
    address: 0xC0
    bitmask: 2
switch:
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    name: "Luce pulsanti luci ingresso/soggiorno/cucina"
    id: Luce_pulsanti_luci_ingresso_soggiorno_cucina
    register_type: holding
    address: 0x70
    bitmask: 1
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    name: "Luce pulsante balcone soggiorno"
    id: Luce_pulsante_balcone_soggiorno
    register_type: holding
    address: 0x70
    bitmask: 2
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    name: "Luce pulsante balcone camere"
    id: Luce_pulsante_balcone_camere
    register_type: holding
    address: 0x70
    bitmask: 4

Anything in the logs that might be useful for us?

[15:08:05][V][modbus_controller:156]: 2 modbus commands already in queue
[15:08:05][V][modbus_controller:125]: Range : 70 Size: 1 (3) skip: 0
[15:08:05][W][modbus_controller:113]: Duplicate modbus command found: type=0x3 address=112 count=1
[15:08:05][V][modbus_controller:125]: Range : C0 Size: 1 (3) skip: 0
[15:08:05][V][modbus_controller:036]: Sending next modbus command to device 1 register 0x70 count 1
[15:08:05][V][modbus:194]: Modbus write: 01.06.00.70.00.02.09.D0 (8)

In this case I turned on the switch corresponding to bit #2, but since bit #8 was already on, the right command should be Modbus write: 01.06.00.70.00.0A+CRC, where 0xA=0x8+0x2
I tried to solve by using lambdas but no success

Additional information

No response

nurachese commented 1 year ago

Found a solution using @martgras modbus_number_write and template switches:

external_components:
  - source:
      type: git
      url: https://github.com/martgras/esphome
      #ref: modbus-fix
      #ref: modbus-duplicate
      #ref: modbus_controller
      ref: modbus_number_write
    components: [modbus_controller]

uart:
  id: modbusio
  tx_pin: GPIO17
  rx_pin: GPIO5
  baud_rate: 19200
  stop_bits: 1

modbus:
  #flow_control_pin: 5
  id: modbus1

modbus_controller:
  - id: moduloio1
    ## the Modbus device addr
    address: 0x1
    modbus_id: modbus1
    setup_priority: -10
    update_interval: 100ms

binary_sensor:
- platform: modbus_controller
  modbus_controller_id: moduloio1
  name: "Pulsanti luci ingresso"
  id: Pulsanti_luci_ingresso
  register_type: holding
  address: 0xC0
  bitmask: 1
  #response_size: 2
- platform: modbus_controller
  modbus_controller_id: moduloio1
  name: "Pulsanti luci soggiorno"
  id: Pulsanti_luci_soggiorno
  register_type: holding
  address: 0xC0
  bitmask: 2

number:
  - platform: modbus_controller
    modbus_controller_id: moduloio1
    id: Uscite_modulo1
    name: "Uscite modulo1"
    address: 0x70
    value_type: U_WORD
    lambda: "return  x * 1.0; "
    write_lambda: |-
      ESP_LOGD("main","Modbus Number incoming value = %f",x);
      uint16_t uscite1 = x ;
      payload.push_back(uscite1);
      return x * 1.0 ;
    ## multiply is ignored because lamdba is used
    #multiply: 1.0
switch:
  - platform: template
    name: "Luce pulsanti luci ingresso/soggiorno/cucina"
    id: Luce_pulsanti_luci_ingresso_soggiorno_cucina
    lambda: |-
      uint16_t UsciteM1 = id(Uscite_modulo1).state;
      if (bitRead(UsciteM1,0)) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state + 1;
    turn_off_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state - 1;
  - platform: template
    name: "Luce pulsante balcone soggiorno"
    id: Luce_pulsante_balcone_soggiorno
    lambda: |-
      uint16_t UsciteM1 = id(Uscite_modulo1).state;
      if (bitRead(UsciteM1,1)) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state + 2;
    turn_off_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state - 2;
  - platform: template
    name: "Luce pulsante balcone camere"
    id: Luce_pulsante_balcone_camere
    lambda: |-
      uint16_t UsciteM1 = id(Uscite_modulo1).state;
      if (bitRead(UsciteM1,2)) {
        return true;
      } else {
        return false;
      }
    turn_on_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state + 4;
    turn_off_action:
       - number.set:
          id: Uscite_modulo1
          value: !lambda |-
           return id(Uscite_modulo1).state - 4;
nurachese commented 1 year ago

I leave it open since the issue is still not solved.

github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

nurachese commented 1 year ago

Still not solved!

645340633 commented 1 year ago

Still not solved!

Hi,guys. have you solved it? I meet the same problem. I've searched for a long time ,but don't find any solution. Writing the codes is difficult for me. it makes me really crazy

645340633 commented 1 year ago

Look, how to implement registers like that in esphome with modbus controller? I would to read and write registers like 0x0032 and 0x0033, it is easy to read,but how to use switch component control the the 0x0032 and 0x0033 register, or write the registers correctly. f3b119927cb7d2f23083d4d61559335e2e9c2b47_2_690x416

tomatensaus commented 1 year ago

I also bumped into this problem, here is how I solved it. I used a modbus select as I wanted the interface to have some nice options to choose from

define a sensor to store the raw value to be used in the write lambda

  - platform: modbus_controller
    modbus_controller_id: ${modbus_controller}
    id: my_charge_raw
    register_type: holding
    address: 111
    value_type: U_WORD

Then I defined the select that would give the options to update the lower 2 bits of this register and retain the values

select:
  - platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: ${modbus_controller}
    name: "Charge option"
    id: my_charge_option
    address: 111
    entity_category: config
    value_type: U_WORD
    optionsmap:
      "None": 0
      "Grid": 1
      "Solar": 2
      "Both Grid/Solar": 3
    lambda: |-
      // we are only interested in the 2 bits binary 0011 need to map the options 00 , 01 , 10 , 11 in select 
      //ESP_LOGE("main","Modbus Number incoming value = %d",x);
      //ESP_LOGE("main","Modbus eval value = %d",(x & 0x0003));
      if ((x & 0x0003) == 0)
        return  std::string("None");
      if ((x & 0x0003) == 1)
        return  std::string("Grid");
      if ((x & 0x0003) == 2)
        return  std::string("Solar");
      if ((x & 0x0003) == 3)
        return  std::string("Both Grid/Solar");
      return {};
    write_lambda: |-
      //ESP_LOGE("main","Modbus write gets = %d",value);
      uint16_t unmodified =  id(my_charge_raw).state;
      //ESP_LOGE("main","Modbus write unmodified = %d", unmodified);
      // optionsmap should only return 3 values... 00 , 01 , 11   so bitmask with complement 0x0003 to ensure we keep the original values in register. then appply or with the value that was chosen
      uint16_t modified = ((unmodified & ~0x0003) | value);
      //ESP_LOGE("main","Modbus write to write = %d", modified);
      return modified;

I hope this snippet helps someone read/write the bits and keep the original register values

645340633 commented 1 year ago

got it , thank you

slipx06 commented 1 year ago

The above solution by @tomatensaus worked for me. Thank you for sharing

Alphaemef commented 10 months ago

I hope this snippet helps someone read/write the bits and keep the original register values

Thanks @tomatensaus super helpful and encouraging. I have two situations where bit mask really is paining me. And I have just slight issues with understanding your solution completely. You are using 0x0003 which is because you are manipulating bits 0 and 1 (for a two bit change). But what if im manipulating bits 4-5 in a 16 bit registry and want the rest to remain the same ? I realise of course the bit masks im adressen are 0x0030, but I have a hard time getting the read / write on it.

Screenshot 2023-11-01 at 21 45 15

slipx06 commented 10 months ago

You would need to look at the 4th and 5th bit so 0x0003 changes to 0x30

  - platform: modbus_controller
    modbus_controller_id: ${modbus_controller}
    id: my_charge_raw
    register_type: holding
    address: 178
    value_type: U_WORD
select:
  - platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: ${modbus_controller}
    name: "Grid Peak Shaving"
    id: esphome_select_grid_peak_shaving
    address: 178
    entity_category: config
    value_type: U_WORD
    optionsmap:
      "Disabled": 16
      "Enabled": 48
    lambda: |-
      if ((x & 0x30) == 16)
        return  std::string("Disabled");
      if ((x & 0x30) == 48)
        return  std::string("Enabled");
      return {};
    write_lambda: |-
      uint16_t unmodified =  id(my_charge_raw).state;
      uint16_t modified = ((unmodified & ~0x30) | value);
      return modified;
Alphaemef commented 10 months ago

4th and 5th should be 0x30 right ? (fourth = 10 and fifth = 20 ?)

Also, that code would report bits 0-5 correctly, but not 6-15 right ? Since the complementary operation only goes til 0x18 (or should be 0x30)

slipx06 commented 10 months ago

Sorry you are correct. I've updated the code. I was looking at the 3rd and 4th bit

Alphaemef commented 10 months ago

Sorry you are correct. I've updated the code. I was looking at the 3rd and 4th bit

Perfect man, I didn't realise the write lambda would write the entire 16 bits and not just the part up to 5 bits! Cheers!