Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .coverage
Binary file not shown.
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
"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,
"python.createEnvironment.contentButton": "show"
}
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
"label": "Pytest",
"type": "shell",
"command": "pytest tests",
"command": "pytest tests --snapshot-update",
"dependsOn": ["Install all Requirements"],
"group": {
"kind": "test",
Expand Down
60 changes: 48 additions & 12 deletions src/solarlog_cli/solarlog_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -267,29 +267,65 @@ 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(
await self.execute_http_request('{ "740": null }', timeout=timeout)
)
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:
Expand Down
17 changes: 15 additions & 2 deletions src/solarlog_cli/solarlog_connector.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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_information(self, timeout: float | None = None) -> tuple[str, date]:
"""Update firmware data (version and release date)."""

return await self.client.get_firmware(timeout)

async def update_inverter_data(self, timeout: float | None = None) -> dict[int, InverterData]:
"""Update device specific data."""

Expand All @@ -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
Expand Down
21 changes: 18 additions & 3 deletions src/solarlog_cli/solarlog_models.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -63,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
63 changes: 63 additions & 0 deletions tests/__snapshots__/test_solarlog_cli.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -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')),
Expand All @@ -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,
}),
})
# ---
Expand Down
14 changes: 13 additions & 1 deletion tests/fixtures/device_data_1.json
Original file line number Diff line number Diff line change
@@ -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":""
}
}
}
}
Loading
Loading