Skip to content

Commit 0877e3d

Browse files
committed
2 parents 7e6350c + 08450b6 commit 0877e3d

6 files changed

Lines changed: 92 additions & 10 deletions

File tree

custom_components/solax_modbus/__init__.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class CoreModbusHub:
119119
REGISTER_ULSB16MSB16,
120120
REGISTER_WORDS,
121121
SCAN_GROUP_DEFAULT,
122+
SCAN_GROUP_MEDIUM,
123+
SCAN_GROUP_AUTO,
122124
# PLUGIN_PATH,
123125
SLEEPMODE_LASTAWAKE,
124126
CONF_TIME_OUT,
@@ -129,6 +131,8 @@ class CoreModbusHub:
129131
WRITE_MULTI_MODBUS,
130132
WRITE_SINGLE_MODBUS,
131133
WRITE_MULTISINGLE_MODBUS,
134+
REG_HOLDING,
135+
REG_INPUT,
132136
)
133137

134138
PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SELECT, Platform.SENSOR, Platform.SWITCH]
@@ -443,17 +447,32 @@ def loadLocalData(self):
443447

444448
# end of save and load section
445449

446-
def entity_group(self, sensor):
450+
def entity_group(self, sensor): # seems to be called for non-sensor entities also - strange
447451
# scan group
448452
g = getattr(sensor.entity_description, "scan_group", None)
449453
if not g:
450-
g = SCAN_GROUP_DEFAULT
454+
regtype = getattr(sensor.entity_description, "register_type", None)
455+
if regtype == REG_HOLDING: g = self.plugin.default_holding_scangroup
456+
elif regtype == REG_INPUT: g = self.plugin.default_input_scangroup
457+
else: g = SCAN_GROUP_DEFAULT # should not occur
458+
459+
if g == SCAN_GROUP_AUTO:
460+
unit = getattr(sensor.entity_description, "native_unit_of_measurement", None)
461+
if unit in ( # slow changing values
462+
UnitOfEnergy.WATT_HOUR,
463+
UnitOfEnergy.KILO_WATT_HOUR,
464+
UnitOfFrequency.HERTZ,
465+
UnitOfTemperature.CELSIUS,
466+
UnitOfTemperature.FAHRENHEIT,
467+
UnitOfTemperature.KELVIN,
468+
UnitOfTime.HOURS,
469+
): g = self.plugin.auto_slow_scangroup
470+
else: g = self.plugin.auto_default_scangroup
451471
# scan interval
452472
g = self.config.get(g, None)
453473
# when declared but not present in config, use default; this MUST exist
454474
if not g:
455475
g = self.config[SCAN_GROUP_DEFAULT]
456-
457476
return g
458477

459478
def device_group_key(self, device_info: DeviceInfo):
@@ -1123,8 +1142,8 @@ def rebuild_blocks(self, initial_groups): #, computedRegs):
11231142
hub_device_group.holdingBlocks = self.splitInBlocks(holdingRegs)
11241143
hub_device_group.inputBlocks = self.splitInBlocks(inputRegs)
11251144
#self.computedSensors = computedRegs # moved outside the loops
1126-
for i in hub_device_group.holdingBlocks: _LOGGER.info(f"{self._name} interval {interval} dev {device_name} returning holding block: 0x{i.start:x} 0x{i.end:x} {i.regs}")
1127-
for i in hub_device_group.inputBlocks: _LOGGER.info(f"{self._name} interval {interval} dev {device_name} returning input block: 0x{i.start:x} 0x{i.end:x} {i.regs}")
1145+
for i in hub_device_group.holdingBlocks: _LOGGER.info(f"{device_name} - interval {interval}s: adding holding block: {', '.join('0x{:x}'.format(num) for num in i.regs)}")
1146+
for i in hub_device_group.inputBlocks: _LOGGER.info(f"{device_name} - interval {interval}s: adding input block: {', '.join('0x{:x}'.format(num) for num in i.regs)}")
11281147
_LOGGER.debug(f"holdingBlocks: {hub_device_group.holdingBlocks}")
11291148
_LOGGER.debug(f"inputBlocks: {hub_device_group.inputBlocks}")
11301149
self.blocks_changed = False

