solarlog-cli 0.6.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.coverage +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/pylint.yml +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/python-publish.yml +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/ruff.yml +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/tests.yml +2 -2
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/typing.yml +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/PKG-INFO +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/pyproject.toml +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/requirements.txt +1 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_client.py +23 -19
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_connector.py +36 -19
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_models.py +7 -1
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/__snapshots__/test_solarlog_cli.ambr +6 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/test_solarlog_cli.py +32 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.devcontainer/devcontainer.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.gitignore +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.vscode/settings.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.vscode/tasks.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/LICENSE +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/README.md +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/pytest.ini +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/__init__.py +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/py.typed +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_exceptions.py +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/__init__.py +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/conftest.py +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data_during_update.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data_no_power.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/battery_data.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/battery_data_without_battery.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_1.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_2.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_3.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_4.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_list.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_list_access_denied.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/energy_per_inverter.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/extended_data.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/power_per_inverter.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/user_salts.json +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/ruff.toml +0 -0
- {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/syrupy.py +0 -0
|
Binary file
|
|
@@ -9,7 +9,7 @@ on:
|
|
|
9
9
|
workflow_dispatch:
|
|
10
10
|
|
|
11
11
|
env:
|
|
12
|
-
DEFAULT_PYTHON: "3.
|
|
12
|
+
DEFAULT_PYTHON: "3.13"
|
|
13
13
|
|
|
14
14
|
jobs:
|
|
15
15
|
pytest:
|
|
@@ -17,7 +17,7 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
strategy:
|
|
19
19
|
matrix:
|
|
20
|
-
python: ["3.
|
|
20
|
+
python: ["3.13"]
|
|
21
21
|
steps:
|
|
22
22
|
- name: ⤵️ Check out code from GitHub
|
|
23
23
|
uses: actions/checkout@v4.1.7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solarlog_cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python library to access the Solar-Log
|
|
5
5
|
Project-URL: Repository, https://github.com/dontinelli/solarlog_cli.git
|
|
6
6
|
Project-URL: Issues, https://github.com/dontinelli/solarlog_cli/issues
|
|
@@ -17,7 +17,7 @@ from .solarlog_exceptions import (
|
|
|
17
17
|
SolarLogUpdateError,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
-
from .solarlog_models import SolarlogData
|
|
20
|
+
from .solarlog_models import EnergyData, SolarlogData
|
|
21
21
|
|
|
22
22
|
SOLARLOG_REQUEST_PAYLOAD = '{ "801": { "170": null } }'
|
|
23
23
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -111,15 +111,17 @@ class Client:
|
|
|
111
111
|
|
|
112
112
|
self._hashed_pwd = True
|
|
113
113
|
|
|
114
|
-
self.session.cookie_jar.update_cookies(
|
|
114
|
+
self.session.cookie_jar.update_cookies(
|
|
115
|
+
{"SolarLog": response.cookies["SolarLog"].value})
|
|
115
116
|
|
|
116
117
|
_LOGGER.debug("response: %s", text)
|
|
117
118
|
_LOGGER.debug("cookies: %s", response.cookies)
|
|
118
|
-
_LOGGER.debug("Login successful, token: %s",
|
|
119
|
+
_LOGGER.debug("Login successful, token: %s",
|
|
120
|
+
response.cookies["SolarLog"].value)
|
|
119
121
|
|
|
120
122
|
return True
|
|
121
123
|
|
|
122
|
-
async def execute_http_request(self, body: str, path: str = "getjp") -> ClientResponse:
|
|
124
|
+
async def execute_http_request(self, body: str, path: str = "getjp", timeout: float | None = None) -> ClientResponse: # pylint: disable=line-too-long
|
|
123
125
|
"""Helper function to process the HTTP Get call."""
|
|
124
126
|
if self.session is None:
|
|
125
127
|
self.session = ClientSession()
|
|
@@ -137,7 +139,7 @@ class Client:
|
|
|
137
139
|
url=url,
|
|
138
140
|
headers=header,
|
|
139
141
|
data=body,
|
|
140
|
-
timeout=ClientTimeout(total=self.request_timeout),
|
|
142
|
+
timeout=ClientTimeout(total=timeout or self.request_timeout),
|
|
141
143
|
)
|
|
142
144
|
except asyncio.TimeoutError as exception:
|
|
143
145
|
msg = f"Timeout occurred while connecting to Solar-Log at {self.host}"
|
|
@@ -211,22 +213,22 @@ class Client:
|
|
|
211
213
|
|
|
212
214
|
return data
|
|
213
215
|
|
|
214
|
-
async def get_battery_data(self) -> list[float]:
|
|
216
|
+
async def get_battery_data(self, timeout: float | None = None) -> list[float]:
|
|
215
217
|
"""Get battery data from Solar-Log"""
|
|
216
218
|
|
|
217
219
|
raw_data: dict = await self.parse_http_response(
|
|
218
|
-
await self.execute_http_request('{ "858": null }')
|
|
220
|
+
await self.execute_http_request('{ "858": null }', timeout=timeout)
|
|
219
221
|
)
|
|
220
222
|
|
|
221
223
|
data: list[float] = raw_data["858"]
|
|
222
224
|
|
|
223
225
|
return data
|
|
224
226
|
|
|
225
|
-
async def get_power_per_inverter(self) -> dict[int, float]:
|
|
227
|
+
async def get_power_per_inverter(self, timeout: float | None = None) -> dict[int, float]:
|
|
226
228
|
"""Get power data from Solar-Log"""
|
|
227
229
|
|
|
228
230
|
raw_data: dict = await self.parse_http_response(
|
|
229
|
-
await self.execute_http_request('{ "782": null }')
|
|
231
|
+
await self.execute_http_request('{ "782": null }', timeout=timeout)
|
|
230
232
|
)
|
|
231
233
|
|
|
232
234
|
data = {int(key): val for key,
|
|
@@ -234,11 +236,11 @@ class Client:
|
|
|
234
236
|
|
|
235
237
|
return data
|
|
236
238
|
|
|
237
|
-
async def get_energy_per_inverter(self) -> dict[int, float]:
|
|
239
|
+
async def get_energy_per_inverter(self, timeout: float | None = None) -> dict[int, float]:
|
|
238
240
|
"""Get power data from Solar-Log"""
|
|
239
241
|
|
|
240
242
|
raw_data: dict = await self.parse_http_response(
|
|
241
|
-
await self.execute_http_request('{ "854": null }')
|
|
243
|
+
await self.execute_http_request('{ "854": null }', timeout=timeout)
|
|
242
244
|
)
|
|
243
245
|
data_list = raw_data["854"][-1][-1]
|
|
244
246
|
|
|
@@ -250,25 +252,27 @@ class Client:
|
|
|
250
252
|
|
|
251
253
|
return data
|
|
252
254
|
|
|
253
|
-
async def get_energy(self,
|
|
255
|
+
async def get_energy(self, timeout: float | None = None) -> EnergyData | None:
|
|
254
256
|
"""Get energy data from Solar-Log"""
|
|
255
257
|
|
|
256
258
|
raw_data: dict = await self.parse_http_response(
|
|
257
|
-
await self.execute_http_request('{ "878": null }')
|
|
259
|
+
await self.execute_http_request('{ "878": null }', timeout=timeout)
|
|
258
260
|
)
|
|
259
261
|
|
|
260
262
|
if raw_data["878"] != "QUERY IMPOSSIBLE 000":
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
return EnergyData(
|
|
264
|
+
production=raw_data["878"][-1][1],
|
|
265
|
+
self_consumption=raw_data["878"][-1][3]
|
|
266
|
+
)
|
|
263
267
|
|
|
264
|
-
return
|
|
268
|
+
return None
|
|
265
269
|
|
|
266
|
-
async def get_device_list(self) -> dict[int, str]:
|
|
270
|
+
async def get_device_list(self, timeout: float | None = None) -> dict[int, str]:
|
|
267
271
|
"""Get list of all connected devices."""
|
|
268
272
|
|
|
269
273
|
# get list of all inverters connected to Solar-Log
|
|
270
274
|
raw_data: dict = await self.parse_http_response(
|
|
271
|
-
await self.execute_http_request('{ "740": null }')
|
|
275
|
+
await self.execute_http_request('{ "740": null }', timeout=timeout)
|
|
272
276
|
)
|
|
273
277
|
raw_data = raw_data["740"]
|
|
274
278
|
|
|
@@ -279,7 +283,7 @@ class Client:
|
|
|
279
283
|
# get name of the inverter
|
|
280
284
|
raw_data = await self.parse_http_response(
|
|
281
285
|
await self.execute_http_request(
|
|
282
|
-
f"""{{ "141": {{ "{key}": {{ "119": null }} }} }}"""
|
|
286
|
+
f"""{{ "141": {{ "{key}": {{ "119": null }} }} }}""", timeout=timeout
|
|
283
287
|
)
|
|
284
288
|
)
|
|
285
289
|
device_list |= {int(key): raw_data["141"][key]["119"]}
|
|
@@ -12,7 +12,7 @@ from .solarlog_exceptions import(
|
|
|
12
12
|
SolarLogConnectionError,
|
|
13
13
|
SolarLogUpdateError,
|
|
14
14
|
)
|
|
15
|
-
from .solarlog_models import BatteryData, InverterData, SolarlogData
|
|
15
|
+
from .solarlog_models import BatteryData, EnergyData, InverterData, SolarlogData
|
|
16
16
|
|
|
17
17
|
_LOGGER = logging.getLogger(__name__)
|
|
18
18
|
|
|
@@ -82,7 +82,28 @@ class SolarLogConnector:
|
|
|
82
82
|
return await self.client.login()
|
|
83
83
|
|
|
84
84
|
async def update_data(self) -> SolarlogData:
|
|
85
|
-
"""Get data from Solar-Log."""
|
|
85
|
+
"""Get all data from Solar-Log."""
|
|
86
|
+
|
|
87
|
+
data: SolarlogData = await self.update_basic_data()
|
|
88
|
+
|
|
89
|
+
if self.extended_data:
|
|
90
|
+
energy_data: EnergyData | None = await self.client.get_energy()
|
|
91
|
+
|
|
92
|
+
if energy_data is not None:
|
|
93
|
+
data.production_year = energy_data.production
|
|
94
|
+
data.self_consumption_year = energy_data.self_consumption
|
|
95
|
+
|
|
96
|
+
if self._device_list != {}:
|
|
97
|
+
data.inverter_data = await self.update_inverter_data()
|
|
98
|
+
|
|
99
|
+
data.battery_data = await self.update_battery_data()
|
|
100
|
+
|
|
101
|
+
_LOGGER.debug("Extended data updated: %s",data)
|
|
102
|
+
|
|
103
|
+
return data
|
|
104
|
+
|
|
105
|
+
async def update_basic_data(self) -> SolarlogData:
|
|
106
|
+
"""Get basic data from Solar-Log."""
|
|
86
107
|
|
|
87
108
|
data: SolarlogData = await self.client.get_basic_data()
|
|
88
109
|
|
|
@@ -94,15 +115,6 @@ class SolarLogConnector:
|
|
|
94
115
|
data.last_updated = data.last_updated.replace(tzinfo=self.timezone)
|
|
95
116
|
|
|
96
117
|
_LOGGER.debug("Basic data updated: %s",data)
|
|
97
|
-
if self.extended_data:
|
|
98
|
-
data = await self.client.get_energy(data)
|
|
99
|
-
|
|
100
|
-
if self._device_list != {}:
|
|
101
|
-
data.inverter_data = await self.update_inverter_data()
|
|
102
|
-
|
|
103
|
-
data.battery_data = await self.update_battery_data()
|
|
104
|
-
|
|
105
|
-
_LOGGER.debug("Extended data updated: %s",data)
|
|
106
118
|
|
|
107
119
|
#calculated values (for downward compatibility)
|
|
108
120
|
data.alternator_loss = data.power_dc - data.power_ac
|
|
@@ -119,11 +131,10 @@ class SolarLogConnector:
|
|
|
119
131
|
|
|
120
132
|
return data
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
async def update_battery_data(self) -> BatteryData | None:
|
|
134
|
+
async def update_battery_data(self, timeout: float | None = None) -> BatteryData | None:
|
|
124
135
|
"""Update device specific data."""
|
|
125
136
|
|
|
126
|
-
raw_data = await self.client.get_battery_data()
|
|
137
|
+
raw_data = await self.client.get_battery_data(timeout)
|
|
127
138
|
|
|
128
139
|
if raw_data == []:
|
|
129
140
|
return None
|
|
@@ -139,12 +150,18 @@ class SolarLogConnector:
|
|
|
139
150
|
|
|
140
151
|
return battery_data
|
|
141
152
|
|
|
142
|
-
async def
|
|
153
|
+
async def update_energy_data(self, timeout: float | None = None) -> EnergyData | None:
|
|
154
|
+
"""Update energy data (production and self consumption)."""
|
|
155
|
+
|
|
156
|
+
return await self.client.get_energy(timeout)
|
|
157
|
+
|
|
158
|
+
async def update_device_list(self, timeout: float | None = None) -> dict[int, InverterData]:
|
|
143
159
|
"""Update list of devices."""
|
|
160
|
+
|
|
144
161
|
if not self.extended_data:
|
|
145
162
|
return {}
|
|
146
163
|
|
|
147
|
-
devices = await self.client.get_device_list()
|
|
164
|
+
devices = await self.client.get_device_list(timeout)
|
|
148
165
|
|
|
149
166
|
self._device_list = {
|
|
150
167
|
key: InverterData(name=value,enabled=self.device(key).enabled)
|
|
@@ -154,16 +171,16 @@ class SolarLogConnector:
|
|
|
154
171
|
|
|
155
172
|
return self._device_list
|
|
156
173
|
|
|
157
|
-
async def update_inverter_data(self) -> dict[int, InverterData]:
|
|
174
|
+
async def update_inverter_data(self, timeout: float | None = None) -> dict[int, InverterData]:
|
|
158
175
|
"""Update device specific data."""
|
|
159
176
|
|
|
160
|
-
raw_data = await self.client.get_power_per_inverter()
|
|
177
|
+
raw_data = await self.client.get_power_per_inverter(timeout)
|
|
161
178
|
for key, value in raw_data.items():
|
|
162
179
|
key = int(key)
|
|
163
180
|
if self._device_list.get(key,InverterData).enabled:
|
|
164
181
|
self._device_list[key].current_power = float(value)
|
|
165
182
|
|
|
166
|
-
raw_data = await self.client.get_energy_per_inverter()
|
|
183
|
+
raw_data = await self.client.get_energy_per_inverter(timeout)
|
|
167
184
|
for key, value in raw_data.items():
|
|
168
185
|
if self._device_list.get(key,InverterData).enabled:
|
|
169
186
|
self._device_list[key].consumption_year = float(value)
|
|
@@ -13,6 +13,12 @@ class BatteryData():
|
|
|
13
13
|
level: float = 0
|
|
14
14
|
voltage: float = 0
|
|
15
15
|
|
|
16
|
+
@dataclass
|
|
17
|
+
class EnergyData():
|
|
18
|
+
"""Energy Data model."""
|
|
19
|
+
|
|
20
|
+
production: float | None = None
|
|
21
|
+
self_consumption: float | None = None
|
|
16
22
|
|
|
17
23
|
@dataclass
|
|
18
24
|
class InverterData():
|
|
@@ -21,7 +27,7 @@ class InverterData():
|
|
|
21
27
|
name: str = ""
|
|
22
28
|
enabled: bool = False
|
|
23
29
|
current_power: float | None = None
|
|
24
|
-
consumption_year: float | None= None
|
|
30
|
+
consumption_year: float | None = None
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
@dataclass
|
|
@@ -297,6 +297,38 @@ async def test_update_data_without_battery(
|
|
|
297
297
|
await solarlog_connector.client.close()
|
|
298
298
|
assert solarlog_connector.client.session.closed
|
|
299
299
|
|
|
300
|
+
async def test_update_energy_data(
|
|
301
|
+
responses: aioresponses,
|
|
302
|
+
snapshot: SnapshotAssertion
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Test update energy data."""
|
|
305
|
+
|
|
306
|
+
responses.post(
|
|
307
|
+
"http://solarlog.com/getjp",
|
|
308
|
+
body=load_fixture("extended_data.json"),
|
|
309
|
+
)
|
|
310
|
+
responses.post(
|
|
311
|
+
"http://solarlog.com/getjp",
|
|
312
|
+
body='{"878": "QUERY IMPOSSIBLE 000"}',
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
solarlog_connector = SolarLogConnector(
|
|
316
|
+
"http://solarlog.com",
|
|
317
|
+
True,
|
|
318
|
+
"UTC",
|
|
319
|
+
{0: True, 1: True, 2: False, 3: True},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
data = await solarlog_connector.update_energy_data()
|
|
323
|
+
|
|
324
|
+
assert data == snapshot
|
|
325
|
+
|
|
326
|
+
data = await solarlog_connector.update_energy_data()
|
|
327
|
+
|
|
328
|
+
assert data is None
|
|
329
|
+
|
|
330
|
+
await solarlog_connector.client.close()
|
|
331
|
+
assert solarlog_connector.client.session.closed
|
|
300
332
|
|
|
301
333
|
@pytest.mark.parametrize(
|
|
302
334
|
("status", "request_timeout", "error"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|