klatremis / esphome-for-deye

Esphome component for Deye 3 phase inverters for Home Assistant
129 stars 31 forks source link

Change bit 4 and 5 on address 178 #42

Closed Vali7719 closed 1 month ago

Vali7719 commented 1 month ago

Hi,

I would like to turn Peak shaving on and off. This is needed if you want to export to grid from batteries at night (only works if Grid peak shaving is disabled) and use Grid Peak shaving at day.

For that I need to change bits 4-5 on address 178 - Well actually only bit 5 will do since bit 4 is always 1.

Can you please provide me a code to do this. I tried like that but not sure what bitmask is doing (not a programmer just mechanical engineer).

Thank you and best regards, Bogdan

Vali7719 commented 1 month ago

I also found same question here https://community.home-assistant.io/t/modbus-two-bit-control-switch/633398 - also without answer.

luckylinux commented 1 month ago

I never tried in practice but I implemented in my modular Code

https://github.com/luckylinux/esphome-for-deye/blob/main/modules/advanced.yaml

The bitmask is always multiplied by 4 (2 bits left-shift), you can clearly see that if you write it in binary (and NOT hexadecimal) format, simply because each "setting" takes 2 bits, so each time you "move" left by 2 Bits (hence multiply by 4).

I'm NOT sure if it's enough to do a "Set" of the specific bit in question (e.g. bit 4) or if it's necessary to basically re-send the ENTIRE Register over RS485, otherwise either the Deye doesn't "register" the update or possibly even goes into Fault State. Did you try that (only sending a single Bit) ? Basically according to one of the code blocks in the File I linked ...

Otherwise you can try to have a look at the notes at the End of that File where there are some Links (last link is the most promising I found at the time using lambda Functions to basically read whole register + split + modify + re-send the whole Thing).

Let me know if/what works.

Vali7719 commented 1 month ago

Hi, thank you for response.

I'm really a bad for code so this is not simple for me.

binary_sensor: - if I need to set bit and want to use a switch to change/enable this option need I put code into section switch: ?

I tried like this and it did not set the option.

switch:

luckylinux commented 1 month ago

Have a look at https://github.com/luckylinux/esphome-for-deye/blob/main/modules/grid.yaml

Most of the ESPHome I learned through Copy Paste, so I'm not really an Expert ...

You might need to:

Did you re-build the code and upload after you changed it ? try esphome clean before esphome run to see if there was some residual stuff that prevented it from changing

https://esphome.io/components/switch/modbus_controller.html

From https://esphome.io/components/modbus_controller#bitmasks

It looks like bitmask is only for reading :disappointed:. So you would probably need to do write_lambda and most likely you'll need 2 sensors or 1 sensor + 1 global variable.

