Change bit 4 and 5 on address 178
Closed this issue ยท 37 comments
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).
- platform: modbus_controller
use_write_multiple: true
modbus_controller_id: ${modbus_controller_id}
name: ${device_type}-Grid peak shaving
register_type: holding
address: 178
bitmask: 5
entity_category: config
icon: "mdi:toggle-switch"
Thank you and best regards,
Bogdan
I also found same question here
https://community.home-assistant.io/t/modbus-two-bit-control-switch/633398 - also without answer.
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.
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:
- 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: 0x10 # bit 4 is variable (0 = disabled , 1 = enabled)
# bit 5 is fixed = 1 for both disabled and enabled
# Maybe we can just set bit 4 and not touch bit 5 ?
# Use bitmask = 0x1
entity_category: config
icon: "mdi:toggle-switch"
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:
- Add:
use_write_multiple
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 ๐. 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):
- Read the entire Thing into a sensor/variable, e.g.
MyRegisterXXXOriginalValue
with Value0xZZZZZZZZZZ
- Force the particular bit (e.g. bit 4) of that variable to 0 or 1 (e.g. for bit 4:
MyRegisterXXXAlteredValue
0xZZZZZ0ZZZZ` - 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:
esphome/issues#3857 (comment)
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 ...
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.
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.
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
Unknown value 'read', valid options are 'custom', 'coil', 'holding'.
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.
[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
Otherwise try with sensor
instead of number
and there you should be able to use register_type: read
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) ....
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
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!
So
10938 = 0b10101010111010
10922 = 0b10101010101010
65535 = 0b1111111111111111
65519 = 0b1111111111101111
So yeah, still bit 4 changing, but why is everything else 1 ?
Alright, not sure what to think to be honest right now ...
Probably sensor
is 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).
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 ...
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.
Yeah but what does the Value in ${entities_id_prefix}_Advanced_Register_178
show ?
Again all 1s ?
How can I display values from register?
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
Wait another thing ...
The Deye Modbus register might be really confusing ... Chinese read right to left don't they ?
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
.
OK
Interesting - I cannot find Sensor - Advanced_Register_178 in HO.
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).
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 ...
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).
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,
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.
I asked on the ESPHome Discord Channel, maybe they have some Ideas ...
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").
@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.
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
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 ๐).
OMG - Thank you - it works!!!!
Thank you!
Best regards from Slovenia,
Bogdan
Glad to have been of help ๐ !
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 ๐.
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 ...