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.
Files changed (43) hide show
  1. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.coverage +0 -0
  2. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/pylint.yml +1 -1
  3. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/python-publish.yml +1 -1
  4. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/ruff.yml +1 -1
  5. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/tests.yml +2 -2
  6. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.github/workflows/typing.yml +1 -1
  7. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/PKG-INFO +1 -1
  8. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/pyproject.toml +1 -1
  9. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/requirements.txt +1 -1
  10. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_client.py +23 -19
  11. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_connector.py +36 -19
  12. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_models.py +7 -1
  13. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/__snapshots__/test_solarlog_cli.ambr +6 -0
  14. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/test_solarlog_cli.py +32 -0
  15. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.devcontainer/devcontainer.json +0 -0
  16. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.gitignore +0 -0
  17. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.vscode/settings.json +0 -0
  18. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/.vscode/tasks.json +0 -0
  19. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/LICENSE +0 -0
  20. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/README.md +0 -0
  21. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/pytest.ini +0 -0
  22. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/__init__.py +0 -0
  23. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/py.typed +0 -0
  24. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/src/solarlog_cli/solarlog_exceptions.py +0 -0
  25. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/__init__.py +0 -0
  26. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/conftest.py +0 -0
  27. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data.json +0 -0
  28. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data_during_update.json +0 -0
  29. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/basic_data_no_power.json +0 -0
  30. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/battery_data.json +0 -0
  31. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/battery_data_without_battery.json +0 -0
  32. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_1.json +0 -0
  33. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_2.json +0 -0
  34. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_3.json +0 -0
  35. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_data_4.json +0 -0
  36. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_list.json +0 -0
  37. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/device_list_access_denied.json +0 -0
  38. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/energy_per_inverter.json +0 -0
  39. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/extended_data.json +0 -0
  40. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/power_per_inverter.json +0 -0
  41. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/fixtures/user_salts.json +0 -0
  42. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/ruff.toml +0 -0
  43. {solarlog_cli-0.6.1 → solarlog_cli-0.7.0}/tests/syrupy.py +0 -0
@@ -7,7 +7,7 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  strategy:
9
9
  matrix:
10
- python-version: ["3.12"]
10
+ python-version: ["3.13"]
11
11
  steps:
12
12
  - name: ⤵️ Checkout the repository
13
13
  uses: actions/checkout@v4.1.7
@@ -25,7 +25,7 @@ jobs:
25
25
  - name: Set up Python
26
26
  uses: actions/setup-python@v3
27
27
  with:
28
- python-version: '3.12'
28
+ python-version: '3.13'
29
29
  - name: Install dependencies
30
30
  run: |
31
31
  python -m pip install --upgrade pip
@@ -18,7 +18,7 @@ jobs:
18
18
  - name: 🏗 Set up Python
19
19
  uses: actions/setup-python@v5.0.0
20
20
  with:
21
- python-version: "3.12"
21
+ python-version: "3.13"
22
22
  cache: "pip"
23
23
  - name: 🏗 Install requirements
24
24
  run: python3 -m pip install -r requirements.txt
@@ -9,7 +9,7 @@ on:
9
9
  workflow_dispatch:
10
10
 
11
11
  env:
12
- DEFAULT_PYTHON: "3.12"
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.12"]
20
+ python: ["3.13"]
21
21
  steps:
22
22
  - name: ⤵️ Check out code from GitHub
23
23
  uses: actions/checkout@v4.1.7
@@ -10,7 +10,7 @@ on:
10
10
  - main
11
11
 
12
12
  env:
13
- DEFAULT_PYTHON: "3.12"
13
+ DEFAULT_PYTHON: "3.13"
14
14
 
15
15
  jobs:
16
16
  mypy:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solarlog_cli
3
- Version: 0.6.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "solarlog_cli"
3
- version = "0.6.1"
3
+ version = "0.7.0"
4
4
  authors = [
5
5
  { name="dontinelli", email="73341522+dontinelli@users.noreply.github.com" },
6
6
  ]
@@ -1,4 +1,4 @@
1
- aiohttp>=3.9.5
1
+ aiohttp>=3.13.3
2
2
  aioresponses == 0.7.6
3
3
  bcrypt >= 4.3.0
4
4
  mashumaro>=3.13
@@ -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({"SolarLog": response.cookies["SolarLog"].value})
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", response.cookies["SolarLog"].value)
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, data: SolarlogData) -> SolarlogData:
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
- data.production_year = raw_data["878"][-1][1]
262
- data.self_consumption_year = raw_data["878"][-1][3]
263
+ return EnergyData(
264
+ production=raw_data["878"][-1][1],
265
+ self_consumption=raw_data["878"][-1][3]
266
+ )
263
267
 
264
- return data
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 update_device_list(self) -> dict[int, InverterData]:
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
@@ -87,3 +87,9 @@
87
87
  }),
88
88
  })
89
89
  # ---
90
+ # name: test_update_energy_data[asyncio]
91
+ dict({
92
+ 'production': 8852462,
93
+ 'self_consumption': 2071,
94
+ })
95
+ # ---
@@ -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