Basically (just brainstorming, I didn't do it myself yet for this Project as you saw):

  1. Read the entire Thing into a sensor/variable, e.g. MyRegisterXXXOriginalValue with Value 0xZZZZZZZZZZ
  2. Force the particular bit (e.g. bit 4) of that variable to 0 or 1 (e.g. for bit 4: MyRegisterXXXAlteredValue0xZZZZZ0ZZZZ`
  3. With the newly updated "Combined" Value, write that to the Register 178

Point 1 is quite easy: you just read the entire thing, without bitmask, into a sensor. Or, alternatively, you read it from the main loop into a global variable.

Point 2 will require some bitmask operators (| 0x0000000010000 to force 1 and & 0x1111101111 to force 0 at specific positions)

Point 3 will write to the register.

That's actually what is done here, it actually seems quite neat and compact: https://github.com/esphome/issues/issues/3857#issuecomment-1591016833

Look at the write_lambda.

I do NOT have however an easy way if you want to "scale it up" to several bits/registers (without doing copy paste of course). You can probably pass a dynamic argument to write_lambda I guess ...

luckylinux commented 1 month ago

@Vali7719

Please use Code Blocks for Proper formatting (triple backticks start, code, triple backticks end).

If I were to take a blind Stab ...

number:
    platform: modbus_controller
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 2
    name: ${entities_name_prefix}- Advanced Register 178
    id: ${entities_id_prefix}_Advanced_Register_178
    register_type: holding
    value_type: U_WORD
    internal: true
    address: 178

switch:
    platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 2
    name: ${entities_name_prefix}- Advanced - Grid Peak Shaving Enable # bit 4 & 5
    id: ${entities_id_prefix}_Advanced_Grid_Peak_Shaving_Enable
    register_type: holding
    address: 178
    # Bitmask is ONLY for reading !!!
    # See https://esphome.io/components/modbus_controller#bitmasks
    bitmask: 0x10 # bit 4 is variable (0 = disabled , 1 = enabled)
                  # bit 5 is fixed = 1 for both disabled and enabled
                  # Use bitmask = 0x10

    write_lambda: |-
      //ESP_LOGE("main","Modbus write gets = %d" , value);

      // For a switch I would expect value to be either 0 (disabled) or 1 (enabled)

      uint16_t current_value =  id(${entities_id_prefix}_Advanced_Register_178).state;

      //ESP_LOGE("main","Modbus: current Register 178 Value (unmodified) = %d", current_value);

      // Declare Variable
      uint16_t write_value = 0;

      // Change State of the Bit that we want (bit 4)
      if(int(value) == 0)
      {
            // Force bit 4 to "0" 
            // Mask 0x10 means only bit 4 set to 1, so do a NOT [Invert] Operation (`~`) on it to obtains 1 everywhere else and zero in the 4-bit position
            write_value = current_value & ~0x10;
      }
      else
      {
            // Force bit 4 to "1"
            write_value = current_value | 0x10;
      }

      //ESP_LOGE("main","Modbus: write %d to Register 178 (Modified)", write_value);
      return write_value;

    entity_category: config
    icon: "mdi:toggle-switch"

Important: I didn't test this at all, just tried to adapt the other Example. If you need help, please post some logs where you can see these messages (ESP_LOGE).

You might need to set Log to DEBUG, although most likely INFO (the Default) might already give us these information.

Just to make sure that 0 and 1 are NOT inverted for whatever reason.

EDIT 1: of course remove the // for the ESP_LOGE lines if you want/need to Debug.

Vali7719 commented 1 month ago

I got some errors but I modified code with CGPT

substitutions:
  entities_name_prefix: "Inverter"
  entities_id_prefix: "inverter"
switch:
  - platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 2
    name: ${entities_name_prefix} - Advanced - Grid Peak Shaving Enable
    id: ${entities_id_prefix}_Advanced_Grid_Peak_Shaving_Enable
    register_type: holding
    address: 178
    bitmask: 0x10

    write_lambda: |-
      uint16_t current_value = id(${entities_id_prefix}_Advanced_Register_178).state;
      uint16_t write_value = 0;

      if (x == 0) {
          write_value = current_value & ~0x10;  // Set bit 4 to 0
      } else {
          write_value = current_value | 0x10;   // Set bit 4 to 1
      }

      ESP_LOGE("main", "Modified Register 178 Value (write) = %d", write_value);

      return write_value;

    entity_category: config
    icon: "mdi:toggle-switch"

number:
  - platform: modbus_controller
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 2
    name: "${entities_name_prefix} - Advanced Register 178"
    id: "${entities_id_prefix}_Advanced_Register_178"
    register_type: holding
    value_type: U_WORD
    internal: true
    address: 178

So with that I don't get error any more.

When I flip the switch in Home-assistant I can see this.

[E][main:182]: Modified Register 178 Value (write) = 10938

So this sets bit 4 to 1 - 10101010111010 - 10938 - 0x10 bit 4

But after some seconds Switch in Home-assistant switches back.

Also on Deye display I don't see status change.

luckylinux commented 1 month ago

Well, you also removed all the other logging lines (and comments).

Basically the error seemed to be we needed to change value to x.

You need to try and show the logs when you toggle from "both states" (enabled -> disable, disabled -> enable). And since it seems to NOT be stored on the Deye, you'll probably need to do this quite quickly.

The only thing I could think of is: a. that the ${entities_id_prefix}_Advanced_Register_178 is actually ALSO doing a "write" to the Register b. write_lambda is NOT correctly doing a write

Point a. I thought would NOT occur, since I explicitly set "number" for that reason (instead of say a "slider" or similar, which should also do write Operations).

Can you see anything if you show the History for ${entities_id_prefix}_Advanced_Register_178 ? Do you see it changing for a few seconds then back at all ? You will need to zoom quite a bit ...

Let's try to force read instead of holding according to https://esphome.io/components/sensor/modbus_controller.html (holding doesn't appear to be valid for a "number" Type, not sure why it doesn't complain actually):

number:
  - platform: modbus_controller
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 2
    name: "${entities_name_prefix} - Advanced Register 178"
    id: "${entities_id_prefix}_Advanced_Register_178"
    register_type: read
    value_type: U_WORD
    internal: true
    address: 178
Vali7719 commented 1 month ago

Unknown value 'read', valid options are 'custom', 'coil', 'holding'.

luckylinux commented 1 month ago

Uhm weird ...

Try with more logging and show the History Graph for the variable that I asked for.

Also show the Graph for the Switch at the same Time.

Vali7719 commented 1 month ago

[07:01:52][D][switch:012]: 'Inverter - Advanced - Grid Peak Shaving Enable' Turning ON. [07:01:52][E][main:182]: Modified Register 178 Value (write) = 10938 [07:01:52][D][switch:055]: 'Inverter - Advanced - Grid Peak Shaving Enable': Sending state ON [07:01:54][D][switch:016]: 'Inverter - Advanced - Grid Peak Shaving Enable' Turning OFF. [07:01:54][E][main:182]: Modified Register 178 Value (write) = 10922

luckylinux commented 1 month ago

Otherwise try with sensor instead of number and there you should be able to use register_type: read

luckylinux commented 1 month ago

So

10938 = 0b10101010111010

10922 = 0b10101010101010

Indeed it's bit 4 that it's flipping, so at least the code is correct.

Now to see why it's not writing correctly (or storing) ....

Vali7719 commented 1 month ago

with sensor I get this:

[07:06:30][D][switch:012]: 'Inverter - Advanced - Grid Peak Shaving Enable' Turning ON. [07:06:30][E][main:182]: Modified Register 178 Value (write) = 65535 [07:06:30][D][switch:055]: 'Inverter - Advanced - Grid Peak Shaving Enable': Sending state ON [07:06:32][D][switch:016]: 'Inverter - Advanced - Grid Peak Shaving Enable' Turning OFF. [07:06:32][E][main:182]: Modified Register 178 Value (write) = 65519

Vali7719 commented 1 month ago

I will post my code to local solar forum so some other Deye owners can try it. Today I cannot resume testing but I will get back if I or somebody else find some solution. Thank you!

luckylinux commented 1 month ago

So

10938 = 0b10101010111010 10922 = 0b10101010101010 65535 = 0b1111111111111111 65519 = 0b1111111111101111

So yeah, still bit 4 changing, but why is everything else 1 ?

luckylinux commented 1 month ago

Alright, not sure what to think to be honest right now ...

Probably sensoris the correct one (number is the one that actually users can modify, forgot that, https://esphome.io/components/number/index.html#number-component and https://developers.home-assistant.io/docs/core/entity/number/#properties, sorry about that), but I wonder why is everything set to 1 now ...

You could try to use S_WORD instead of U_WORD if for whatever reason they encode it differently. Although I'd say in this case it should probably not matter (and the 1010 etc pattern in number seemed more correct to me).

luckylinux commented 1 month ago

Just to be clear switching from number to sensor seems to have "added" 2 bits (multiplied the value by 4 / shifted left by 2 bits). Plus of course all the other bits being changed to 1 ...

Can you try just "logging" data and clicking on several Options in the Deye HMI, and track what changes and associated with when you change what (disabled -> enabled for Option XX, enabled -> disabled for Option XX, etc) ?

I'm curious if sensor is not reading anything at all correctly at this point ...

Vali7719 commented 1 month ago

I tried to enable Grid peak shaving on mobile app from inverter and if there is enabled then switch in my Home-assistant is also flipped and stays - but I cannot turn it off from HA. So it works as an indicator.

luckylinux commented 1 month ago

Yeah but what does the Value in ${entities_id_prefix}_Advanced_Register_178 show ? Again all 1s ?

Vali7719 commented 1 month ago

How can I display values from register?

luckylinux commented 1 month ago

Just look in Home Assistant under Entity -> Click -> you get a Chart -> Possibly "Show More" if it doesn't want to refresh or you want to change the Time Range

luckylinux commented 1 month ago

Wait another thing ...

The Deye Modbus register might be really confusing ... Chinese read right to left don't they ?

image

So if they say Bit 0-1 : 10: Disable, is it bit 1 (not 0) that we need to set ?

Similar for Bit 4-5: 10:Grid peak-shaving disable, is it bit 5 (not 4) that we need to set ?

My comment is mostly about their ordering of the Bit (0-1, 4-5) and if that implies that the values on the right are reversed ... And Usually we (North-Americans, Europeans, etc) write bit 5-4-3-2-1-0 when listing (as a general Rule).

Strange that the reading mask is correct though ...

For write_lambda, try to replace BOTH (including the one with the ~ NOT Operator) 0x10 with 0x20.

Vali7719 commented 1 month ago

OK

Interesting - I cannot find Sensor - Advanced_Register_178 in HO.

Vali7719 commented 1 month ago

slika

luckylinux commented 1 month ago

Ah yeah, my fault: remove internal: true. I didn't want you to "tamper" with the RAW value, but you need to disable internal: true even if you want to look at it (apparently).

Vali7719 commented 1 month ago

State is unknown. slika

luckylinux commented 1 month ago

I'm giving it a try on my side, but I am OFF Grid, so I can only test if it actually changes something on the Deye HMI ...

luckylinux commented 1 month ago

I tried to basically swap the bits chinese-style. Not sure if the Documentation is right or wrong, but I thought I'd give it a try (basically multiply all bitmasks by 2 in DECIMAL value).

Vali7719 commented 1 month ago

I need to go now - I will resume tomorrow - family day today. If you find something please post it. Thank you for your efforts. Best regards,

luckylinux commented 1 month ago

OK actually you need holding and not read. If you use read it will always be unknown. With holding and sensor (NOT number) I have some value in the 10000 Range which is where we should be.

As for the Reversed bits I'll need to head to the Garage and test. But it's actually not writing, so maybe return in write_lambda doesn't work as expected.

luckylinux commented 1 month ago

I asked on the ESPHome Discord Channel, maybe they have some Ideas ...

luckylinux commented 1 month ago

Apparently write_lambda return expects boolean (true/false) value, saw this when trying to convert to float using return write_value*1.0 and the Compiler error out.

That explains why it wasn't doing anything (any value != 0 is "true").

luckylinux commented 1 month ago

@Vali7719 I think I can see the Issue. First of all ESPHome Documentation is VERY cryptic on the Switch Modbus Controller write_lambda.

Indeed what the other user did was a Select Modbus Controller. And the Documentation there is much better (you basically send 16 RAW Bits).

So I guess if you are fine with Select instead of Switch, we should be able to get something fairly Easily.

luckylinux commented 1 month ago

OK so either you switch to using a Select according to the example in the esphome GitHub Issue I linked or, apparently, you can use bitmask ...

I now inverted bit 0-1 , 2-3, etc and something seems to be going on.

I'll check on the Inverter HMI to see what is going on actually

https://github.com/luckylinux/esphome-for-deye/blob/main/modules/advanced_readwrite_switch_bitmask.yaml

https://github.com/luckylinux/esphome-for-deye/commit/fa559134287ca9c54b4ce9f02d932209c0a164a4

luckylinux commented 1 month ago

@Vali7719

image

I just managed to get it working (tested Grid Peak Shaving, Gen Peak Shaving, On Grid Always On, BMS_Err_Stop):

https://github.com/luckylinux/esphome-for-deye/blob/main/modules/advanced_readwrite_select.yaml

A few Caveats though ... a. It's using Select instead of Switch. Switch behaves RADICALLY different and you have to configure the Payload by Hand (also uint8_t instead of uint16_t) b. Do NOT change multiple Settings "too quickly" otherwise you might "miss" one change c. You might need to adjust skip_updates: 10 to something lower. Right now there is QUITE A BIT of Delay

Anyways you seem to only be interested in bit 4/5 so ...


  - platform: modbus_controller
    use_write_multiple: true
    modbus_controller_id: ${modbus_controller_id}
    skip_updates: 10
    name: "${entities_name_prefix}-Advanced - Grid Peak Shaving Enable" # bit 4 & 5
    id: "${entities_id_prefix}_Advanced_Grid_Peak_Shaving_Enable"
    #register_type: holding
    address: 178

    optionsmap:
      "Disabled": 0
      "Enabled": 1

    # Deye Documentation ... TO_BE_COMPLETED
    # bit 4 is variable (0 = disabled , 1 = enabled)
    # bit 5 is fixed = 1 for both disabled and enabled
    # Use bitmask = 0x10

    lambda: |-
      // Convert Register Value to Integer
      uint16_t register_complete = uint16_t(x);

      // Log Whole Modbus Register Contents
      ESP_LOGI("main","Modbus: Register Read - Overall Value = %d",register_complete);

      // Apply bitmask to extract the Value we are Actually Interested in
      uint16_t setting_value = (register_complete & 0x10) >> 4;

      // Log Setting Value
      ESP_LOGI("main","Modbus: Register Decode - Advanced_Grid_Peak_Shaving_Enable = %d",setting_value);

      if (setting_value == 0)
        return  std::string("Disabled");
      //if (setting_value == 1)
      else
        return  std::string("Enabled");
      return {};

    write_lambda: |-
      // Convert Select Value to Integer
      uint16_t select_value = uint16_t(value);

      // Debug
      ESP_LOGI("main","Modbus: Write - Advanced_Grid_Peak_Shaving_Enable set to %d" , select_value);

      // Current Value of the Register (all bits)
      uint16_t current_value = id(${entities_id_prefix}_Advanced_Register_178).state;

      // Debug
      ESP_LOGI("main","Modbus: Write - Previous Register 178 Value (unmodified) = %d", current_value);

      // Declare Variable
      uint16_t write_value = 0;

      // Define new Combined (all bits) value for the Register
      ESP_LOGI("main","Modbus: apply Bitmask on Bit %d" , 5);
      if (select_value == 0) {
          write_value = current_value & ~0x10;  // Set bit 5 to 0
      } else {
          write_value = current_value | 0x10;   // Set bit 5 to 1
      }

      // Log Value to be written to the Register
      ESP_LOGI("main", "Modbus: Write - Updated Register 178 Value (write) = %d", write_value);

      // Return Result
      return write_value;

    entity_category: config
    icon: "mdi:toggle-switch"

Funnily enough the Deye Docs Left/Right order of bits seems wrong, as this is the file I tested against the Deye HMI and the Settings seem to be reflected with this Code.

Give it a try and let me know !

If you want to disable logging, just add a comment at the beginning of each line (//). I suggest you do NOT delete what you don't understand (otherwise you're on your own later :wink:).

Vali7719 commented 1 month ago

OMG - Thank you - it works!!!!

Thank you!

Best regards from Slovenia, Bogdan

luckylinux commented 1 month ago

Glad to have been of help :smile: !

If you really need a "Switch", you can probably adapt quite a bit and configure the payload manually. But I couldn't figure an "easy" way to do it.

Other Options could involve into using local-only Entities/Switches + Global Variables and "build" your Overall Register into a number Entity, then submitting that over Modbus. But then YOU will always overwrite the Inverter Settings (e.g. changing via HMI or APP no longer Possible).

My suggestion, if you can live with the select, is to keep it that way :laughing:.

EDIT: Note that the comments about bit 4/5 are NOT always consistent (Code appears to work, so it's safe to assume that the Deye Documentation was wrong). I'll try to clean up a bit the Comments when I update the Code next Time ...