diff --git a/custom_components/solis_modbus/__init__.py b/custom_components/solis_modbus/__init__.py index b2a1f19..c91c96a 100644 --- a/custom_components/solis_modbus/__init__.py +++ b/custom_components/solis_modbus/__init__.py @@ -39,7 +39,7 @@ def service_write_holding_register(call: ServiceCall): controller = hass.data[DOMAIN][CONTROLLER] # Perform the logic to write to the holding register using register_address and value_to_write # ... - controller.write_holding_register(address, value) + asyncio.create_task(controller.write_holding_register(address, value)) hass.services.async_register( DOMAIN, "solis_write_holding_register", service_write_holding_register, schema=SCHEME_HOLDING_REGISTER @@ -61,6 +61,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): port = entry.data.get("port", 502) hass.data[DOMAIN][CONTROLLER] = ModbusController(host, port) + controller = hass.data[DOMAIN][CONTROLLER] + if not controller.connected(): + await controller.connect() _LOGGER.debug(f'config entry host = {host}, post = {port}') diff --git a/custom_components/solis_modbus/config_flow.py b/custom_components/solis_modbus/config_flow.py index 9847edf..398ca8f 100644 --- a/custom_components/solis_modbus/config_flow.py +++ b/custom_components/solis_modbus/config_flow.py @@ -31,7 +31,7 @@ async def _validate_config(self, user_input): modbus_controller = ModbusController(user_input["host"], user_input.get("port", 502)) try: await modbus_controller.connect() - await modbus_controller.read_input_register(33093) + await modbus_controller.async_read_input_register(33093) return True except ConnectionError: return False diff --git a/custom_components/solis_modbus/const.py b/custom_components/solis_modbus/const.py index e1f397c..3bd2179 100644 --- a/custom_components/solis_modbus/const.py +++ b/custom_components/solis_modbus/const.py @@ -1,6 +1,6 @@ DOMAIN = "solis_modbus" CONTROLLER = "modbus_controller" -VERSION = "1.4.4" +VERSION = "1.4.5" POLL_INTERVAL_SECONDS = 15 MANUFACTURER = "Solis" MODEL = "S6" diff --git a/custom_components/solis_modbus/manifest.json b/custom_components/solis_modbus/manifest.json index f201025..84cbdce 100644 --- a/custom_components/solis_modbus/manifest.json +++ b/custom_components/solis_modbus/manifest.json @@ -10,5 +10,5 @@ "issue_tracker": "https://github.com/Pho3niX90/solis_modbus/issues", "quality_scale": "silver", "requirements": ["pymodbus==3.5.4"], - "version": "1.4.4" + "version": "1.4.5" } diff --git a/custom_components/solis_modbus/modbus_controller.py b/custom_components/solis_modbus/modbus_controller.py index 052ca60..8fb3f29 100644 --- a/custom_components/solis_modbus/modbus_controller.py +++ b/custom_components/solis_modbus/modbus_controller.py @@ -18,49 +18,63 @@ def __init__(self, host, port=502): async def connect(self): _LOGGER.debug('connecting') try: - async with self._lock: - if not await self.client.connect(): - self.connect_failures += 1 - raise _LOGGER.warning(f"Failed to connect to Modbus device. Will retry, failures = {self.connect_failures}") - else: - self.connect_failures = 0 + if self.client.connected: + return True + + if not await self.client.connect(): + self.connect_failures += 1 + raise _LOGGER.warning(f"Failed to connect to Modbus device. Will retry, failures = {self.connect_failures}") + else: + self.connect_failures = 0 return True + except Exception as e: raise _LOGGER.error(f"Failed to connect to Modbus device. Will retry") async def async_read_input_register(self, register, count=1): try: + await self.connect() async with self._lock: result = await self.client.read_input_registers(register, count, slave=1) _LOGGER.debug(f'register value, register = {register}, result = {result.registers}') return result.registers - except ModbusIOException as e: - raise ValueError(f"Failed to read Modbus register: {str(e)}") + except Exception as e: + raise _LOGGER.error(f"Failed to read Modbus register: {str(e)}") async def async_read_holding_register(self, register: int, count=1): try: + await self.connect() async with self._lock: result = await self.client.read_holding_registers(register, count, slave=1) _LOGGER.debug(f'holding register value, register = {register}, result = {result.registers}') return result.registers - except ModbusIOException as e: - raise ValueError(f"Failed to read Modbus holding register: {str(e)}") + except Exception as e: + raise _LOGGER.error(f"Failed to read Modbus holding register: {str(e)}") async def async_write_holding_register(self, register: int, value): try: + await self.connect() async with self._lock: result = await self.client.write_register(register, value, slave=1) return result - except ModbusIOException as e: - raise ValueError(f"Failed to write Modbus holding register ({register}): {str(e)}") + except Exception as e: + raise _LOGGER.error(f"Failed to write Modbus holding register ({register}): {str(e)}") - async def write_holding_registers(self, start_register: int, values: list[int]): + async def async_write_holding_registers(self, start_register: int, values: list[int]): try: + await self.connect() async with self._lock: result = await self.client.write_registers(start_register, values, slave=1) return result - except ModbusIOException as e: - raise ValueError(f"Failed to write Modbus holding registers ({start_register}), values = {values}: {str(e)}") + except Exception as e: + raise _LOGGER.error(f"Failed to write Modbus holding registers ({start_register}), values = {values}: {str(e)}") + + def write_holding_registers(self, start_register: int, values: list[int]): + try: + result = self.client.write_registers(start_register, values, slave=1) + return result + except Exception as e: + raise _LOGGER.error(f"Failed to write Modbus holding registers ({start_register}), values = {values}: {str(e)}") def close_connection(self): self.client.close() diff --git a/custom_components/solis_modbus/number.py b/custom_components/solis_modbus/number.py index d715ce7..5946af1 100644 --- a/custom_components/solis_modbus/number.py +++ b/custom_components/solis_modbus/number.py @@ -127,18 +127,16 @@ async def async_added_to_hass(self) -> None: await super().async_added_to_hass() _LOGGER.debug(f"async_added_to_hass {self._attr_name}, {self.entity_id}, {self.unique_id}") - def update(self): + async def async_update(self): """Update Modbus data periodically.""" controller = self._hass.data[DOMAIN][CONTROLLER] self._attr_available = True value: float = self._hass.data[DOMAIN]['values'][str(self._register)] - if value == 0: - _LOGGER.debug(f'got 0 for register {self._register}, forcing update') - value = controller.async_read_holding_register(self._register)[0] - - _LOGGER.debug(f'Update number entity with value = {value / self._multiplier}') + if value == 0 and controller.connected(): + register_value = await controller.async_read_holding_register(self._register) + value = register_value[0] if register_value else value self._attr_native_value = round(value / self._multiplier) @@ -158,6 +156,6 @@ def set_native_value(self, value): if self._attr_native_value == value: return - self._modbus_controller.async_write_holding_register(self._register, round(value * self._multiplier)) + asyncio.create_task(self._modbus_controller.async_write_holding_register(self._register, round(value * self._multiplier))) self._attr_native_value = value self.schedule_update_ha_state() diff --git a/custom_components/solis_modbus/sensor.py b/custom_components/solis_modbus/sensor.py index c169120..57770d7 100644 --- a/custom_components/solis_modbus/sensor.py +++ b/custom_components/solis_modbus/sensor.py @@ -744,7 +744,7 @@ def extract_serial_number(values): return ''.join([hex_to_ascii(hex_value) for hex_value in values]) -def clock_drift_test(controller, hours, minutes, seconds): +async def clock_drift_test(hass, controller, hours, minutes, seconds): # Get the current time current_time = datetime.now() @@ -757,8 +757,16 @@ def clock_drift_test(controller, hours, minutes, seconds): d_seconds = r_seconds - seconds total_drift = (d_hours * 60 * 60) + (d_minutes * 60) + d_seconds + drift_counter = hass.data[DOMAIN].get('drift_counter', 0) + if abs(total_drift) > 5: - controller.async_write_holding_registers(43003, [current_time.hour, current_time.minute, current_time.second]) + """this is to make sure that we do not accidentally roll back the time, resetting all stats""" + if drift_counter > 5: + await controller.write_holding_registers(43003, [current_time.hour, current_time.minute, current_time.second]) + else: + hass.data[DOMAIN]['drift_counter'] = drift_counter + 1 + else: + hass.data[DOMAIN]['drift_counter'] = 0 class SolisDerivedSensor(RestoreSensor, SensorEntity): @@ -899,7 +907,7 @@ def update(self): hours = self._hass.data[DOMAIN]['values'][str(int(self._register[0]) - 2)] minutes = self._hass.data[DOMAIN]['values'][str(int(self._register[0]) - 1)] seconds = self._hass.data[DOMAIN]['values'][self._register[0]] - clock_drift_test(self._modbus_controller, hours, minutes, seconds) + self.hass.create_task(clock_drift_test(self._hass, self._modbus_controller, hours, minutes, seconds)) if len(self._register) == 1 and self._register[0] in ('33001', '33002', '33003'): n_value = hex(round(get_value(self)))[2:] diff --git a/custom_components/solis_modbus/switch.py b/custom_components/solis_modbus/switch.py index 4e7154a..0c19126 100644 --- a/custom_components/solis_modbus/switch.py +++ b/custom_components/solis_modbus/switch.py @@ -1,3 +1,4 @@ +import asyncio import logging from datetime import timedelta from typing import List, Any @@ -106,8 +107,8 @@ def set_register_bit(self, value): _LOGGER.debug( f"Attempting bit {self._bit_position} to {value} in register {self._read_register}. New value for register {new_register_value}") # we only want to write when values has changed. After, we read the register again to make sure it applied. - if current_register_value != new_register_value: - controller.async_write_holding_register(self._write_register, new_register_value) + if current_register_value != new_register_value and controller.connected(): + self._hass.create_task(controller.async_write_holding_register(self._write_register, new_register_value)) self._hass.data[DOMAIN]['values'][str(self._read_register)] = new_register_value self._attr_is_on = value diff --git a/custom_components/solis_modbus/time.py b/custom_components/solis_modbus/time.py index 126512c..0b4989a 100644 --- a/custom_components/solis_modbus/time.py +++ b/custom_components/solis_modbus/time.py @@ -5,6 +5,7 @@ This is where we will describe what this module does """ +import asyncio import logging from datetime import time from datetime import timedelta @@ -63,10 +64,9 @@ async def async_setup_entry(hass, config_entry: ConfigEntry, async_add_devices): async_add_devices(timeEntities, True) @callback - def async_update(now): + async def async_update(now): """Update Modbus data periodically.""" - for entity in hass.data[DOMAIN]["time_entities"]: - entity.update() + await asyncio.gather(*[entity.async_update() for entity in hass.data[DOMAIN]["time_entities"]]) # Schedule the update function to run every X seconds async_track_time_interval(hass, async_update, timedelta(seconds=POLL_INTERVAL_SECONDS * 5)) @@ -103,7 +103,7 @@ async def async_added_to_hass(self) -> None: await super().async_added_to_hass() _LOGGER.debug(f"async_added_to_hass {self._attr_name}, {self.entity_id}, {self.unique_id}") - def update(self): + async def async_update(self): """Update Modbus data periodically.""" controller = self._hass.data[DOMAIN][CONTROLLER] self._attr_available = True @@ -111,15 +111,13 @@ def update(self): hour = self._hass.data[DOMAIN]['values'][str(self._register)] minute = self._hass.data[DOMAIN]['values'][str(self._register + 1)] - if hour == 0 or minute == 0: - new_vals = controller.async_read_holding_register(self._register, count=2) - hour = new_vals[0] - minute = new_vals[1] + if (hour == 0 or minute == 0) and controller.connected(): + new_vals = await controller.async_read_holding_register(self._register, count=2) + hour = new_vals[0] if new_vals else None + minute = new_vals[1] if new_vals else None - _LOGGER.debug(f'Update time entity with hour = {hour}, minute = {minute}') - - self._attr_native_value = time(hour=hour, minute=minute) - # self.async_write_ha_state() + if hour is not None: + self._attr_native_value = time(hour=hour, minute=minute) @property def device_info(self):