custom_components/solax_modbus/const.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class UnitOfReactivePower(StrEnum):
9292
SCAN_GROUP_DEFAULT = CONF_SCAN_INTERVAL # default scan group, slow; should always work
9393
SCAN_GROUP_MEDIUM = CONF_SCAN_INTERVAL_MEDIUM # medium speed scanning (energy, temp, soc...)
9494
SCAN_GROUP_FAST = CONF_SCAN_INTERVAL_FAST # fast scanning (power,...)
95+
SCAN_GROUP_AUTO = "auto" # _MEDIUM for temperatures, frequency and energy (kWh), otherwise _DEFAULT
9596
CONF_TIME_OUT = "time_out"
9697
DEFAULT_TIME_OUT = 5
9798

@@ -151,6 +152,10 @@ class plugin_base:
151152
order16: int | None = None # Endian.BIG or Endian.LITTLE
152153
order32: int | None = None
153154
inverter_model: str = None
155+
default_holding_scangroup: str = SCAN_GROUP_DEFAULT
156+
default_input_scangroup: str = SCAN_GROUP_DEFAULT # or SCAN_GROUP_AUTO
157+
auto_default_scangroup: str = SCAN_GROUP_FAST, # only used when default_xxx_scangroup is set to SCAN_GROUP_AUTO
158+
auto_slow_scangroup: str = SCAN_GROUP_MEDIUM, # only usedwhen default_xxx_scangroup is set to SCAN_GROUP_AUTO
154159

155160
def isAwake(self, datadict):
156161
return True # always awake by default
@@ -194,7 +199,7 @@ class BaseModbusSensorEntityDescription(SensorEntityDescription):
194199
blacklist: list = None
195200
register: int = -1 # initialize with invalid register
196201
rounding: int = 1
197-
register_type: int = None # REGISTER_HOLDING or REGISTER_INPUT or REG_DATA
202+
register_type: int = None # REG_HOLDING or REG_INPUT or REG_DATA
198203
unit: int = None # e.g. REGISTER_U16
199204
scan_group: int = None # <=0 -> default group
200205
internal: bool = False # internal sensors are used for reading data only; used for computed, selects, etc

