Skip to content

Commit 44d1ed0

Browse files
authored
Merge pull request #1468 from infradom/main
Enhanded button autorepeat mechanism; Solax mode 8 power control
2 parents 5d6f037 + dd3df0e commit 44d1ed0

10 files changed

Lines changed: 940 additions & 55 deletions

custom_components/solax_modbus/__init__.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ class CoreModbusHub:
118118
SLEEPMODE_LASTAWAKE,
119119
CONF_TIME_OUT,
120120
DEFAULT_TIME_OUT,
121+
BUTTONREPEAT_FIRST,
122+
BUTTONREPEAT_LOOP,
123+
BUTTONREPEAT_POST,
124+
WRITE_MULTI_MODBUS,
125+
WRITE_SINGLE_MODBUS,
126+
WRITE_MULTISINGLE_MODBUS,
121127
)
122128

123129
PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SELECT, Platform.SENSOR, Platform.SWITCH]
@@ -392,7 +398,9 @@ def loadLocalData(self):
392398
else:
393399
if loaded.get("_version") == self.DATAFORMAT_VERSION:
394400
for desc in self.writeLocals:
395-
self.data[desc] = loaded.get(desc)
401+
val = loaded.get(desc)
402+
if val != None: self.data[desc] = val
403+
else: self.data[desc] = self.writeLocals[desc].initvalue # first time initialisation
396404
else:
397405
_LOGGER.warning(f"local persistent data lost - please reinitialize {self.writeLocals.keys()}")
398406
fp.close()
@@ -719,13 +727,13 @@ async def async_read_modbus_data(self, group):
719727
try:
720728
res = await self.async_read_modbus_registers_all(group)
721729
except ConnectionException as ex:
722-
_LOGGER.error("Reading data failed! Inverter is offline.")
730+
_LOGGER.error(f"Reading data failed! Inverter is offline. {ex}")
723731
res = False
724732
except ModbusIOException as ex:
725733
_LOGGER.error(f"ModbusIOError: {ex}")
726734
res = False
727735
except Exception as ex:
728-
_LOGGER.exception("Something went wrong reading from modbus")
736+
_LOGGER.exception(f"Something went wrong reading from modbus: {ex}")
729737
res = False
730738
return res
731739

@@ -910,7 +918,8 @@ async def async_read_modbus_registers_all(self, group):
910918
else:
911919
_LOGGER.debug(f"device group inverter")
912920

913-
data = {"_repeatUntil": self.data["_repeatUntil"]}
921+
data = {"_repeatUntil": self.data["_repeatUntil"]} # remove for issue #1440 but then does not recognize comm errors
922+
#data = self.data # add for issue #1440 - is an alias, not a copy - but then does not recognize communication errors anymore
914923
res = True
915924
for block in group.holdingBlocks:
916925
res = res and await self.async_read_modbus_block(data, block, "holding")
@@ -931,8 +940,8 @@ async def async_read_modbus_registers_all(self, group):
931940
_LOGGER.warning(f"device group check not success")
932941
return True
933942

934-
for key, value in data.items():
935-
self.data[key] = value
943+
for key, value in data.items(): # remove for issue #1440, but then does not recognize communication errors anymore
944+
self.data[key] = value # remove for issue #1440, but then comm errors are not detected
936945

937946
if res and self.writequeue and self.plugin.isAwake(self.data): # self.awakeplugin(self.data):
938947
# process outstanding write requests
@@ -941,20 +950,43 @@ async def async_read_modbus_registers_all(self, group):
941950
val = self.writequeue.get(addr)
942951
await self.async_write_register(self._modbus_addr, addr, val)
943952
self.writequeue = {} # make sure we do not write multiple times
953+
954+
# execute autorepeat buttons
944955
self.last_ts = time()
945956
for (
946957
k,
947958
v,
948-
) in self.data["_repeatUntil"].items():
959+
) in list(self.data["_repeatUntil"].items()): # use a list copy because dict may change during iteration
960+
buttondescr = self.computedButtons[k]
949961
if self.last_ts < v:
950-
buttondescr = self.computedButtons[k]
951-
payload = buttondescr.value_function(0, buttondescr, self.data)
952-
_LOGGER.debug(f"ready to repeat button {k} data: {payload}")
953-
await self.async_write_registers_multi(
954-
unit=self._modbus_addr,
955-
address=buttondescr.register,
956-
payload=payload,
957-
)
962+
payload = buttondescr.value_function(BUTTONREPEAT_LOOP, buttondescr, self.data) # initval = 1 means autorepeat run
963+
if payload:
964+
reg = payload.get("register", buttondescr.register)
965+
action = payload.get("action")
966+
if not action: __LOGGER.error(f"autorepeat value function for {k} must return dict containing action")
967+
else:
968+
if action == WRITE_MULTI_MODBUS:
969+
_LOGGER.debug(f"**debug** ready to repeat button {k} data: {payload}")
970+
await self.async_write_registers_multi(
971+
unit=self._modbus_addr,
972+
address=reg,
973+
payload=payload.get('data'),
974+
)
975+
else: # expired autorepeats
976+
if self.data["_repeatUntil"][k] > 0: # expired recently
977+
self.data["_repeatUntil"][k] = 0 # mark as finally expired, no further buttonrepeat post after this one
978+
_LOGGER.info(f"calling final value function POST for {k} with initval {BUTTONREPEAT_POST}")
979+
payload = buttondescr.value_function(BUTTONREPEAT_POST, buttondescr, self.data) # None means no final call after expiration
980+
if payload:
981+
reg = payload.get("register", buttondescr.register)
982+
action = payload.get("action")
983+
if action == WRITE_MULTI_MODBUS:
984+
_LOGGER.info(f"terminating loop {k} - ready to send final payload data: {payload}")
985+
await self.async_write_registers_multi(
986+
unit=self._modbus_addr,
987+
address=reg,
988+
payload=payload.get('data'),
989+
)
958990
return res
959991

