Skip to content

Commit

Permalink
Inverter type autodetect (#35)
Browse files Browse the repository at this point in the history
New feature:
- inverter type (MI or HM) is now automatically detected
- compatybility with pymodbus 3.7
- added parameter communication parameter `comm-reconnect-delay-max`

Breaking changes:
- removed parameter microinverter-type (no longer needed)
- due to support for pymodbus 3.7 removed parameters:
  - `comm-retry-on-empty`
  - `comm-close-comm-on-error`
  - `comm-strict`
- default value of `comm-reconnect-delay` changed to 0 (means reconnections are disabled). The previous value was big to achieve the same. However, the new value is the proper solution.
  • Loading branch information
wasilukm authored Dec 8, 2024
1 parent 722fe07 commit 9be4c08
Show file tree
Hide file tree
Showing 7 changed files with 778 additions and 725 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

Send data from Hoymiles photovoltaic installation to Home Assistant through MQTT broker.

Disclaimer: This is an independent project, not affiliated with Hoymiles.
Any trademarks or product names mentioned are the property of their respective owners.

* GitHub: <https://github.com/wasilukm/hoymiles-mqtt>
* PyPI: <https://pypi.org/project/hoymiles-mqtt/>
* Free software: MIT
Expand Down
2 changes: 1 addition & 1 deletion hoymiles_mqtt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging

__author__ = """Foo Bar"""
__author__ = """Mariusz Wasiluk"""
__email__ = '[email protected]'
__version__ = '0.8.1'

Expand Down
57 changes: 16 additions & 41 deletions hoymiles_mqtt/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import configargparse
from hoymiles_modbus.client import HoymilesModbusTCP
from hoymiles_modbus.datatypes import MicroinverterType

from hoymiles_mqtt import MI_ENTITIES, PORT_ENTITIES, _main_logger
from hoymiles_mqtt.ha import HassMqtt
Expand Down Expand Up @@ -90,15 +89,6 @@ def _parse_args() -> argparse.Namespace:
env_var='QUERY_PERIOD',
help='How often (in seconds) DTU shall be queried.',
)
cfg_parser.add(
'--microinverter-type',
required=False,
type=str,
choices=['MI', 'HM'],
default='MI',
env_var='MICROINVERTER_TYPE',
help='Type od microinverters in the installation. Mixed types are not supported.',
)
cfg_parser.add(
'--mi-entities',
required=False,
Expand Down Expand Up @@ -147,36 +137,25 @@ def _parse_args() -> argparse.Namespace:
help="Additional low level modbus communication parameter - max number of retries per request.",
)
cfg_parser.add(
'--comm-retry-on-empty',
required=False,
type=bool,
default=False,
env_var='COMM_RETRY_ON_EMPTY',
help="Additional low level modbus communication parameter - retry if received an empty response.",
)
cfg_parser.add(
'--comm-close-comm-on-error',
required=False,
type=bool,
default=False,
env_var='COMM_CLOSE_COMM_ON_ERROR',
help="Additional low level modbus communication parameter - close connection on error.",
)
cfg_parser.add(
'--comm-strict',
'--comm-reconnect-delay',
required=False,
type=bool,
default=True,
env_var='COMM_STRICT',
help="Additional low level modbus communication parameter - strict timing, 1.5 character between requests.",
type=float,
default=0,
env_var='COMM_RECONNECT_DELAY',
help="Additional low level modbus communication parameter - Minimum "
"delay in seconds.milliseconds before reconnecting. "
"Doubles automatically with each unsuccessful connect, from "
"**reconnect_delay** to **reconnect_delay_max**. "
"Default is 0 which means that reconnecting is disabled.",
)
cfg_parser.add(
'--comm-reconnect-delay',
'--comm-reconnect-delay-max',
required=False,
type=int,
default=60000 * 5,
env_var='COMM_RECONNECT_DELAY',
help="Additional low level modbus communication parameter - delay in milliseconds before reconnecting.",
type=float,
default=300,
env_var='COMM_RECONNECT_DELAY_MAX',
help="Additional low level modbus communication parameter - maximum "
"delay in seconds.milliseconds before reconnecting.",
)
cfg_parser.add(
'--log-level',
Expand Down Expand Up @@ -213,19 +192,15 @@ def main():
mqtt_builder = HassMqtt(
mi_entities=options.mi_entities, port_entities=options.port_entities, expire_after=options.expire_after
)
microinverter_type = getattr(MicroinverterType, options.microinverter_type)
modbus_client = HoymilesModbusTCP(
host=options.dtu_host,
port=options.dtu_port,
microinverter_type=microinverter_type,
unit_id=options.modbus_unit_id,
)
modbus_client.comm_params.timeout = options.comm_timeout
modbus_client.comm_params.retries = options.comm_retries
modbus_client.comm_params.retry_on_empty = options.comm_retry_on_empty
modbus_client.comm_params.close_comm_on_error = options.comm_close_comm_on_error
modbus_client.comm_params.strict = options.comm_strict
modbus_client.comm_params.reconnect_delay = options.comm_reconnect_delay
modbus_client.comm_params.reconnect_delay = options.comm_reconnect_delay_max

mqtt_publisher = MqttPublisher(
mqtt_broker=options.mqtt_broker,
Expand Down
21 changes: 11 additions & 10 deletions hoymiles_mqtt/ha.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import json
from dataclasses import dataclass
from typing import Callable, Dict, Iterable, List, Optional, Tuple

from hoymiles_modbus.datatypes import PlantData
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Optional, Tuple

from hoymiles_mqtt import _main_logger

if TYPE_CHECKING:
from hoymiles_modbus.datatypes import PlantData

logger = _main_logger.getChild('ha')

PLATFORM_SENSOR = 'sensor'
Expand Down Expand Up @@ -231,7 +232,7 @@ def clear_production_today(self) -> None:
self._logger.debug('Clear today production cache.')
self._prod_today_cache = {}

def get_configs(self, plant_data: PlantData) -> Iterable[Tuple[str, str]]:
def get_configs(self, plant_data: 'PlantData') -> Iterable[Tuple[str, str]]:
"""Get MQTT config messages for given data from DTU.
Arguments:
Expand All @@ -240,7 +241,7 @@ def get_configs(self, plant_data: PlantData) -> Iterable[Tuple[str, str]]:
"""
for topic, payload in self._get_config_payloads('DTU', plant_data.dtu, DtuEntities):
yield topic, payload
for microinverter_data in plant_data.microinverter_data:
for microinverter_data in plant_data.inverters:
for topic, payload in self._get_config_payloads('inv', microinverter_data.serial_number, self._mi_entities):
yield topic, payload
for topic, payload in self._get_config_payloads(
Expand Down Expand Up @@ -270,8 +271,8 @@ def _get_state(
state_topic = self._get_state_topic(device_serial, port)
return state_topic, payload

def _update_cache(self, plant_data: PlantData) -> None:
for microinverter in plant_data.microinverter_data:
def _update_cache(self, plant_data: 'PlantData') -> None:
for microinverter in plant_data.inverters:
cache_key = (microinverter.serial_number, microinverter.port_number)
if cache_key not in self._prod_today_cache:
self._prod_today_cache[cache_key] = ZERO
Expand Down Expand Up @@ -299,12 +300,12 @@ def _update_cache(self, plant_data: PlantData) -> None:
)
microinverter.total_production = self._prod_total_cache[cache_key]

def _process_plant_data(self, plant_data: PlantData) -> None:
def _process_plant_data(self, plant_data: 'PlantData') -> None:
self._update_cache(plant_data)
plant_data.today_production = sum(self._prod_today_cache.values()) if self._prod_today_cache else ZERO
plant_data.total_production = sum(self._prod_total_cache.values()) if self._prod_total_cache else ZERO

def get_states(self, plant_data: PlantData) -> Iterable[Tuple[str, str]]:
def get_states(self, plant_data: 'PlantData') -> Iterable[Tuple[str, str]]:
"""Get MQTT message for DTU data.
Arguments:
Expand All @@ -315,7 +316,7 @@ def get_states(self, plant_data: PlantData) -> Iterable[Tuple[str, str]]:
self._process_plant_data(plant_data)
yield self._get_state(plant_data.dtu, DtuEntities, plant_data)
known_serials = []
for microinverter_data in plant_data.microinverter_data:
for microinverter_data in plant_data.inverters:
if microinverter_data.serial_number not in known_serials:
known_serials.append(microinverter_data.serial_number)
yield self._get_state(microinverter_data.serial_number, self._mi_entities, microinverter_data)
Expand Down
Loading

0 comments on commit 9be4c08

Please sign in to comment.