custom_components/solax_modbus/plugin_solax.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4594,6 +4594,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
45944594
0: "Disconnected",
45954595
1: "Connected",
45964596
},
4597+
scan_group = SCAN_GROUP_DEFAULT,
45974598
register_type=REG_INPUT,
45984599
allowedtypes=AC | HYBRID | GEN3 | GEN4 | GEN5 | GEN6,
45994600
icon="mdi:state-machine",
@@ -4655,14 +4656,15 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
46554656
state_class=SensorStateClass.MEASUREMENT,
46564657
register=0x1C,
46574658
register_type=REG_INPUT,
4658-
allowedtypes=AC | HYBRID | GEN2 | GEN3 | GEN4,
4659+
allowedtypes=AC | HYBRID | GEN2 | GEN3 | GEN4 ,
46594660
),
46604661
SolaXModbusSensorEntityDescription(
46614662
name="Battery 1 Capacity",
46624663
key="battery_1_capacity_charge",
46634664
native_unit_of_measurement=PERCENTAGE,
46644665
device_class=SensorDeviceClass.BATTERY,
46654666
register=0x1C,
4667+
scan_group = SCAN_GROUP_DEFAULT,
46664668
register_type=REG_INPUT,
46674669
allowedtypes=AC | HYBRID | GEN5 | GEN6,
46684670
),
@@ -4753,6 +4755,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
47534755
name="Battery Package Number",
47544756
key="battery_package_number",
47554757
register=0x22,
4758+
scan_group = SCAN_GROUP_DEFAULT,
47564759
register_type=REG_INPUT,
47574760
entity_registry_enabled_default=False,
47584761
allowedtypes=HYBRID | GEN2,
@@ -4762,6 +4765,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
47624765
key="battery_state_of_health",
47634766
icon="mdi:battery-heart",
47644767
register=0x23,
4768+
scan_group = SCAN_GROUP_DEFAULT,
47654769
register_type=REG_INPUT,
47664770
native_unit_of_measurement=PERCENTAGE,
47674771
entity_registry_enabled_default=False,
@@ -4786,6 +4790,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
47864790
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
47874791
entity_registry_enabled_default=False,
47884792
register=0x24,
4793+
scan_group = SCAN_GROUP_DEFAULT,
47894794
register_type=REG_INPUT,
47904795
scale=0.1,
47914796
rounding=1,
@@ -4798,6 +4803,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
47984803
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
47994804
entity_registry_enabled_default=False,
48004805
register=0x25,
4806+
scan_group = SCAN_GROUP_DEFAULT,
48014807
register_type=REG_INPUT,
48024808
scale=0.1,
48034809
rounding=1,
@@ -5042,6 +5048,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
50425048
1: "Unlocked",
50435049
2: "Unlocked - Advanced",
50445050
},
5051+
scan_group = SCAN_GROUP_DEFAULT,
50455052
register_type=REG_INPUT,
50465053
allowedtypes=AC | HYBRID,
50475054
internal=True,
@@ -5053,6 +5060,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
50535060
device_class=SensorDeviceClass.VOLTAGE,
50545061
entity_registry_enabled_default=False,
50555062
register=0x66,
5063+
scan_group = SCAN_GROUP_DEFAULT,
50565064
register_type=REG_INPUT,
50575065
scale=0.1,
50585066
rounding=1,
@@ -5065,6 +5073,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
50655073
device_class=SensorDeviceClass.VOLTAGE,
50665074
entity_registry_enabled_default=False,
50675075
register=0x67,
5076+
scan_group = SCAN_GROUP_DEFAULT,
50685077
register_type=REG_INPUT,
50695078
scale=0.1,
50705079
rounding=1,
@@ -5074,6 +5083,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
50745083
name="Overload Fault Val",
50755084
key="overload_fault_val",
50765085
register=0x68,
5086+
scan_group = SCAN_GROUP_DEFAULT,
50775087
register_type=REG_INPUT,
50785088
entity_registry_enabled_default=False,
50795089
allowedtypes=AC | HYBRID,
@@ -5085,6 +5095,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
50855095
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
50865096
device_class=SensorDeviceClass.VOLTAGE,
50875097
register=0x69,
5098+
scan_group = SCAN_GROUP_DEFAULT,
50885099
register_type=REG_INPUT,
50895100
scale=0.1,
50905101
rounding=1,
@@ -5247,6 +5258,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
52475258
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
52485259
device_class=SensorDeviceClass.VOLTAGE,
52495260
register=0x76,
5261+
scan_group = SCAN_GROUP_DEFAULT,
52505262
register_type=REG_INPUT,
52515263
scale=0.1,
52525264
rounding=1,
@@ -5258,6 +5270,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
52585270
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
52595271
device_class=SensorDeviceClass.CURRENT,
52605272
register=0x77,
5273+
scan_group = SCAN_GROUP_DEFAULT,
52615274
register_type=REG_INPUT,
52625275
scale=0.1,
52635276
rounding=1,
@@ -5327,6 +5340,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
53275340
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
53285341
device_class=SensorDeviceClass.VOLTAGE,
53295342
register=0x7E,
5343+
scan_group = SCAN_GROUP_DEFAULT,
53305344
register_type=REG_INPUT,
53315345
scale=0.1,
53325346
rounding=1,
@@ -5338,6 +5352,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
53385352
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
53395353
device_class=SensorDeviceClass.CURRENT,
53405354
register=0x7F,
5355+
scan_group = SCAN_GROUP_DEFAULT,
53415356
register_type=REG_INPUT,
53425357
scale=0.1,
53435358
rounding=1,
@@ -5358,6 +5373,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
53585373
key="eps_power_l3",
53595374
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
53605375
register=0x81,
5376+
scan_group = SCAN_GROUP_DEFAULT,
53615377
register_type=REG_INPUT,
53625378
allowedtypes=AC | HYBRID | X3 | EPS,
53635379
),
@@ -5568,6 +5584,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
55685584
SolaXModbusSensorEntityDescription(
55695585
key="grid_export_limit",
55705586
register=0x9C,
5587+
scan_group = SCAN_GROUP_DEFAULT,
55715588
register_type=REG_INPUT,
55725589
unit=REGISTER_S32,
55735590
allowedtypes=AC | HYBRID | GEN3,
@@ -5737,6 +5754,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
57375754
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
57385755
device_class=SensorDeviceClass.VOLTAGE,
57395756
register=0xBC,
5757+
scan_group = SCAN_GROUP_DEFAULT,
57405758
register_type=REG_INPUT,
57415759
scale=0.001,
57425760
rounding=3,
@@ -5749,6 +5767,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
57495767
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
57505768
device_class=SensorDeviceClass.VOLTAGE,
57515769
register=0xBD,
5770+
scan_group = SCAN_GROUP_DEFAULT,
57525771
register_type=REG_INPUT,
57535772
scale=0.001,
57545773
rounding=3,
@@ -5773,6 +5792,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
57735792
native_unit_of_measurement=PERCENTAGE,
57745793
state_class=SensorStateClass.MEASUREMENT,
57755794
register=0xBF,
5795+
scan_group = SCAN_GROUP_DEFAULT,
57765796
register_type=REG_INPUT,
57775797
entity_registry_enabled_default=False,
57785798
allowedtypes=AC | HYBRID | GEN4 | GEN5,
@@ -5795,6 +5815,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
57955815
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
57965816
device_class=SensorDeviceClass.VOLTAGE,
57975817
register=0xC9,
5818+
scan_group = SCAN_GROUP_DEFAULT,
57985819
register_type=REG_INPUT,
57995820
scale=0.1,
58005821
rounding=1,
@@ -5807,6 +5828,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
58075828
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
58085829
device_class=SensorDeviceClass.VOLTAGE,
58095830
register=0xCA,
5831+
scan_group = SCAN_GROUP_DEFAULT,
58105832
register_type=REG_INPUT,
58115833
scale=0.1,
58125834
rounding=1,
@@ -5819,6 +5841,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
58195841
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
58205842
device_class=SensorDeviceClass.VOLTAGE,
58215843
register=0xCB,
5844+
scan_group = SCAN_GROUP_DEFAULT,
58225845
register_type=REG_INPUT,
58235846
scale=0.1,
58245847
rounding=1,
@@ -5831,6 +5854,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
58315854
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
58325855
device_class=SensorDeviceClass.VOLTAGE,
58335856
register=0xCC,
5857+
scan_group = SCAN_GROUP_DEFAULT,
58345858
register_type=REG_INPUT,
58355859
scale=0.1,
58365860
rounding=1,
@@ -5956,6 +5980,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
59565980
device_class=SensorDeviceClass.REACTIVE_POWER,
59575981
state_class=SensorStateClass.MEASUREMENT,
59585982
register=0x10E,
5983+
scan_group = SCAN_GROUP_DEFAULT,
59595984
register_type=REG_INPUT,
59605985
unit=REGISTER_S32,
59615986
allowedtypes=AC | HYBRID | GEN4 | GEN5,
@@ -5967,6 +5992,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
59675992
device_class=SensorDeviceClass.REACTIVE_POWER,
59685993
state_class=SensorStateClass.MEASUREMENT,
59695994
register=0x110,
5995+
scan_group = SCAN_GROUP_DEFAULT,
59705996
register_type=REG_INPUT,
59715997
unit=REGISTER_S32,
59725998
allowedtypes=AC | HYBRID | GEN4 | GEN5,
@@ -6089,6 +6115,7 @@ def value_function_battery_voltage_cell_difference(initval, descr, datadict):
60896115
native_unit_of_measurement=PERCENTAGE,
60906116
device_class=SensorDeviceClass.BATTERY,
60916117
register=0x12E,
6118+
scan_group = SCAN_GROUP_DEFAULT,
60926119
register_type=REG_INPUT,
60936120
allowedtypes=HYBRID | GEN5,
60946121
),
@@ -8716,4 +8743,8 @@ def localDataCallback(self, hub):
87168743
order16=Endian.BIG,
87178744
order32=Endian.LITTLE,
87188745
auto_block_ignore_readerror=True,
8746+
default_holding_scangroup=SCAN_GROUP_MEDIUM,
8747+
default_input_scangroup=SCAN_GROUP_AUTO, # SCAN_GROUP_MEDIUM for slow changing units like temperature, kWh, ...
8748+
auto_default_scangroup=SCAN_GROUP_FAST,
8749+
auto_slow_scangroup=SCAN_GROUP_MEDIUM,
87198750
)