960992

custom_components/solax_modbus/button.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .const import ATTR_MANUFACTURER, DOMAIN, CONF_MODBUS_ADDR, DEFAULT_MODBUS_ADDR
22
from .const import WRITE_DATA_LOCAL, WRITE_MULTISINGLE_MODBUS, WRITE_SINGLE_MODBUS, WRITE_MULTI_MODBUS
3-
from .const import autorepeat_set
3+
from .const import autorepeat_set, BUTTONREPEAT_FIRST
44
from homeassistant.components.button import PLATFORM_SCHEMA, ButtonEntity
55
from homeassistant.const import CONF_NAME
66
from homeassistant.core import callback
@@ -85,9 +85,16 @@ async def async_press(self) -> None:
8585
duration = self._hub.data.get(self.button_info.autorepeat, 0)
8686
autorepeat_set(self._hub.data, self.button_info.key, time() + duration - 0.5 )
8787
if self.button_info.value_function:
88-
res = self.button_info.value_function(0, self.button_info, self._hub.data )
88+
res = self.button_info.value_function(BUTTONREPEAT_FIRST, self.button_info, self._hub.data ) # initval = 0 means first manual run
8989
if res:
90+
if self.button_info.autorepeat: # different return value structure for autorepeat value function
91+
reg = res.get('register', self._register)
92+
data = res.get('data', None)
93+
action = res.get('action')
94+
if not action: _LOGGER.error(f"autorepeat value function for {self._key} must return dict containing action")
95+
else: data = res
9096
_LOGGER.info(f"writing {self._platform_name} button register {self._register} value {res}")
91-
await self._hub.async_write_registers_multi(
92-
unit=self._modbus_addr, address=self._register, payload=res
93-
)
97+
if action == WRITE_MULTI_MODBUS:
98+
await self._hub.async_write_registers_multi(
99+
unit=self._modbus_addr, address=reg, payload=data
100+
)

custom_components/solax_modbus/const.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ class UnitOfReactivePower(StrEnum):
9595
CONF_TIME_OUT = "time_out"
9696
DEFAULT_TIME_OUT = 5
9797

98+
# ================================= Button autorepeat initval codes for button value_functions ==========================
99+
BUTTONREPEAT_FIRST = 0 # first manual trigger click
100+
BUTTONREPEAT_LOOP = 1 # automated loop
101+
BUTTONREPEAT_POST = -1 # final call after autoduration expired - no action needed in most cases
102+
98103
# ================================= Definitions for Sensor Declarations =================================================
99104

100105
REG_HOLDING = 1 # modbus holding register
@@ -275,6 +280,8 @@ def autorepeat_set(datadict, entitykey, value):
275280
def autorepeat_stop(datadict, entitykey):
276281
datadict["_repeatUntil"][entitykey] = 0
277282

283+
def autorepeat_stop_with_postaction(datadict, entitykey):
284+
datadict["_repeatUntil"][entitykey] = 1
278285

279286
def autorepeat_remaining(datadict, entitykey, timestamp):
280287
remaining = datadict["_repeatUntil"].get(entitykey, 0) - timestamp
@@ -284,7 +291,8 @@ def autorepeat_remaining(datadict, entitykey, timestamp):
284291
# ================================= Computed sensor value functions =================================================
285292

286293

287-
def value_function_pv_power_total(initval, descr, datadict):
294+
def value_function_pv_power_total(initval, descr, datadict): # this function can be enhanced to handle undefined values better
295+
datadict.pop("pv_power_total", None)
288296
vals = [v for k, v in datadict.items() if k.startswith("pv_power_")]
289297
return None if any(p is None for p in vals) else sum(vals)
290298

custom_components/solax_modbus/number.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,18 @@ def native_value(self) -> float:
148148
except:
149149
val = self._hub.data[self._key]
150150
return val
151-
else: # first time initialize
152-
if descr.initvalue == None:
153-
return None
154-
else:
155-
res = descr.initvalue
156-
if self._attr_native_max_value != None:
157-
res = min(res, self._attr_native_max_value)
158-
if self._attr_native_min_value != None:
159-
res = max(res, self._attr_native_min_value)
160-
self._hub.data[self._key] = res
161-
# _LOGGER.warning(f"****** (debug) initializing {self._key} = {res}")
162-
return res
151+
#else: # first time initialize
152+
# if descr.initvalue == None:
153+
# return None
154+
# else:
155+
# res = descr.initvalue
156+
# if self._attr_native_max_value != None:
157+
# res = min(res, self._attr_native_max_value)
158+
# if self._attr_native_min_value != None:
159+
# res = max(res, self._attr_native_min_value)
160+
# self._hub.data[self._key] = res
161+
# # _LOGGER.warning(f"****** (debug) initializing {self._key} = {res}")
162+
# return res
163163

164164
async def async_set_native_value(self, value: float) -> None:
165165
"""Change the number value."""

custom_components/solax_modbus/payload.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,5 @@ def skip_bytes(self, nbytes):
455455
456456
:param nbytes: The number of bytes to skip
457457
"""
458-
self._pointer += nbytes
458+
self._pointer += nbytes
459+

0 commit comments

Comments
 (0)