From 1e8a05f8c36657f874702e0018242b1a76e9e32e Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:12:55 +0000 Subject: [PATCH 01/26] First commit --- .coverage | Bin 53248 -> 53248 bytes .vscode/tasks.json | 2 +- src/solarlog_cli/solarlog_client.py | 60 ++++++++++++++++---- src/solarlog_cli/solarlog_connector.py | 17 +++++- src/solarlog_cli/solarlog_models.py | 17 +++++- tests/__snapshots__/test_solarlog_cli.ambr | 63 +++++++++++++++++++++ tests/fixtures/device_data_1.json | 14 ++++- tests/fixtures/device_data_2.json | 14 ++++- tests/fixtures/device_data_3.json | 14 ++++- tests/fixtures/device_data_4.json | 14 ++++- tests/fixtures/device_error_list.json | 11 ++++ tests/fixtures/device_event_list_1.json | 12 ++++ tests/fixtures/device_event_list_2.json | 12 ++++ tests/fixtures/device_event_list_3.json | 12 ++++ tests/fixtures/device_event_list_4.json | 12 ++++ tests/fixtures/device_last_event.json | 27 +++++++++ tests/fixtures/device_status.json | 9 +++ tests/fixtures/firmware_data.json | 6 ++ tests/test_solarlog_cli.py | 40 +++++++++++++ 19 files changed, 335 insertions(+), 21 deletions(-) create mode 100644 tests/fixtures/device_error_list.json create mode 100644 tests/fixtures/device_event_list_1.json create mode 100644 tests/fixtures/device_event_list_2.json create mode 100644 tests/fixtures/device_event_list_3.json create mode 100644 tests/fixtures/device_event_list_4.json create mode 100644 tests/fixtures/device_last_event.json create mode 100644 tests/fixtures/device_status.json create mode 100644 tests/fixtures/firmware_data.json diff --git a/.coverage b/.coverage index cbd66c95cd1f94c5898d9f5964af2ae3d1ad6cbd..2c7daa0dd851da7d24a0a768422a04a8b4890b98 100644 GIT binary patch delta 117 zcmV-*0E+*BpaX!Q1F!~w42J*@`47_%q7Ru5hqDn7bq^sB1q1;JA_cB{w|n=`@&BLC z0|x{I0SOQUvTZK|1OW+Pldg{=E_Q|0&dOPt8YBV)0SQV1x`s0J+pU!D`Ky$6tI{dI XSK6mi)>X>)zG|&iRbL0Q`Hu%ctdcJ; delta 109 zcmZozz}&Eac>`MmPd@|yPyVa?EBNR0_iq*ysO6XEV`X9Flw)02y}i2c|Nj~}b`~IC zfMrvzHZzbBF?mJ5vc{USRg6kZK&~6pt~R4Dx7SX2_jC1>velO~_lMtF9&{~qUwHP~ NRllq@|LkXX007vjDeM3M diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cff6590..36c31d4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,7 +4,7 @@ { "label": "Pytest", "type": "shell", - "command": "pytest tests", + "command": "pytest tests --snapshot-update", "dependsOn": ["Install all Requirements"], "group": { "kind": "test", diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index f5c4a75..6c426df 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import asyncio -from datetime import datetime +from datetime import datetime, date import json import logging from typing import Any @@ -17,7 +17,7 @@ SolarLogUpdateError, ) -from .solarlog_models import EnergyData, SolarlogData +from .solarlog_models import EnergyData, EventData, SolarlogData SOLARLOG_REQUEST_PAYLOAD = '{ "801": { "170": null } }' _LOGGER = logging.getLogger(__name__) @@ -121,7 +121,7 @@ async def login(self) -> bool: return True - async def execute_http_request(self, body: str, path: str = "getjp", timeout: float | None = None) -> ClientResponse: # pylint: disable=line-too-long + async def execute_http_request(self, body: str, path: str = "getjp", timeout: float | None = None) -> ClientResponse: # pylint: disable=line-too-long """Helper function to process the HTTP Get call.""" if self.session is None: self.session = ClientSession() @@ -214,7 +214,7 @@ async def get_basic_data(self) -> SolarlogData: return data async def get_battery_data(self, timeout: float | None = None) -> list[float]: - """Get battery data from Solar-Log""" + """Get battery data from Solar-Log.""" raw_data: dict = await self.parse_http_response( await self.execute_http_request('{ "858": null }', timeout=timeout) @@ -225,7 +225,7 @@ async def get_battery_data(self, timeout: float | None = None) -> list[float]: return data async def get_power_per_inverter(self, timeout: float | None = None) -> dict[int, float]: - """Get power data from Solar-Log""" + """Get power data from Solar-Log.""" raw_data: dict = await self.parse_http_response( await self.execute_http_request('{ "782": null }', timeout=timeout) @@ -237,7 +237,7 @@ async def get_power_per_inverter(self, timeout: float | None = None) -> dict[int return data async def get_energy_per_inverter(self, timeout: float | None = None) -> dict[int, float]: - """Get power data from Solar-Log""" + """Get power data from Solar-Log.""" raw_data: dict = await self.parse_http_response( await self.execute_http_request('{ "854": null }', timeout=timeout) @@ -253,7 +253,7 @@ async def get_energy_per_inverter(self, timeout: float | None = None) -> dict[in return data async def get_energy(self, timeout: float | None = None) -> EnergyData | None: - """Get energy data from Solar-Log""" + """Get energy data from Solar-Log.""" raw_data: dict = await self.parse_http_response( await self.execute_http_request('{ "878": null }', timeout=timeout) @@ -267,8 +267,21 @@ async def get_energy(self, timeout: float | None = None) -> EnergyData | None: return None - async def get_device_list(self, timeout: float | None = None) -> dict[int, str]: - """Get list of all connected devices.""" + async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: + """Get firmware data from Solar-Log.""" + + raw_data: dict = await self.parse_http_response( + await self.execute_http_request('{ "801": {"101" : None, "102" : None } }', timeout=timeout) + ) + fw_version: str = raw_data["801"]["101"] + fw_date: date = datetime.strptime( + raw_data["801"]["102"], "%d.%m.%Y").date() + + return (fw_version, fw_date) + + async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str, str, str]]: + """Get list of all connected devices. + Return value is a dict with name, possible events and error codes per device.""" # get list of all inverters connected to Solar-Log raw_data: dict = await self.parse_http_response( @@ -276,20 +289,43 @@ async def get_device_list(self, timeout: float | None = None) -> dict[int, str]: ) raw_data = raw_data["740"] - device_list: dict[int, str] = {} + device_list: dict[int, tuple[str, str, str]] = {} for key, value in raw_data.items(): if value != "Err": # get name of the inverter raw_data = await self.parse_http_response( await self.execute_http_request( - f"""{{ "141": {{ "{key}": {{ "119": null }} }} }}""", timeout=timeout + f"""{{ "141": {{ "{key}": {{ "119": null, "708": null, "709": null }} }} }}""", timeout=timeout ) ) - device_list |= {int(key): raw_data["141"][key]["119"]} + device_list |= {int(key): ( + raw_data["141"][key]["119"], raw_data["141"][key]["708"], raw_data["141"][key]["709"])} return device_list + async def get_device_last_event(self, device: int, timeout: float | None = None) -> EventData: + """Get list of last event/error of a device.""" + + raw_data = await self.parse_http_response( + await self.execute_http_request( + f"""{{ "141": {{ "{device}": {{ "710": null }} }} }}""", timeout=timeout + ) + ) + print(raw_data) + events = raw_data["141"][str(device)]["710"]["0"] + + return EventData(start=datetime.fromtimestamp(events[0][0]), end=datetime.fromtimestamp(events[0][1]), event=int(events[0][3]), error=int(events[0][4])) + + async def get_status_per_device(self, timeout: float | None = None) -> dict[int, str]: + """Get inverter status from Solar-Log""" + + raw_data: dict = await self.parse_http_response( + await self.execute_http_request('{ "608": null }', timeout=timeout) + ) + + return raw_data["608"] + async def close(self) -> None: """Close open client session.""" if self.session and self._close_session: diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index 10dbd5a..de0540c 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -1,6 +1,6 @@ """Connector class to manage access to Solar-Log.""" -from datetime import timezone, tzinfo +from datetime import date, timezone, tzinfo import logging from zoneinfo import ZoneInfo @@ -164,13 +164,18 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In devices = await self.client.get_device_list(timeout) self._device_list = { - key: InverterData(name=value,enabled=self.device(key).enabled) + key: InverterData(name=value[0],enabled=self.device(key).enabled) for key, value in devices.items() } _LOGGER.debug("Device list: %s",self._device_list) return self._device_list + async def update_firmware(self, timeout: float | None = None) -> tuple[str, date]: + """Update firmware data.""" + + return await self.client.get_firmware(timeout) + async def update_inverter_data(self, timeout: float | None = None) -> dict[int, InverterData]: """Update device specific data.""" @@ -185,6 +190,14 @@ async def update_inverter_data(self, timeout: float | None = None) -> dict[int, if self._device_list.get(key,InverterData).enabled: self._device_list[key].consumption_year = float(value) + raw_data = await self.client.get_status_per_device(timeout) + for key, value in raw_data.items(): + key = int(key) + if self._device_list.get(key,InverterData).enabled: + self._device_list[key].status = value + + self._device_list[key].last_event = await self.client.get_device_last_event(key) + _LOGGER.debug("Inverter data updated: %s",self._device_list) return self._device_list diff --git a/src/solarlog_cli/solarlog_models.py b/src/solarlog_cli/solarlog_models.py index 1340350..357e700 100644 --- a/src/solarlog_cli/solarlog_models.py +++ b/src/solarlog_cli/solarlog_models.py @@ -20,14 +20,27 @@ class EnergyData(): production: float | None = None self_consumption: float | None = None +@dataclass +class EventData(): + """Event Data model.""" + + end: datetime + error: int + event: int + start: datetime + @dataclass class InverterData(): """Inverter Data model.""" name: str = "" - enabled: bool = False - current_power: float | None = None consumption_year: float | None = None + current_power: float | None = None + enabled: bool = False + errors: dict[int,str] = field(default_factory=dict) + events: dict[int,str] = field(default_factory=dict) + last_event: EventData | None = None + status: str | None = None @dataclass diff --git a/tests/__snapshots__/test_solarlog_cli.ambr b/tests/__snapshots__/test_solarlog_cli.ambr index 79b3d13..2e4dfba 100644 --- a/tests/__snapshots__/test_solarlog_cli.ambr +++ b/tests/__snapshots__/test_solarlog_cli.ambr @@ -21,25 +21,64 @@ 'consumption_year': 4227027.0, 'current_power': 3170.0, 'enabled': True, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': dict({ + 'end': datetime.datetime(2026, 1, 12, 17, 33, 59), + 'error': 0, + 'event': 6, + 'start': datetime.datetime(2026, 1, 12, 8, 13, 31), + }), 'name': '', + 'status': 'Power', }), 1: dict({ 'consumption_year': 1920650.0, 'current_power': None, 'enabled': True, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': dict({ + 'end': datetime.datetime(2026, 1, 12, 17, 33, 59), + 'error': 0, + 'event': 6, + 'start': datetime.datetime(2026, 1, 12, 8, 13, 31), + }), 'name': '', + 'status': 'Power', }), 2: dict({ 'consumption_year': None, 'current_power': None, 'enabled': False, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': None, 'name': '', + 'status': None, }), 3: dict({ 'consumption_year': None, 'current_power': 2816.0, 'enabled': True, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': dict({ + 'end': datetime.datetime(2026, 1, 12, 17, 33, 59), + 'error': 0, + 'event': 6, + 'start': datetime.datetime(2026, 1, 12, 8, 13, 31), + }), 'name': '', + 'status': 'OFF', }), }), 'last_updated': datetime.datetime(2024, 8, 26, 14, 19, 45, tzinfo=zoneinfo.ZoneInfo(key='UTC')), @@ -65,25 +104,49 @@ 'consumption_year': None, 'current_power': None, 'enabled': True, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': None, 'name': 'Device 1', + 'status': None, }), 1: dict({ 'consumption_year': None, 'current_power': None, 'enabled': False, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': None, 'name': 'Device 2', + 'status': None, }), 2: dict({ 'consumption_year': None, 'current_power': None, 'enabled': False, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': None, 'name': 'Device 3', + 'status': None, }), 3: dict({ 'consumption_year': None, 'current_power': None, 'enabled': True, + 'errors': dict({ + }), + 'events': dict({ + }), + 'last_event': None, 'name': 'Device 4', + 'status': None, }), }) # --- diff --git a/tests/fixtures/device_data_1.json b/tests/fixtures/device_data_1.json index b913068..65d1da0 100644 --- a/tests/fixtures/device_data_1.json +++ b/tests/fixtures/device_data_1.json @@ -1,7 +1,19 @@ { "141": { "0": { - "119":"Device 1" + "119":"Device 1", + "708":{ + "0":"No Current", + "1":" Power", + "2":"", + "3":"" + }, + "709":{ + "0":" ", + "1":"No Current", + "2":"Power", + "3":"" + } } } } \ No newline at end of file diff --git a/tests/fixtures/device_data_2.json b/tests/fixtures/device_data_2.json index 7e9e683..82166c6 100644 --- a/tests/fixtures/device_data_2.json +++ b/tests/fixtures/device_data_2.json @@ -1,7 +1,19 @@ { "141": { "1": { - "119":"Device 2" + "119":"Device 2", + "708":{ + "0":"No Current", + "1":" Power", + "2":"", + "3":"" + }, + "709":{ + "0":" ", + "1":"No Current", + "2":"Power", + "3":"" + } } } } \ No newline at end of file diff --git a/tests/fixtures/device_data_3.json b/tests/fixtures/device_data_3.json index 8dfc488..2bc8540 100644 --- a/tests/fixtures/device_data_3.json +++ b/tests/fixtures/device_data_3.json @@ -1,7 +1,19 @@ { "141": { "2": { - "119":"Device 3" + "119":"Device 3", + "708":{ + "0":"No Current", + "1":" Power", + "2":"", + "3":"" + }, + "709":{ + "0":" ", + "1":"No Current", + "2":"Power", + "3":"" + } } } } \ No newline at end of file diff --git a/tests/fixtures/device_data_4.json b/tests/fixtures/device_data_4.json index 29d621a..492c66e 100644 --- a/tests/fixtures/device_data_4.json +++ b/tests/fixtures/device_data_4.json @@ -1,7 +1,19 @@ { "141": { "3": { - "119":"Device 4" + "119":"Device 4", + "708":{ + "0":"No Current", + "1":" Power", + "2":"", + "3":"" + }, + "709":{ + "0":" ", + "1":"No Current", + "2":"Power", + "3":"" + } } } } \ No newline at end of file diff --git a/tests/fixtures/device_error_list.json b/tests/fixtures/device_error_list.json new file mode 100644 index 0000000..43bbeaa --- /dev/null +++ b/tests/fixtures/device_error_list.json @@ -0,0 +1,11 @@ +{ + "141": { + "3":{ + "708":{ + "0":" ", + "1":"Undefined", + "2":"" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_event_list_1.json b/tests/fixtures/device_event_list_1.json new file mode 100644 index 0000000..4a746fe --- /dev/null +++ b/tests/fixtures/device_event_list_1.json @@ -0,0 +1,12 @@ +{ + "141": { + "0": { + "710": { + "0": [ + [1768205611,1768239239,3,6,0], + [1768239240,1768292129,3,0,0] + ] + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_event_list_2.json b/tests/fixtures/device_event_list_2.json new file mode 100644 index 0000000..2494aff --- /dev/null +++ b/tests/fixtures/device_event_list_2.json @@ -0,0 +1,12 @@ +{ + "141": { + "1": { + "710": { + "0": [ + [1768205611,1768239239,3,6,0], + [1768239240,1768292129,3,0,0] + ] + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_event_list_3.json b/tests/fixtures/device_event_list_3.json new file mode 100644 index 0000000..afe94c2 --- /dev/null +++ b/tests/fixtures/device_event_list_3.json @@ -0,0 +1,12 @@ +{ + "141": { + "2": { + "710": { + "0": [ + [1768205611,1768239239,3,6,0], + [1768239240,1768292129,3,0,0] + ] + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_event_list_4.json b/tests/fixtures/device_event_list_4.json new file mode 100644 index 0000000..61a11f4 --- /dev/null +++ b/tests/fixtures/device_event_list_4.json @@ -0,0 +1,12 @@ +{ + "141": { + "3": { + "710": { + "0": [ + [1768205611,1768239239,3,6,0], + [1768239240,1768292129,3,0,0] + ] + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_last_event.json b/tests/fixtures/device_last_event.json new file mode 100644 index 0000000..4dde43a --- /dev/null +++ b/tests/fixtures/device_last_event.json @@ -0,0 +1,27 @@ +{ + "141": { + "3":{ + "708":{ + "0":"OFF", + "1":"INIT", + "2":"IsoMeas", + "3":"GridCheck", + "4":"StartUp","5":" ", + "6":"FeedIn", + "7":"Throttled", + "8":"ExtSwitchOff", + "9":"Update", + "10":"Standby", + "11":"GridSync", + "12":"GridPreCheck", + "13":"GridSwitchOff", + "14":"Overheating", + "15":"Shutdown", + "16":"ImproperDcVoltage", + "17":"ESB", + "18":"Undefined", + "19":"" + } + } + } +} \ No newline at end of file diff --git a/tests/fixtures/device_status.json b/tests/fixtures/device_status.json new file mode 100644 index 0000000..578696a --- /dev/null +++ b/tests/fixtures/device_status.json @@ -0,0 +1,9 @@ +{ + "608":{ + "0":"Power", + "1":"Power", + "2":"Off", + "3":"OFF", + "4":"OFFLINE" + } +} \ No newline at end of file diff --git a/tests/fixtures/firmware_data.json b/tests/fixtures/firmware_data.json new file mode 100644 index 0000000..10e44f5 --- /dev/null +++ b/tests/fixtures/firmware_data.json @@ -0,0 +1,6 @@ +{ + "801": { + "101":16974156, + "102":"16.07.2024" + } +} diff --git a/tests/test_solarlog_cli.py b/tests/test_solarlog_cli.py index 85c4639..c77bced 100644 --- a/tests/test_solarlog_cli.py +++ b/tests/test_solarlog_cli.py @@ -238,6 +238,22 @@ async def test_update_data( "http://solarlog.com/getjp", body=load_fixture("energy_per_inverter.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_status.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_1.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_2.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_4.json"), + ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data.json"), @@ -278,10 +294,30 @@ async def test_update_data_without_battery( "http://solarlog.com/getjp", body=load_fixture("energy_per_inverter.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_status.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_1.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_2.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_4.json"), + ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data_without_battery.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("firmware_data.json"), + ) solarlog_connector = SolarLogConnector( "http://solarlog.com", @@ -294,6 +330,10 @@ async def test_update_data_without_battery( assert data.battery_data is None + firmware = await solarlog_connector.update_firmware() + + assert firmware[0] == 16974156 + await solarlog_connector.client.close() assert solarlog_connector.client.session.closed From 3ed97d19a9e95150ee95a10428a0b51bf736dbeb Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Fri, 3 Apr 2026 06:25:06 +0000 Subject: [PATCH 02/26] Add VSCode testing environment --- .vscode/settings.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d74721e..7bd7ee1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,10 @@ "python.analysis.extraPaths": [ "./src/solarlog_cli" ], - "python.analysis.typeCheckingMode": "basic" + "python.analysis.typeCheckingMode": "basic", + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } \ No newline at end of file From 78c9d35a21a40b7f7e158e43816550d54590b86d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Fri, 3 Apr 2026 06:31:53 +0000 Subject: [PATCH 03/26] Rename function for clarity --- src/solarlog_cli/solarlog_connector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index de0540c..d663795 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -171,8 +171,8 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In return self._device_list - async def update_firmware(self, timeout: float | None = None) -> tuple[str, date]: - """Update firmware data.""" + async def update_firmware_information(self, timeout: float | None = None) -> tuple[str, date]: + """Update firmware data (version and release date).""" return await self.client.get_firmware(timeout) From 1c313384a49320da1df08feeb361b6196c5c0e4d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Sat, 4 Apr 2026 09:36:00 +0000 Subject: [PATCH 04/26] minor adjustments --- .vscode/settings.json | 3 ++- src/solarlog_cli/solarlog_models.py | 4 +++- tests/test_solarlog_cli.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7bd7ee1..1efa477 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python.createEnvironment.contentButton": "show" } \ No newline at end of file diff --git a/src/solarlog_cli/solarlog_models.py b/src/solarlog_cli/solarlog_models.py index 357e700..cf37904 100644 --- a/src/solarlog_cli/solarlog_models.py +++ b/src/solarlog_cli/solarlog_models.py @@ -1,6 +1,6 @@ """Models for SolarLog.""" from dataclasses import dataclass, field -from datetime import datetime +from datetime import date, datetime from mashumaro import DataClassDictMixin @@ -76,6 +76,8 @@ class SolarlogData(DataClassDictMixin): #extended data battery_data: BatteryData | None = None + firmware_date: date | None = None + firmware_version: int | None = None inverter_data: dict[int, InverterData] = field(default_factory=dict) production_year: float | None = None self_consumption_year: float | None = None diff --git a/tests/test_solarlog_cli.py b/tests/test_solarlog_cli.py index c77bced..df2138f 100644 --- a/tests/test_solarlog_cli.py +++ b/tests/test_solarlog_cli.py @@ -330,7 +330,7 @@ async def test_update_data_without_battery( assert data.battery_data is None - firmware = await solarlog_connector.update_firmware() + firmware = await solarlog_connector.update_firmware_information() assert firmware[0] == 16974156 From e72679f5d3039a49e2f21df1b056f5685aab81b3 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:51:01 +0000 Subject: [PATCH 05/26] Add token to body in API request --- src/solarlog_cli/solarlog_client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 6c426df..7018a4f 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -41,6 +41,7 @@ def __init__(self, host: str, session: ClientSession | None, password: str = "") self._hashed_pwd: bool = False self._close_session: bool = True + self._token = "" async def test_connection(self) -> bool: """Test the connection to Solar-Log.""" @@ -113,6 +114,9 @@ async def login(self) -> bool: self.session.cookie_jar.update_cookies( {"SolarLog": response.cookies["SolarLog"].value}) + + if not self._hashed_pwd: + self._token = response.cookies["SolarLog"].value _LOGGER.debug("response: %s", text) _LOGGER.debug("cookies: %s", response.cookies) @@ -131,6 +135,9 @@ async def execute_http_request(self, body: str, path: str = "getjp", timeout: fl header = {"Content-Type": "text/html", "X-SL-CSRF-PROTECTION": "1"} + if self._token != "": + body = f"token={self._token}; " + body + _LOGGER.debug("HTTP-request header: %s", header) _LOGGER.debug("HTTP-request body: %s", body) From bfffcbf4f1f141e9ec71af567c10271a0bb87df9 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:52:57 +0000 Subject: [PATCH 06/26] update coverage --- src/solarlog_cli/solarlog_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 7018a4f..f838f69 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -114,7 +114,7 @@ async def login(self) -> bool: self.session.cookie_jar.update_cookies( {"SolarLog": response.cookies["SolarLog"].value}) - + if not self._hashed_pwd: self._token = response.cookies["SolarLog"].value From 4ef02f17064c9c17f7063d32b3f1c14155ff4ef8 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 08:27:13 +0000 Subject: [PATCH 07/26] add comment --- src/solarlog_cli/solarlog_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index f838f69..fa1fba1 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -41,7 +41,7 @@ def __init__(self, host: str, session: ClientSession | None, password: str = "") self._hashed_pwd: bool = False self._close_session: bool = True - self._token = "" + self._token = "" #required for older firmware, where cookie is not sufficient async def test_connection(self) -> bool: """Test the connection to Solar-Log.""" From 05e528b49462d2ad68a5989f38bb7f0297e510d8 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:42:04 +0200 Subject: [PATCH 08/26] Bump version from 0.7.0 to 0.7.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0a9cdb0..4f09c9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "solarlog_cli" -version = "0.7.0" +version = "0.7.1" authors = [ { name="dontinelli", email="73341522+dontinelli@users.noreply.github.com" }, ] From 8779ed3fc8611b23b1c915146c254c6a3403bf1d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:12:55 +0000 Subject: [PATCH 09/26] First commit --- src/solarlog_cli/solarlog_connector.py | 4 +-- tests/test_solarlog_cli.py | 38 +++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index d663795..de0540c 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -171,8 +171,8 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In return self._device_list - async def update_firmware_information(self, timeout: float | None = None) -> tuple[str, date]: - """Update firmware data (version and release date).""" + async def update_firmware(self, timeout: float | None = None) -> tuple[str, date]: + """Update firmware data.""" return await self.client.get_firmware(timeout) diff --git a/tests/test_solarlog_cli.py b/tests/test_solarlog_cli.py index df2138f..5d93700 100644 --- a/tests/test_solarlog_cli.py +++ b/tests/test_solarlog_cli.py @@ -254,6 +254,22 @@ async def test_update_data( "http://solarlog.com/getjp", body=load_fixture("device_event_list_4.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_status.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_1.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_2.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_4.json"), + ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data.json"), @@ -310,6 +326,22 @@ async def test_update_data_without_battery( "http://solarlog.com/getjp", body=load_fixture("device_event_list_4.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_status.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_1.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_2.json"), + ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("device_event_list_4.json"), + ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data_without_battery.json"), @@ -318,6 +350,10 @@ async def test_update_data_without_battery( "http://solarlog.com/getjp", body=load_fixture("firmware_data.json"), ) + responses.post( + "http://solarlog.com/getjp", + body=load_fixture("firmware_data.json"), + ) solarlog_connector = SolarLogConnector( "http://solarlog.com", @@ -330,7 +366,7 @@ async def test_update_data_without_battery( assert data.battery_data is None - firmware = await solarlog_connector.update_firmware_information() + firmware = await solarlog_connector.update_firmware() assert firmware[0] == 16974156 From 355b485d459457da4b1e38108677658b82cadb48 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 14:15:34 +0000 Subject: [PATCH 10/26] Tidy up --- .coverage | Bin 53248 -> 53248 bytes src/solarlog_cli/solarlog_connector.py | 4 +-- tests/__snapshots__/test_solarlog_cli.ambr | 2 ++ tests/test_solarlog_cli.py | 34 +-------------------- 4 files changed, 5 insertions(+), 35 deletions(-) diff --git a/.coverage b/.coverage index 2c7daa0dd851da7d24a0a768422a04a8b4890b98..978baf476504b92056958e3ccc6d65e2de3ad3cd 100644 GIT binary patch delta 324 zcmZozz}&Eac|w*{1OtB@uN1#7Pdd*>?m0XboT1#A>^hu!Y!MqLUSZwb%XWy3t3`l` zU0hU@v4eSYB=2QLV+A14)m2bX&qyrJP**5PttbHr!2wXt11O`ApQccfk*biASdzF| zi~o)Q+XDvvcl-}F3kqD}SKwh{mgNT;5T96-Tx=k~$Sf^cmReMtnV)B_XK12lu*rZe zfTxXt|0n-({)PNA_}hTmO8C_USXmf3l~~tRZ)g5~o{^#9JU=@NP(*-bQ?3d#km1F& ztIdez#^n3`Qtk#!K!GIYSJ4-GvSY7DM&5P0`|tm)xY*qNtIuzG8z1@q|8k$yu(0rF zn!68Wr9WNc!^_IZ!pX_U$i=|Gz{-E0f&VxETmCPb`6fK!=U`%H1DeLjy!qFBE``l3 I3;yr}07_wFkpKVy delta 302 zcmZozz}&Eac|w*{F9UxZuRp&o&m3Mg?u$IRoL$`W*b_OE*?Kn%3UIP*j$k{)#?>6n z#4au>%GfTsS%vp9<7OTHTLNqk8TjAxKLo0}%0KymKA#{zBeS%4S!z*nW`3Tzo|&HE zCIhwro^}TQpZu5k7xB;JZwIO^<&O|xWntu0VqI6gz54t4kN@YNXJ%&siU_c5%GGBE zG7^|yMPKO2Ui*5@n!JU1|Lfmgy;>JOKlS$hP5=J;&pfqe&AKzeWvy4wor;_(rNRW% z;Kj77&FIVR$!q(iL|GYGI63(kxfmE2So!ZW@c-uj!vA(N--IXp9L&sYK*fwqn}5yc nQs86de-4uU#{ZH34gYhnFC>{+nHhMQSwI9ch+qN{j6ebaP?=wo diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index de0540c..99a9d8d 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -171,8 +171,8 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In return self._device_list - async def update_firmware(self, timeout: float | None = None) -> tuple[str, date]: - """Update firmware data.""" + async def update_firmware_information(self, timeout: float | None = None) -> tuple[str, date]: + """Update firmware data (version and relase date).""" return await self.client.get_firmware(timeout) diff --git a/tests/__snapshots__/test_solarlog_cli.ambr b/tests/__snapshots__/test_solarlog_cli.ambr index 2e4dfba..40b7580 100644 --- a/tests/__snapshots__/test_solarlog_cli.ambr +++ b/tests/__snapshots__/test_solarlog_cli.ambr @@ -16,6 +16,8 @@ 'consumption_year': 4218251, 'consumption_yesterday': 94350, 'efficiency': 96.65663657639585, + 'firmware_date': None, + 'firmware_version': None, 'inverter_data': dict({ 0: dict({ 'consumption_year': 4227027.0, diff --git a/tests/test_solarlog_cli.py b/tests/test_solarlog_cli.py index 5d93700..76e6138 100644 --- a/tests/test_solarlog_cli.py +++ b/tests/test_solarlog_cli.py @@ -254,22 +254,6 @@ async def test_update_data( "http://solarlog.com/getjp", body=load_fixture("device_event_list_4.json"), ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_status.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_1.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_2.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_4.json"), - ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data.json"), @@ -326,22 +310,6 @@ async def test_update_data_without_battery( "http://solarlog.com/getjp", body=load_fixture("device_event_list_4.json"), ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_status.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_1.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_2.json"), - ) - responses.post( - "http://solarlog.com/getjp", - body=load_fixture("device_event_list_4.json"), - ) responses.post( "http://solarlog.com/getjp", body=load_fixture("battery_data_without_battery.json"), @@ -366,7 +334,7 @@ async def test_update_data_without_battery( assert data.battery_data is None - firmware = await solarlog_connector.update_firmware() + firmware = await solarlog_connector.update_firmware_information() assert firmware[0] == 16974156 From dc512dbb005c5eacbc25854a0323a75029c02877 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 14:22:33 +0000 Subject: [PATCH 11/26] Fix lint-errors --- src/solarlog_cli/solarlog_client.py | 22 +++++++++++++++++----- src/solarlog_cli/solarlog_models.py | 2 ++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index fa1fba1..c58ebf8 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -278,7 +278,9 @@ async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: """Get firmware data from Solar-Log.""" raw_data: dict = await self.parse_http_response( - await self.execute_http_request('{ "801": {"101" : None, "102" : None } }', timeout=timeout) + await self.execute_http_request( + '{ "801": {"101" : None, "102" : None } }', timeout=timeout + ) ) fw_version: str = raw_data["801"]["101"] fw_date: date = datetime.strptime( @@ -286,7 +288,7 @@ async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: return (fw_version, fw_date) - async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str, str, str]]: + async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str,str,str]]: """Get list of all connected devices. Return value is a dict with name, possible events and error codes per device.""" @@ -303,11 +305,16 @@ async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple # get name of the inverter raw_data = await self.parse_http_response( await self.execute_http_request( - f"""{{ "141": {{ "{key}": {{ "119": null, "708": null, "709": null }} }} }}""", timeout=timeout + f"""{{ "141": {{ "{key}": {{ "119": null, "708": null, "709": null }} }} }}""", # pylint: disable=line-too-long + timeout=timeout, ) ) device_list |= {int(key): ( - raw_data["141"][key]["119"], raw_data["141"][key]["708"], raw_data["141"][key]["709"])} + raw_data["141"][key]["119"], + raw_data["141"][key]["708"], + raw_data["141"][key]["709"], + ) + } return device_list @@ -322,7 +329,12 @@ async def get_device_last_event(self, device: int, timeout: float | None = None) print(raw_data) events = raw_data["141"][str(device)]["710"]["0"] - return EventData(start=datetime.fromtimestamp(events[0][0]), end=datetime.fromtimestamp(events[0][1]), event=int(events[0][3]), error=int(events[0][4])) + return EventData( + start=datetime.fromtimestamp(events[0][0]), + end=datetime.fromtimestamp(events[0][1]), + event=int(events[0][3]), + error=int(events[0][4]) + ) async def get_status_per_device(self, timeout: float | None = None) -> dict[int, str]: """Get inverter status from Solar-Log""" diff --git a/src/solarlog_cli/solarlog_models.py b/src/solarlog_cli/solarlog_models.py index cf37904..fd1ee17 100644 --- a/src/solarlog_cli/solarlog_models.py +++ b/src/solarlog_cli/solarlog_models.py @@ -33,6 +33,8 @@ class EventData(): class InverterData(): """Inverter Data model.""" + # pylint: disable=too-many-instance-attributes + name: str = "" consumption_year: float | None = None current_power: float | None = None From 8c5bafc6507d731fb79b375ffa81bc6ea1902733 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 17:35:49 +0000 Subject: [PATCH 12/26] test fixes --- .coverage | Bin 53248 -> 53248 bytes src/solarlog_cli/solarlog_client.py | 4 ++-- src/solarlog_cli/solarlog_connector.py | 4 ++-- tests/__snapshots__/test_solarlog_cli.ambr | 20 ++++++++++++++++++++ tests/fixtures/device_data_1.json | 11 ++++------- tests/fixtures/device_data_2.json | 11 ++++------- tests/fixtures/device_data_3.json | 11 ++++------- tests/fixtures/device_data_4.json | 11 ++++------- 8 files changed, 40 insertions(+), 32 deletions(-) diff --git a/.coverage b/.coverage index 978baf476504b92056958e3ccc6d65e2de3ad3cd..255234970274fe724fdddf961273b905e3b88fcd 100644 GIT binary patch delta 76 zcmZozz}&Eac>`MmPb&lePyS>43;3t=w{8{`DCQRuU}a(CRAOCMy`A~EJv#$~o&Dq) g{rYT%OhDn3$!GgjIG=N^w(T$acJj*RpZ)9(0D}P;k^lez delta 75 zcmZozz}&Eac>`MmPa6aOPyXZl3;Ad8w`~>_DB%|pU}a(CRAOCMy`A~{c}9kY^Zb*i f_v^D6Fad>=CZFk7;n;mBEB)yjAKuMB``H};pF tuple[str, date]: return (fw_version, fw_date) - async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str,str,str]]: + async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str,dict,dict]]: """Get list of all connected devices. Return value is a dict with name, possible events and error codes per device.""" @@ -298,7 +298,7 @@ async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple ) raw_data = raw_data["740"] - device_list: dict[int, tuple[str, str, str]] = {} + device_list: dict[int, tuple[str, dict, dict]] = {} for key, value in raw_data.items(): if value != "Err": diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index 99a9d8d..abe959b 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -164,10 +164,10 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In devices = await self.client.get_device_list(timeout) self._device_list = { - key: InverterData(name=value[0],enabled=self.device(key).enabled) + key: InverterData(name=value[0],enabled=self.device(key).enabled,errors=value[2],events=value[1]) for key, value in devices.items() } - _LOGGER.debug("Device list: %s",self._device_list) + _LOGGER.debug("Device list updated: %s",self._device_list) return self._device_list diff --git a/tests/__snapshots__/test_solarlog_cli.ambr b/tests/__snapshots__/test_solarlog_cli.ambr index 40b7580..270b80a 100644 --- a/tests/__snapshots__/test_solarlog_cli.ambr +++ b/tests/__snapshots__/test_solarlog_cli.ambr @@ -107,8 +107,13 @@ 'current_power': None, 'enabled': True, 'errors': dict({ + '0': 'No error', + '1': 'Connection error', + '2': 'Unknown error', }), 'events': dict({ + '0': 'No Current', + '1': 'Power', }), 'last_event': None, 'name': 'Device 1', @@ -119,8 +124,13 @@ 'current_power': None, 'enabled': False, 'errors': dict({ + '0': 'No error', + '1': 'Connection error', + '2': 'Unknown error', }), 'events': dict({ + '0': 'No Current', + '1': 'Power', }), 'last_event': None, 'name': 'Device 2', @@ -131,8 +141,13 @@ 'current_power': None, 'enabled': False, 'errors': dict({ + '0': 'No error', + '1': 'Connection error', + '2': 'Unknown error', }), 'events': dict({ + '0': 'No Current', + '1': 'Power', }), 'last_event': None, 'name': 'Device 3', @@ -143,8 +158,13 @@ 'current_power': None, 'enabled': True, 'errors': dict({ + '0': 'No error', + '1': 'Connection error', + '2': 'Unknown error', }), 'events': dict({ + '0': 'No Current', + '1': 'Power', }), 'last_event': None, 'name': 'Device 4', diff --git a/tests/fixtures/device_data_1.json b/tests/fixtures/device_data_1.json index 65d1da0..e0f64f7 100644 --- a/tests/fixtures/device_data_1.json +++ b/tests/fixtures/device_data_1.json @@ -4,15 +4,12 @@ "119":"Device 1", "708":{ "0":"No Current", - "1":" Power", - "2":"", - "3":"" + "1":"Power" }, "709":{ - "0":" ", - "1":"No Current", - "2":"Power", - "3":"" + "0":"No error", + "1":"Connection error", + "2":"Unknown error" } } } diff --git a/tests/fixtures/device_data_2.json b/tests/fixtures/device_data_2.json index 82166c6..fe1d939 100644 --- a/tests/fixtures/device_data_2.json +++ b/tests/fixtures/device_data_2.json @@ -4,15 +4,12 @@ "119":"Device 2", "708":{ "0":"No Current", - "1":" Power", - "2":"", - "3":"" + "1":"Power" }, "709":{ - "0":" ", - "1":"No Current", - "2":"Power", - "3":"" + "0":"No error", + "1":"Connection error", + "2":"Unknown error" } } } diff --git a/tests/fixtures/device_data_3.json b/tests/fixtures/device_data_3.json index 2bc8540..be4ad37 100644 --- a/tests/fixtures/device_data_3.json +++ b/tests/fixtures/device_data_3.json @@ -4,15 +4,12 @@ "119":"Device 3", "708":{ "0":"No Current", - "1":" Power", - "2":"", - "3":"" + "1":"Power" }, "709":{ - "0":" ", - "1":"No Current", - "2":"Power", - "3":"" + "0":"No error", + "1":"Connection error", + "2":"Unknown error" } } } diff --git a/tests/fixtures/device_data_4.json b/tests/fixtures/device_data_4.json index 492c66e..f439838 100644 --- a/tests/fixtures/device_data_4.json +++ b/tests/fixtures/device_data_4.json @@ -4,15 +4,12 @@ "119":"Device 4", "708":{ "0":"No Current", - "1":" Power", - "2":"", - "3":"" + "1":"Power" }, "709":{ - "0":" ", - "1":"No Current", - "2":"Power", - "3":"" + "0":"No error", + "1":"Connection error", + "2":"Unknown error" } } } From 6cf3d2def5bf6bf888ff071688cf55b52df227c7 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 18:55:15 +0000 Subject: [PATCH 13/26] linter --- src/solarlog_cli/solarlog_client.py | 11 ++++++----- src/solarlog_cli/solarlog_connector.py | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 0a50bb3..216b7af 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -288,7 +288,7 @@ async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: return (fw_version, fw_date) - async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str,dict,dict]]: + async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str, dict, dict]]: """Get list of all connected devices. Return value is a dict with name, possible events and error codes per device.""" @@ -303,11 +303,12 @@ async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple for key, value in raw_data.items(): if value != "Err": # get name of the inverter + query = ( + f'{{ "141": {{ "{key}": {{ "119": null, "708": null, ' + f'"709": null }} }} }}' + ) raw_data = await self.parse_http_response( - await self.execute_http_request( - f"""{{ "141": {{ "{key}": {{ "119": null, "708": null, "709": null }} }} }}""", # pylint: disable=line-too-long - timeout=timeout, - ) + await self.execute_http_request(query, timeout=timeout) ) device_list |= {int(key): ( raw_data["141"][key]["119"], diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index abe959b..cfd7d18 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -164,7 +164,11 @@ async def update_device_list(self, timeout: float | None = None) -> dict[int, In devices = await self.client.get_device_list(timeout) self._device_list = { - key: InverterData(name=value[0],enabled=self.device(key).enabled,errors=value[2],events=value[1]) + key: InverterData( + name=value[0], + enabled=self.device(key).enabled,errors=value[2], + events=value[1] + ) for key, value in devices.items() } _LOGGER.debug("Device list updated: %s",self._device_list) From 86dbd81f529ab243d1e0d63deaccd62cb73f6718 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 18:56:08 +0000 Subject: [PATCH 14/26] update coverage --- .coverage | Bin 53248 -> 53248 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.coverage b/.coverage index 255234970274fe724fdddf961273b905e3b88fcd..e0833e7fe63ca8d12bc9845466fbd2bdfbef0e0e 100644 GIT binary patch delta 65 zcmV-H0KWf#paX!Q1F!~w41@p=`47bppAVJ~gtHM4au1V*k2erkrF`$J)>>8dbut13 X0SRW4%#SAr%^v0Klc@o-`Hu%cF~%BF delta 64 zcmV-G0Kfl$paX!Q1F!~w421v>`47eqpbwW1g|iV5a}Se*k2eri%J;r%tyNWD2QdN! W0SRQ2%a11pJ&$ts$<(v?j|V^izZoO| From 9c7c6ce3a679f915d2187090a25381f2601ccc64 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 19:00:33 +0000 Subject: [PATCH 15/26] split line --- src/solarlog_cli/solarlog_client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 216b7af..e29d4f9 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -288,7 +288,10 @@ async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: return (fw_version, fw_date) - async def get_device_list(self, timeout: float | None = None) -> dict[int, tuple[str, dict, dict]]: + async def get_device_list( + self, + timeout: float | None = None + ) -> dict[int, tuple[str, dict, dict]]: """Get list of all connected devices. Return value is a dict with name, possible events and error codes per device.""" From a4904e588e7ad529ff511ea2a2d5c7389e7f64d2 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 19:02:36 +0000 Subject: [PATCH 16/26] delete whitespace --- src/solarlog_cli/solarlog_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index e29d4f9..c6f5f81 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -289,7 +289,7 @@ async def get_firmware(self, timeout: float | None = None) -> tuple[str, date]: return (fw_version, fw_date) async def get_device_list( - self, + self, timeout: float | None = None ) -> dict[int, tuple[str, dict, dict]]: """Get list of all connected devices. From 7462e97c4c192e79d966125a011624685425e7d5 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:12:55 +0000 Subject: [PATCH 17/26] First commit --- tests/__snapshots__/test_solarlog_cli.ambr | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/__snapshots__/test_solarlog_cli.ambr b/tests/__snapshots__/test_solarlog_cli.ambr index 270b80a..553d972 100644 --- a/tests/__snapshots__/test_solarlog_cli.ambr +++ b/tests/__snapshots__/test_solarlog_cli.ambr @@ -124,6 +124,7 @@ 'current_power': None, 'enabled': False, 'errors': dict({ +<<<<<<< HEAD '0': 'No error', '1': 'Connection error', '2': 'Unknown error', @@ -131,6 +132,10 @@ 'events': dict({ '0': 'No Current', '1': 'Power', +======= + }), + 'events': dict({ +>>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 2', @@ -141,6 +146,7 @@ 'current_power': None, 'enabled': False, 'errors': dict({ +<<<<<<< HEAD '0': 'No error', '1': 'Connection error', '2': 'Unknown error', @@ -148,6 +154,10 @@ 'events': dict({ '0': 'No Current', '1': 'Power', +======= + }), + 'events': dict({ +>>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 3', @@ -158,6 +168,7 @@ 'current_power': None, 'enabled': True, 'errors': dict({ +<<<<<<< HEAD '0': 'No error', '1': 'Connection error', '2': 'Unknown error', @@ -165,6 +176,10 @@ 'events': dict({ '0': 'No Current', '1': 'Power', +======= + }), + 'events': dict({ +>>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 4', From 60abf7c1bcd44ce8ee0a887d243965550ff1827f Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Wed, 13 May 2026 19:25:47 +0000 Subject: [PATCH 18/26] update coverage --- .coverage | Bin 53248 -> 53248 bytes tests/__snapshots__/test_solarlog_cli.ambr | 15 --------------- 2 files changed, 15 deletions(-) diff --git a/.coverage b/.coverage index e0833e7fe63ca8d12bc9845466fbd2bdfbef0e0e..05724362a798b4a60ef5f57cbc902a88a41f7563 100644 GIT binary patch delta 23 fcmZozz}&EadBewkj)>fE8tL>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 2', @@ -146,7 +141,6 @@ 'current_power': None, 'enabled': False, 'errors': dict({ -<<<<<<< HEAD '0': 'No error', '1': 'Connection error', '2': 'Unknown error', @@ -154,10 +148,6 @@ 'events': dict({ '0': 'No Current', '1': 'Power', -======= - }), - 'events': dict({ ->>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 3', @@ -168,7 +158,6 @@ 'current_power': None, 'enabled': True, 'errors': dict({ -<<<<<<< HEAD '0': 'No error', '1': 'Connection error', '2': 'Unknown error', @@ -176,10 +165,6 @@ 'events': dict({ '0': 'No Current', '1': 'Power', -======= - }), - 'events': dict({ ->>>>>>> b04adda (First commit) }), 'last_event': None, 'name': 'Device 4', From 6cd42d2e00f9cda2cc2dd66162cd774649aebe94 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Thu, 14 May 2026 06:14:24 +0000 Subject: [PATCH 19/26] update data type --- src/solarlog_cli/solarlog_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_models.py b/src/solarlog_cli/solarlog_models.py index fd1ee17..f2c6781 100644 --- a/src/solarlog_cli/solarlog_models.py +++ b/src/solarlog_cli/solarlog_models.py @@ -79,7 +79,7 @@ class SolarlogData(DataClassDictMixin): #extended data battery_data: BatteryData | None = None firmware_date: date | None = None - firmware_version: int | None = None + firmware_version: str | None = None inverter_data: dict[int, InverterData] = field(default_factory=dict) production_year: float | None = None self_consumption_year: float | None = None From e9574863435c4dfd329eb35083647ecfa005e84b Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Thu, 14 May 2026 11:03:00 +0200 Subject: [PATCH 20/26] Add .coverage to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b3054f9..2ca946c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ src/solarlog_cli/__pycache__ tests/__pycache__ +.coverage .mypy_cache .pytest_cache .gitignore From 99abe852e3ed6793e41997910ceacc6597c752f6 Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Thu, 14 May 2026 09:11:30 +0000 Subject: [PATCH 21/26] Remove .coverage --- .coverage | Bin 53248 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 05724362a798b4a60ef5f57cbc902a88a41f7563..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI)TWB0r7zgl~oxOB7*-WIAC5DoND9O@XnkrZoOwu-BZ9rRlRod- zY%ZwC)?$6|MJPTAeUd&DedvqgOP{oliVsx^MW1RBS`n;JE7tGKWiM&Aq=C|={D-~F z&YW}RJHK<8o!w;L*gd1WoRuuw&|LPc)GNudG|8AGNlCh==^ic#T8V})=vi)A@3fkf zMt6OZR`*NY@q?0jB>kj1(p~QUv+J|&t;u~|dSW;^N;|MX00Izzz+)p&-`$RtqGQy^qZOyYPw^>QAaHg9@y`Z_e zS!V7m4^!71UKHmn+D2zBk#N+@FV=E;k#f1bOhr^}-O%hsc8o6$dkKQ0xqQL(H&7v5 zFPkC-8w#>#naxYw=4OFAeiTD`F`LSM@O~$wq_P)xiiePMhy!V#kt`6wY4o{%(YlWH@#|_j_&G~$@l^<)LdR%wt~X~EnEn% zwKi1NQBk3ze$L=nEjrE2%j67_2boJ0oN0xu&5YvEl-Y~ftQmPCf4gQ>MdMHo|G=)* zDQBm=t`$^v#ci`pTQ=+4)|?@^Xi+~`&D4+Ac%$)$Su^LbR}Iq|W}*_*1D@%MXI@w@ zOVlg*f^)n=Z=G^l&9%JOIcogeI6ZWqd}3W9GcX{ZI_`B8!IR7DuG3gawso4u<_=v) z?C;!f#3*U+Bw`d*D$FW*Lq_Cge&bMYZXGjYA%^{$ zUp$fN@0U*`y>1k=R`8T)>q22mE;EgWy9};5H9MS0?lnn*Nc}d)3ZIuvPnnPg8iD2d7S2cH*-5tBwOo0!^;+dB= zbP8OAkQ*0|@sqs~FXWFDPv4+PU)+_pkj0i}$h-LB$edQ8*`%uJHVs*tgZ(oA6A6nN zCD!s*&Gq`Pcle^`9okc>te2dI_v@D9=yX~Yr!D8ja4Q>})f_PkIfE>3 zSrx9C{<(p2>~!duy2HE}?mjC`G|O-nMH%I}K`*B88>~nTS#it>_PT(I_n?&d>6@iu2D# zJ2aQOy1|)9;sxV%ta;601)I~(;9TkjMq;x|qw@}LD=`B)g=Mvy8hT^4v z1TiD}&7Sw^!c@T*{Ftp7P7flR{HybzX5oDbo{x| z%_*u?M7<`d*VLP9vVsU20uX=z1Rwwb2tWV=5P$##AOL}eK)13{4nGEnq?G<-@F{@! z{y*09m85>B*42rgTeOM=0uX=z1Rwwb2tWV=5P$##An>RNOva*8JvKIP*~gqJ{j`uX z=2#WYu2|(DrHhxWgHYGH(e@W zdxT2pT16c6(;6l(xRyZE$2=O0?d0s#m> z00Izz00bZa0SG_<0uXp)1g4_(n7r~U-o~c`jqCr|bhMscO$lw+|IyXP(YXFsc0}u4 ztBIra`aiN&nNO}Y(xWQ2ESEIwnVx$sXDJ|9|xtNxiLJSN~G4 z(kd1RKmY;|fB*y_009U<00Izz00h>SK%71pkYh3LDH`>jB8vBvXRrVO From 7ce2cdd0781557c649de2135555795e2c5cc928c Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:51:01 +0000 Subject: [PATCH 22/26] Add token to body in API request --- src/solarlog_cli/solarlog_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index c6f5f81..96ce1c3 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -114,6 +114,9 @@ async def login(self) -> bool: self.session.cookie_jar.update_cookies( {"SolarLog": response.cookies["SolarLog"].value}) + + if not self._hashed_pwd: + self._token = response.cookies["SolarLog"].value if not self._hashed_pwd: self._token = response.cookies["SolarLog"].value From 349526612384e62aafdcd4e6834db9eb59bacfdc Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 30 Mar 2026 06:52:57 +0000 Subject: [PATCH 23/26] update coverage --- src/solarlog_cli/solarlog_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 96ce1c3..7a1f197 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -114,7 +114,7 @@ async def login(self) -> bool: self.session.cookie_jar.update_cookies( {"SolarLog": response.cookies["SolarLog"].value}) - + if not self._hashed_pwd: self._token = response.cookies["SolarLog"].value From f9fd2e462384f5e99a2f27692a251d3e9f7fb4ce Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Thu, 14 May 2026 10:03:32 +0000 Subject: [PATCH 24/26] Reduce noice in log --- src/solarlog_cli/solarlog_client.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/solarlog_cli/solarlog_client.py b/src/solarlog_cli/solarlog_client.py index 7a1f197..4699a66 100644 --- a/src/solarlog_cli/solarlog_client.py +++ b/src/solarlog_cli/solarlog_client.py @@ -72,7 +72,7 @@ async def login(self) -> bool: response = await self.execute_http_request(payload, "login") text = await response.text() - _LOGGER.debug("Response: %s", text) + _LOGGER.debug("Response to request for login: %s", text) if text.count("FAILED - User was wrong"): # Response means, that no password is required self.password = "" @@ -141,8 +141,8 @@ async def execute_http_request(self, body: str, path: str = "getjp", timeout: fl if self._token != "": body = f"token={self._token}; " + body - _LOGGER.debug("HTTP-request header: %s", header) - _LOGGER.debug("HTTP-request body: %s", body) + # _LOGGER.debug("HTTP-request header: %s", header) + # _LOGGER.debug("HTTP-request body: %s", body) try: response = await self.session.post( @@ -166,14 +166,14 @@ async def execute_http_request(self, body: str, path: str = "getjp", timeout: fl {"Content-Type": content_type, "response": text}, ) - _LOGGER.debug("HTTP-request successful: %s", response) + # _LOGGER.debug("HTTP-request successful: %s", response) return response async def parse_http_response(self, response: ClientResponse) -> dict[str, Any]: """Helper function to parse the HTTP response.""" text = await response.text(errors="replace") - _LOGGER.debug("Parsing http response: %s", text) + # _LOGGER.debug("Parsing http response: %s", text) if text.count('{"QUERY IMPOSSIBLE 000"}'): raise SolarLogUpdateError(f"Server response: {text}") From 7a4936c6f5742c07e04708b16778fa8706e9594d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:13:55 +0000 Subject: [PATCH 25/26] Fix typing error --- requirements.txt | 2 +- src/solarlog_cli/solarlog_connector.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1fac8f0..2e536bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp>=3.13.3 +aiohttp>=3.14.1 aioresponses == 0.7.6 bcrypt >= 4.3.0 mashumaro>=3.13 diff --git a/src/solarlog_cli/solarlog_connector.py b/src/solarlog_cli/solarlog_connector.py index cfd7d18..bd059fe 100644 --- a/src/solarlog_cli/solarlog_connector.py +++ b/src/solarlog_cli/solarlog_connector.py @@ -194,8 +194,8 @@ async def update_inverter_data(self, timeout: float | None = None) -> dict[int, if self._device_list.get(key,InverterData).enabled: self._device_list[key].consumption_year = float(value) - raw_data = await self.client.get_status_per_device(timeout) - for key, value in raw_data.items(): + raw_status_data = await self.client.get_status_per_device(timeout) + for key, value in raw_status_data.items(): key = int(key) if self._device_list.get(key,InverterData).enabled: self._device_list[key].status = value From 131a3094a0c3c484d8cd6c299181ba956414f43d Mon Sep 17 00:00:00 2001 From: dontinelli <73341522+dontinelli@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:34:39 +0000 Subject: [PATCH 26/26] migrate to aiointercept --- requirements.txt | 2 +- tests/test_solarlog_cli.py | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2e536bf..42dddfa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ aiohttp>=3.14.1 -aioresponses == 0.7.6 +aiointercept >= 0.1.8 bcrypt >= 4.3.0 mashumaro>=3.13 pytest-aio==1.9.0 diff --git a/tests/test_solarlog_cli.py b/tests/test_solarlog_cli.py index 76e6138..1dd8dd7 100644 --- a/tests/test_solarlog_cli.py +++ b/tests/test_solarlog_cli.py @@ -1,6 +1,6 @@ """Tests for solarlog_cli.""" -from aioresponses import aioresponses +from aiointercept import aiointercept from aiohttp import ClientSession import pytest @@ -25,7 +25,7 @@ ], ) async def test_connection( - responses: aioresponses, + responses: aiointercept, response_status: str, return_value: bool, ) -> None: @@ -50,7 +50,7 @@ async def test_connection( async def test_existing_session( - responses: aioresponses, + responses: aiointercept, ) -> None: """Test connection.""" responses.post( @@ -72,7 +72,7 @@ async def test_existing_session( async def test_extended_data_available( - responses: aioresponses, + responses: aiointercept, ) -> None: """Test extended data available.""" @@ -133,7 +133,7 @@ async def test_extended_data_available( assert solarlog_connector.client.session.closed -async def test_login_and_data_retreival(responses: aioresponses) -> None: +async def test_login_and_data_retreival(responses: aiointercept) -> None: """Test login into Solar-Log.""" responses.post( "http://solarlog.com/login", @@ -155,7 +155,7 @@ async def test_login_and_data_retreival(responses: aioresponses) -> None: assert solarlog_connector.client.session.closed -async def test_login_hashed_pwd(responses: aioresponses) -> None: +async def test_login_hashed_pwd(responses: aiointercept) -> None: """Test login into Solar-Log.""" responses.post( @@ -180,7 +180,7 @@ async def test_login_hashed_pwd(responses: aioresponses) -> None: assert solarlog_connector.client.session.closed -async def test_login_exceptions(responses: aioresponses) -> None: +async def test_login_exceptions(responses: aiointercept) -> None: """Test exceptions at login into Solar-Log.""" solarlog_connector = SolarLogConnector( "http://solarlog.com", password="pwd") @@ -218,7 +218,7 @@ async def test_login_exceptions(responses: aioresponses) -> None: async def test_update_data( - responses: aioresponses, + responses: aiointercept, snapshot: SnapshotAssertion ) -> None: """Test update data.""" @@ -275,7 +275,7 @@ async def test_update_data( async def test_update_data_without_battery( - responses: aioresponses, + responses: aiointercept, ) -> None: """Test update data.""" responses.post( @@ -342,7 +342,7 @@ async def test_update_data_without_battery( assert solarlog_connector.client.session.closed async def test_update_energy_data( - responses: aioresponses, + responses: aiointercept, snapshot: SnapshotAssertion ) -> None: """Test update energy data.""" @@ -382,7 +382,7 @@ async def test_update_energy_data( ], ) async def test_update_data_exceptions( - responses: aioresponses, + responses: aiointercept, status: int, request_timeout: bool, error: SolarLogError, @@ -411,7 +411,7 @@ async def test_update_data_exceptions( async def test_update_data_with_data_exceptions( - responses: aioresponses, + responses: aiointercept, ) -> None: """Test update data with exceptions due to data.""" responses.post( @@ -452,7 +452,7 @@ async def test_update_data_with_data_exceptions( async def test_update_device_list( - responses: aioresponses, + responses: aiointercept, snapshot: SnapshotAssertion ) -> None: """Test update device list.""" @@ -497,7 +497,7 @@ async def test_update_device_list( assert solarlog_connector.client.session.closed -async def test_enabled_devices(responses: aioresponses) -> None: +async def test_enabled_devices(responses: aiointercept) -> None: """Test enabled devices.""" responses.post( "http://solarlog.com/getjp",