docs/developer_guide.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,32 @@ Documentation to be completed ...
9090

9191
## Scan groups for differentiated polling
9292
Some inverter plugins use different scan groups (to differentiate between slowly changing sensor entities and entities that are updated frequently?)
93-
To be documented ...
94-
93+
This is determined by the presence of attribute scan_group on sensor entities.
94+
The scan_group attribute can take following values: SCAN_GROUP_DEFAULT, SCAN_GROUP_MEDIUM, SCAN_GROUP_FAST or SCAN_GROUP_AUTO
95+
If no scan_group is specified, the plugin-specific default will be used. There can be a different default for holding and input registers. See the plugin declaration at the end of your plugin. Example:
96+
```
97+
plugin_instance = solax_plugin(
98+
plugin_name="SolaX",
99+
plugin_manufacturer="SolaX Power",
100+
SENSOR_TYPES=SENSOR_TYPES_MAIN,
101+
NUMBER_TYPES=NUMBER_TYPES,
102+
BUTTON_TYPES=BUTTON_TYPES,
103+
SELECT_TYPES=SELECT_TYPES,
104+
SWITCH_TYPES=[],
105+
block_size=100,
106+
order16=Endian.BIG,
107+
order32=Endian.LITTLE,
108+
auto_block_ignore_readerror=True,
109+
default_holding_scangroup=SCAN_GROUP_DEFAULT,
110+
default_input_scangroup=SCAN_GROUP_AUTO,
111+
auto_default_scangroup=SCAN_GROUP_FAST,
112+
auto_slow_scangroup=SCAN_GROUP_MEDIUM,
113+
)
114+
```
115+
The actual polling speed for each group is determined during initial or subsequent configuration of the intergration (config_flow).
116+
If the 3 polling group times are set to the same value, the system will act as if there is only one scangroup (they are merged together by interval time)
117+
118+
If SCAN_GROUP_AUTO is chosen for `default_input_scangroup` or `default_holding_scangroup`, entities without a `scan_group` declararation will get the value specified in `plugin.auto_slow_scangroup` if their native unit is slowly changing like temperatures or kWh .., otherwise the value specified in `plugin.auto_default_scangroup`
95119

