Skip to content

Commit

Permalink
chore: fix lint configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
mukaschultze committed Feb 1, 2025
1 parent c906018 commit eb61e6e
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 197 deletions.
16 changes: 8 additions & 8 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"name": "mukaschultze/ha-must-inverter",
"image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
"image": "mcr.microsoft.com/devcontainers/python:3.12-bullseye",
"postCreateCommand": "scripts/setup",
"forwardPorts": [8123],
"forwardPorts": [
8123
],
"portsAttributes": {
"8123": {
"label": "Home Assistant",
Expand All @@ -15,15 +17,14 @@
"ms-python.python",
"github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance"
"ms-python.vscode-pylance",
"charliermarsh.ruff"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 4,
"python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": false,
"python.formatting.provider": "black",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
Expand All @@ -32,6 +33,5 @@
}
},
"remoteUser": "vscode",
"features": {
}
}
"features": {}
}
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: "Set up Python"
uses: actions/[email protected]
with:
python-version: "3.11"
python-version: "3.12"
cache: "pip"

- name: "Install requirements"
Expand Down
10 changes: 6 additions & 4 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml

target-version = "py310"
target-version = "py312"
line-length = 120

[lint]
select = [
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
Expand Down Expand Up @@ -38,11 +40,11 @@ ignore = [
"E731", # do not assign a lambda expression, use a def
]

[flake8-pytest-style]
[lint.flake8-pytest-style]
fixture-parentheses = false

[pyupgrade]
[lint.pyupgrade]
keep-runtime-typing = true

[mccabe]
[lint.mccabe]
max-complexity = 25
149 changes: 78 additions & 71 deletions custom_components/must_inverter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import asyncio
import logging
import contextlib

from datetime import timedelta

from homeassistant.config_entries import ConfigEntry
Expand All @@ -13,12 +15,21 @@
from .mapper import *
# from .utils.register_monitor import RegisterMonitor

PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.NUMBER, Platform.SELECT, Platform.SWITCH, Platform.BUTTON]
PLATFORMS = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.NUMBER,
Platform.SELECT,
Platform.SWITCH,
Platform.BUTTON,
]
_LOGGER = logging.getLogger(__name__)


async def async_setup(hass, config):
hass.data[DOMAIN] = {}
return True # Return boolean to indicate that initialization was successful.
return True # Return boolean to indicate that initialization was successful.


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the must inverter component."""
Expand All @@ -28,36 +39,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Get model from config entry, default to PV1800
model = entry.data.get("model") or entry.options.get("model", MODEL_PV1800)
_LOGGER.debug("Setting up Must Inverter with model: %s", model)

# Get model-specific sensors
sensors = get_sensors_for_model(model)

# Initialize inverter with model-specific sensors
inverter = MustInverter(hass, entry, sensors)

successConnecting = await inverter.connect()

if not successConnecting:
raise ConfigEntryNotReady(f"Unable to connect to modbus device")
raise ConfigEntryNotReady("Unable to connect to modbus device")

successReading = await inverter._async_refresh_modbus_data()

if not successReading:
raise ConfigEntryNotReady(f"Unable to read from modbus device")
raise ConfigEntryNotReady("Unable to read from modbus device")

# Store both inverter instance and model info
hass.data[DOMAIN][entry.entry_id] = {
"inverter": inverter,
"model": model,
"sensors": sensors
}
hass.data[DOMAIN][entry.entry_id] = {"inverter": inverter, "model": model, "sensors": sensors}

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

# Removing register monitor as we've found all needed registers
# If you need to add it back, uncomment the following lines and set the ranges to scan in register_monitor.py
# monitor = RegisterMonitor(hass)

# Add monitoring to the update cycle, but at a slower rate
# async def delayed_monitor():
# """Run monitor at a slower rate to avoid overwhelming the device."""
Expand All @@ -68,84 +75,71 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# except asyncio.CancelledError:
# _LOGGER.debug("Register monitor task cancelled")
# raise

# monitor_task = asyncio.create_task(delayed_monitor())

# Store the task so we can cancel it later
# hass.data[DOMAIN][entry.entry_id]["monitor_task"] = monitor_task

return True
except ConfigEntryNotReady as ex:
raise ex
except Exception as ex:
_LOGGER.exception("Error setting up modbus device", exc_info=True)
raise ConfigEntryNotReady(f"Unknown error connecting to modbus device") from ex
raise ConfigEntryNotReady("Unknown error connecting to modbus device") from ex


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

if unload_ok:
# Cancel the monitor task
if monitor_task := hass.data[DOMAIN][entry.entry_id].get("monitor_task"):
monitor_task.cancel()
try:
with contextlib.suppress(asyncio.CancelledError):
await monitor_task
except asyncio.CancelledError:
pass


hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener, called when the config entry options are changed."""
await hass.config_entries.async_reload(entry.entry_id)

