To reduce the workload on my Solar Inverter I am trying to read multiple registers in a single transaction.
I can read 7 holding registers (0 - 6) that returns the value as a string of 14 numbers / letters that results in the serial number. ie A1A111A1111111 (Technical manual states these registers are Uint16)
If I try and read 3 holding registers (133 - 135) that represent the RTC seconds, minutes and hours using data_type: unit I get the error Unable to detect data type for Solax Group Test 3 sensor, try a custom type (Technical manual states these registers are Uint16)
If I use the following data_type: custom structure: ">3H" I only get the first register displayed as the sensor result.
I am expecting a similar result to reading the registers separately. 12 38 12 Which would be sec min hrs
The same happens when I read multiple input registers. (Technical manual states these registers are Uint16)
Input register 3 and 4 represent the PV Voltage.
Register 3 (256.2v)
Actual value returned 2562
Register 4 (260.1v)
Actual value returned 2601
But again I only ever see the first registers value when reading both together.
I am expecting to see for example 2526 2601 But I would only see 2526
arch | x86_64
-- | --
chassis | 聽
dev | false
docker | true
docker_version | 18.09.8
hassio | true
host_os | 聽
installation_type | Home Assistant Supervised
os_name | Linux
os_version | 4.4.59+
python_version | 3.8.5
supervisor | 247
timezone | Europe/London
version | 0.116.4
virtualenv | false
configuration.yamlWorking example where the returned registers just result in a string (Serial Number)
modbus:
name: SolaX
type: tcp
host: !secret inverter_ip
port: 502
sensor:
- platform: modbus
scan_interval: 2
registers:
- name: SolaX Serial Number
hub: SolaX
register: 0
count: 7
data_type: string
RTC Example:
sensor:
- platform: modbus
scan_interval: 2
registers:
- name: Solax Group Test 3
hub: SolaX
register: 133
count: 3
data_type: custom
structure: ">3H"
PV Voltage Example:
sensor:
- platform: modbus
scan_interval: 2
registers:
- name: SolaX Group Test
hub: SolaX
register: 3
register_type: input
count: 2
data_type: custom
structure: ">2H"
RTC Log for reading 3 registers
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Current transaction state - TRANSACTION_COMPLETE
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Running transaction 39
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] SEND: 0x0 0x27 0x0 0x0 0x0 0x6 0x0 0x3 0x0 0x85 0x0 0x3
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.client.sync] New Transaction state 'SENDING'
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] RECV: 0x0 0x27 0x0 0x0 0x0 0x9 0x0 0x3 0x6 0x0 0x28 0x0 0x18 0x0 0xc
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.framer.socket_framer] Processing: 0x0 0x27 0x0 0x0 0x0 0x9 0x0 0x3 0x6 0x0 0x28 0x0 0x18 0x0 0xc
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.factory] Factory Response[ReadHoldingRegistersResponse: 3]
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Adding transaction 39
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Getting transaction 39
2020-10-16 12:23:03 DEBUG (SyncWorker_17) [pymodbus.transaction] Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
2020-10-16 12:23:03 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.solax_group_test_3, old_state=<state sensor.solax_group_test_3=38; friendly_name=Solax Group Test 3 @ 2020-10-16T12:23:01.226637+01:00>, new_state=<state sensor.solax_group_test_3=40; friendly_name=Solax Group Test 3 @ 2020-10-16T12:23:03.225624+01:00>>
PV Volatage Example
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Current transaction state - TRANSACTION_COMPLETE
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Running transaction 45
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] SEND: 0x0 0x2d 0x0 0x0 0x0 0x6 0x0 0x4 0x0 0x3 0x0 0x2
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.client.sync] New Transaction state 'SENDING'
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] RECV: 0x0 0x2d 0x0 0x0 0x0 0x7 0x0 0x4 0x4 0xa 0x27 0xa 0x4e
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.framer.socket_framer] Processing: 0x0 0x2d 0x0 0x0 0x0 0x7 0x0 0x4 0x4 0xa 0x27 0xa 0x4e
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.factory] Factory Response[ReadInputRegistersResponse: 4]
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Adding transaction 45
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Getting transaction 45
2020-10-16 13:12:13 DEBUG (SyncWorker_2) [pymodbus.transaction] Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
2020-10-16 13:12:13 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=sensor.solax_group_test, old_state=<state sensor.solax_group_test=2572; friendly_name=SolaX Group Test @ 2020-10-16T13:12:11.033668+01:00>, new_state=<state sensor.solax_group_test=2599; friendly_name=SolaX Group Test @ 2020-10-16T13:12:13.034278+01:00>>
modbus documentation
modbus source
(message by IssueLinks)
Hey there @adamchengtkc, @janiversen, @vzahradnik, mind taking a look at this issue as its been labeled with an integration (modbus) you are listed as a codeowner for? Thanks!
(message by CodeOwnersMention)
I have noticed there is a PR to add data_count: to the climate section of ModBus
https://github.com/home-assistant/core/pull/32439
Does this work in the same way as the count: I am using, or is it a different approach?
If it's a different approach, could this be causing the issue I am experiencing where I can only see the value of the first register?
Does this work in the same way as the
count:?
Yes, I ported the code to Climate. Except the data_count attribute, there's no difference.
The same happens when I read multiple input registers. (Technical manual states these registers are Uint16)
Input register 3 and 4 represent the PV Voltage.
Register 3 (256.2v)
Actual value returned 2562
Register 4 (260.1v)
Actual value returned 2601But again I only ever see the first registers value when reading both together.
I am expecting to see for example 2526 2601 But I would only see 2526
I have tried adding:
reverse_order: true
As the documentation states:
reverse_order boolean (optional, default: false)
Reverse the order of registers when count >1.
But all that results in is you seeing 2601 instead of 2526, so it still only display the one register.
I'd like to see at the issue this Weekend. If I find something, I will let you know.
I know what's the problem. I'm not sure if it's a bug, but I will sum it up here.
DEFAULT_STRUCT_FORMAT = {
DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"},
DATA_TYPE_UINT: {1: "H", 2: "I", 4: "Q"},
DATA_TYPE_FLOAT: {1: "e", 2: "f", 4: "d"},
}
f...
count: 3
data_type: custom
structure: ">3H"
In your case, we correctly read 3 registers. Later, we use the struct definition >3H to parse the value.
Here's the relevant code:
byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers])
if self._data_type != DATA_TYPE_STRING:
val = struct.unpack(self._structure, byte_string)[0]
struct.unpack(...) method. Our code expects that the unpack method returns a single value. Note the [0]. It ignores the rest
Ideally, you should define your struct format so that it parses the bytes into a single value. Perhaps we could extend the code to check for the size of the array after unpack(...), but I'm not sure, how we should represent such data. We need to combine those 3 values into a single value, which can be stored internally.
@janiversen any suggestions?
The capability to read multiple registers at once and to assign them to different sensors would be a very nice and efficient solution. It would suits more to the Read Multiple Register from the Modbus protocol.
For example, I am driving with HA a De Dietrich boiler via a RTU Serial Modbus connections. Today HA is pooling 30 registers one by one. It is not optimized and creates limits in pooling capability. With a buffer read of 128 registers ( I think it is the maximum defined in the standard ), HA could make only 2 reads, populating an array. Even if the limit would be 10 to 16 registers it would be a great help.
For me personally it would be ideal if the value was returned comma separated.
The reason being is that not all registers return a 4 digit number.
If I read input registers 0-9 I get the following:
2438
20
435
1253
1250
4
4
4994
33
1
So Ideally when I use count: 10
My sensor would return 2438,20,435,1253,1250,4,4,4994,33,1
Then I can just split at the commas into separate sensors using a template.
I thought about splitting them after x numbers, but the likes of 20, 435, 4 & 4 in the above don't always stay 2, 3 & 1 digit long. They become 3, 4 or 2 long depending on the current / power generated.
So if I used the following:
solax_split_1:
friendly_name: "SolaX Split Sensor 1"
value_template: '{{ states.sensor.solax_group_test[0:4] }}'
solax_split_2:
friendly_name: "SolaX Split Sensor 2"
value_template: '{{ states.sensor.solax_group_test[4:6] }}'
solax_split_3:
friendly_name: "SolaX Split Sensor 3"
value_template: '{{ states.sensor.solax_group_test[6:9] }}'
I would sometimes be reading values from a different register altogether, depending on how lengths change over the day.
I think we could implement this. If the unpack() returns more than one number, we'll separate values by comma.
Today I'll prepare a pull request.
If you want me to try out the code change as a Custom Component of ModBus before you submit the PR just let me know.
That would be great! I will mention you in the PR, then you can grab the code.
@wills106 it's implemented in PR #42354. Feel free to grab the code. If you find any issues with the code, please report them in the PR. Thanks!
Most helpful comment
I think we could implement this. If the unpack() returns more than one number, we'll separate values by comma.
Today I'll prepare a pull request.