96120

97121
## Autorepeat mechanism for buttons

docs/solax-mode1-modbus-power-control.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ The work described here uses mode 1 from the Solax KB document: [KB document : S
1919
A similar autorepeat mechanism is under development for mode 8
2020

2121
### Note:
22-
For people using external tasks to control the power, the _remotecontrol_xxx_direct_ versions of the entities could be used. This is outside the scope if this wiki page.
22+
- For people using external tasks to control the power, the _remotecontrol_xxx_direct_ versions of the entities could be used. This is outside the scope if this wiki page.
23+
- If you use modbus power control autorepeat loops, make sure the polling interval for the fastest scangroup is sufficiently low e.g. 3-5 seconds, otherwise the values are updated too slowly. A too short interval can overload your system or modbus communication bus. Some people can go as low as 1 second, but it is safer to have more margin.
2324

2425
***
2526
## Solax Gen4 approach - Mode 1
2627
***
2728

29+
2830
The Solax Gen4 inverters and higher use a modbus write_multiple_registers command.
2931
On the Gen4, these actions are not stored in EEPROM, so they can be executed frequently.
3032
The integration hides this complexity and implements one button to trigger a single or repeated update(s). The action behind this button is configurable through 6 parameter entities.

docs/solax-mode8-modbus-power-control.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ A similar autorepeat mechanism is available for mode 1; see the dedicated wiki p
2121
* For people using external tasks to control the power, the _remotecontrol_xxx_direct_ versions of the entities could be used. This is outside the scope if this wiki page.
2222
* Although the addition of Mode 8 in new firmwares is a big enhancement, we still feel there is a missing mode that focusses on the grid interface and can limit the PV in case of negative prices. Our Mode 8 autorepeat loop tries to emulate this, but can only adjust every polling cycle, a firmware based solution could react faster.
2323
* This mechanism will only work on the most recent firmware versions; previous versions do not support mode 8 or 9. I am running Firmware: DSP v1.52 ARM v1.50 at the time of my testing
24+
* If you use modbus power control autorepeat loops, make sure the polling interval for the fastest scangroup is sufficiently low e.g. 3-5 seconds, otherwise the values are updated too slowly. A too short interval can overload your system or modbus communication bus. Some people can go as low as 1 second, but it is safer to have more margin.
2425

2526

2627
### KNOWN ISSUES: (Work in progress):

0 commit comments

Comments
 (0)