class MustInverter:

def __init__(
self,
hass,
entry: ConfigEntry,
sensors: list
):
class MustInverter:
def __init__(self, hass, entry: ConfigEntry, sensors: list):
self._hass = hass
self._sensor_defs = sensors # Store sensor definitions
self._callbacks = [] # Store callbacks separately
self._callbacks = [] # Store callbacks separately
# Check both data and options
self._model = entry.data.get("model") or entry.options.get("model", MODEL_PV1800)
_LOGGER.debug("Initializing MustInverter with model: %s", self._model)

common = {
'timeout': entry.options["timeout"],
'retries': entry.options["retries"],
'reconnect_delay': entry.options["reconnect_delay"],
'reconnect_delay_max': entry.options["reconnect_delay_max"],
"timeout": entry.options["timeout"],
"retries": entry.options["retries"],
"reconnect_delay": entry.options["reconnect_delay"],
"reconnect_delay_max": entry.options["reconnect_delay_max"],
}

match entry.options["mode"]:
case "serial":
self._client = AsyncModbusSerialClient(
entry.options["device"],
baudrate = entry.options["baudrate"],
stopbits = entry.options["stopbits"],
bytesize = entry.options["bytesize"],
parity = entry.options["parity"],
**common
baudrate=entry.options["baudrate"],
stopbits=entry.options["stopbits"],
bytesize=entry.options["bytesize"],
parity=entry.options["parity"],
**common,
)
case "tcp":
self._client = AsyncModbusTcpClient(
entry.options["host"],
port = entry.options["port"],
**common
)
self._client = AsyncModbusTcpClient(entry.options["host"], port=entry.options["port"], **common)
case "udp":
self._client = AsyncModbusUdpClient(
entry.options["host"],
port = entry.options["port"],
**common
)
self._client = AsyncModbusUdpClient(entry.options["host"], port=entry.options["port"], **common)
case _:
raise Exception("Invalid mode")

Expand All @@ -154,7 +148,7 @@ def __init__(
self._lock = asyncio.Lock()
self._scan_interval = timedelta(seconds=entry.options.get("scan_interval", DEFAULT_SCAN_INTERVAL))
self._reading = False
self.registers = dict()
self.registers = {}
self.data = {}

@callback
Expand All @@ -179,7 +173,7 @@ def async_remove_must_inverter_sensor(self, update_callback):

async def _async_refresh_modbus_data(self, now=None):
if not await self._check_and_reopen():
#if not connected, skip
# if not connected, skip
_LOGGER.warning("not connected, skipping refresh")
return False

Expand All @@ -189,7 +183,7 @@ async def _async_refresh_modbus_data(self, now=None):
if update_result:
for update_callback in self._callbacks:
update_callback()
except Exception as e:
except Exception:
_LOGGER.exception("error reading inverter data", exc_info=True)
update_result = False

Expand All @@ -215,9 +209,13 @@ async def connect(self):
result = await self._client.connect()

if result:
_LOGGER.info("successfully connected to %s:%s", self._client.comm_params.host, self._client.comm_params.port)
_LOGGER.info(
"successfully connected to %s:%s", self._client.comm_params.host, self._client.comm_params.port
)
else:
_LOGGER.warning("not able to connect to %s:%s", self._client.comm_params.host, self._client.comm_params.port)
_LOGGER.warning(
"not able to connect to %s:%s", self._client.comm_params.host, self._client.comm_params.port
)
return result

async def write_modbus_data(self, address, value):
Expand All @@ -239,7 +237,9 @@ async def read_modbus_data(self):
_LOGGER.debug("reading modbus data")

if self._reading:
_LOGGER.warning("skipping reading modbus data, previous read still in progress, make sure your scan interval is not too low")
_LOGGER.warning(
"skipping reading modbus data, previous read still in progress, make sure your scan interval is not too low"
)
return False
self._reading = True

Expand All @@ -250,31 +250,33 @@ async def read_modbus_data(self):
# the inverter restarted for it to come back online.
# Base register ranges for all models
registersAddresses = [
# (10000, 10008, convert_partArr1), # Charger Control Messages
(10101, 10124, convert_partArr2), # Charger Control Messages
(15201, 15221, convert_partArr3), # Charger Display Messages
(20000, 20016, convert_partArr4), # Inverter Control Messages
(20101, 20214, convert_partArr5), # Inverter Control Messages
(25201, 25279, convert_partArr6), # Inverter Display Messages
# (10000, 10008, convert_partArr1), # Charger Control Messages
(10101, 10124, convert_partArr2), # Charger Control Messages
(15201, 15221, convert_partArr3), # Charger Display Messages
(20000, 20016, convert_partArr4), # Inverter Control Messages
(20101, 20214, convert_partArr5), # Inverter Control Messages
(25201, 25279, convert_partArr6), # Inverter Display Messages
]

# Add PV19-specific register ranges if needed
# Keep register ranges for pv separate to not overload the inverter
if self._model == MODEL_PV1900:
registersAddresses.extend([
(113, 114, convert_battery_status), # Battery Status (SoC, SoH)
(15207, 15208, convert_pv_data), # PV1 Data (Current, Power)
(16205, 16208, convert_pv_data), # PV2 Data (Voltage, Current, Power)
])
registersAddresses.extend(
[
(113, 114, convert_battery_status), # Battery Status (SoC, SoH)
(15207, 15208, convert_pv_data), # PV1 Data (Current, Power)
(16205, 16208, convert_pv_data), # PV2 Data (Voltage, Current, Power)
]
)

read = dict()
read = {}

for register in registersAddresses:
if self._model == MODEL_PV1900:
# Add a small delay between reading standard and PV19-specific registers
# to prevent any potential issues
await asyncio.sleep(0.1)

# Log when reading PV19-specific registers
_LOGGER.debug("Reading PV19-specific registers")

Expand All @@ -293,13 +295,18 @@ async def read_modbus_data(self):
if response.isError():
_LOGGER.error("error reading modbus data at address %s: %s", start, response)
elif len(response.registers) != count:
_LOGGER.warn("wrong number of registers read at address %s, expected %s, got %s", start, count, len(response.registers))
_LOGGER.warning(
"wrong number of registers read at address %s, expected %s, got %s",
start,
count,
len(response.registers),
)
else:
for i in range(count):
read[start + i] = response.registers[i]
self.data.update(register[2](read))
except asyncio.exceptions.CancelledError:
_LOGGER.warn("cancelled modbus read")
_LOGGER.warning("cancelled modbus read")
raise
except:
_LOGGER.error("error reading modbus data at address %s", start, exc_info=True)
Expand All @@ -320,4 +327,4 @@ def _device_info(self):
"hw_version": self.data["InverterHardwareVersion"],
"sw_version": self.data["InverterSoftwareVersion"],
"serial_number": self.data["InverterSerialNumber"],
}
}
Loading

0 comments on commit eb61e6e

Please sign in to comment.