solarlog-cli 0.1.6__tar.gz → 0.2.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.2.0/.coverage +0 -0
- solarlog_cli-0.2.0/.devcontainer/devcontainer.json +44 -0
- solarlog_cli-0.2.0/.github/workflows/pylint.yml +27 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/.github/workflows/ruff.yml +8 -11
- solarlog_cli-0.2.0/.github/workflows/tests.yml +70 -0
- solarlog_cli-0.2.0/.github/workflows/typing.yml +33 -0
- solarlog_cli-0.2.0/.gitignore +6 -0
- solarlog_cli-0.2.0/.vscode/settings.json +6 -0
- solarlog_cli-0.2.0/.vscode/tasks.json +78 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/PKG-INFO +2 -1
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/pyproject.toml +2 -1
- solarlog_cli-0.2.0/pytest.ini +2 -0
- solarlog_cli-0.2.0/requirements.txt +6 -0
- solarlog_cli-0.2.0/src/solarlog_cli/py.typed +0 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/src/solarlog_cli/solarlog_client.py +35 -38
- solarlog_cli-0.2.0/src/solarlog_cli/solarlog_connector.py +142 -0
- solarlog_cli-0.2.0/src/solarlog_cli/solarlog_models.py +52 -0
- solarlog_cli-0.2.0/tests/__init__.py +9 -0
- solarlog_cli-0.2.0/tests/__snapshots__/test_solarlog_cli.ambr +83 -0
- solarlog_cli-0.2.0/tests/conftest.py +22 -0
- solarlog_cli-0.2.0/tests/fixtures/basic_data.json +23 -0
- solarlog_cli-0.2.0/tests/fixtures/basic_data_during_update.json +23 -0
- solarlog_cli-0.2.0/tests/fixtures/basic_data_no_power.json +23 -0
- solarlog_cli-0.2.0/tests/fixtures/device_data_1.json +7 -0
- solarlog_cli-0.2.0/tests/fixtures/device_data_2.json +7 -0
- solarlog_cli-0.2.0/tests/fixtures/device_data_3.json +7 -0
- solarlog_cli-0.2.0/tests/fixtures/device_data_4.json +7 -0
- solarlog_cli-0.2.0/tests/fixtures/device_list.json +9 -0
- solarlog_cli-0.2.0/tests/fixtures/energy_per_inverter.json +9 -0
- solarlog_cli-0.2.0/tests/fixtures/extended_data.json +9 -0
- solarlog_cli-0.2.0/tests/fixtures/power_per_inverter.json +8 -0
- solarlog_cli-0.2.0/tests/ruff.toml +15 -0
- solarlog_cli-0.2.0/tests/syrupy.py +67 -0
- solarlog_cli-0.2.0/tests/test_solarlog_cli.py +219 -0
- solarlog_cli-0.1.6/.github/workflows/pylint.yml +0 -24
- solarlog_cli-0.1.6/.gitignore +0 -1
- solarlog_cli-0.1.6/requirements.txt +0 -1
- solarlog_cli-0.1.6/src/solarlog_cli/solarlog_connector.py +0 -129
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/.github/workflows/python-publish.yml +0 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/LICENSE +0 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/README.md +0 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/src/solarlog_cli/__init__.py +0 -0
- {solarlog_cli-0.1.6 → solarlog_cli-0.2.0}/src/solarlog_cli/solarlog_exceptions.py +0 -0
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-in-docker
|
|
3
|
+
{
|
|
4
|
+
"name": "Docker in Docker",
|
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
|
|
7
|
+
|
|
8
|
+
"features": {
|
|
9
|
+
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
|
10
|
+
"moby": true,
|
|
11
|
+
"azureDnsAutoDetection": true,
|
|
12
|
+
"installDockerBuildx": true,
|
|
13
|
+
"installDockerComposeSwitch": true,
|
|
14
|
+
"version": "latest"
|
|
15
|
+
},
|
|
16
|
+
"ghcr.io/devcontainers/features/github-cli:1": {
|
|
17
|
+
"installDirectlyFromGitHubRelease": true,
|
|
18
|
+
"version": "latest"
|
|
19
|
+
},
|
|
20
|
+
"ghcr.io/devcontainers/features/python:1": {
|
|
21
|
+
"installTools": true,
|
|
22
|
+
"version": "latest"
|
|
23
|
+
},
|
|
24
|
+
"ghcr.io/devcontainers-community/npm-features/prettier:1": {
|
|
25
|
+
"version": "latest"
|
|
26
|
+
},
|
|
27
|
+
"ghcr.io/hspaans/devcontainer-features/pytest:1": {
|
|
28
|
+
"version": "latest",
|
|
29
|
+
"plugins": "pytest-testinfra"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
34
|
+
// "forwardPorts": [],
|
|
35
|
+
|
|
36
|
+
// Use 'postCreateCommand' to run commands after the container is created.
|
|
37
|
+
// "postCreateCommand": "docker --version",
|
|
38
|
+
|
|
39
|
+
// Configure tool-specific properties.
|
|
40
|
+
// "customizations": {},
|
|
41
|
+
|
|
42
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
|
43
|
+
// "remoteUser": "root"
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Pylint
|
|
2
|
+
|
|
3
|
+
on: [push]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
python-version: ["3.12"]
|
|
11
|
+
steps:
|
|
12
|
+
- name: ⤵️ Checkout the repository
|
|
13
|
+
uses: actions/checkout@v4.1.7
|
|
14
|
+
- name: 🏗 Set up Python ${{ matrix.python-version }}
|
|
15
|
+
uses: actions/setup-python@v5.0.0
|
|
16
|
+
with:
|
|
17
|
+
python-version: ${{ matrix.python-version }}
|
|
18
|
+
cache: "pip"
|
|
19
|
+
- name: 🏗 Install requirement
|
|
20
|
+
run: |
|
|
21
|
+
python -m pip install --upgrade pip
|
|
22
|
+
pip install -r requirements.txt
|
|
23
|
+
- name: 🏗 Install pylint
|
|
24
|
+
run: python3 -m pip install pylint
|
|
25
|
+
- name: 🚀 Analysing the code with pylint
|
|
26
|
+
run: |
|
|
27
|
+
pylint $(git ls-files '*.py')
|
|
@@ -13,19 +13,16 @@ jobs:
|
|
|
13
13
|
name: "Ruff"
|
|
14
14
|
runs-on: "ubuntu-latest"
|
|
15
15
|
steps:
|
|
16
|
-
- name:
|
|
17
|
-
uses:
|
|
18
|
-
|
|
19
|
-
- name: "Set up Python"
|
|
16
|
+
- name: ⤵️ Checkout the repository
|
|
17
|
+
uses: actions/checkout@v4.1.7
|
|
18
|
+
- name: 🏗 Set up Python
|
|
20
19
|
uses: actions/setup-python@v5.0.0
|
|
21
20
|
with:
|
|
22
21
|
python-version: "3.12"
|
|
23
22
|
cache: "pip"
|
|
24
|
-
|
|
25
|
-
- name: "Install requirements"
|
|
23
|
+
- name: 🏗 Install requirements
|
|
26
24
|
run: python3 -m pip install -r requirements.txt
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
python3 -m ruff check ./src/solarlog_cli/*
|
|
25
|
+
- name: 🏗 Install ruff
|
|
26
|
+
run: python3 -m pip install ruff
|
|
27
|
+
- name: 🚀 Run
|
|
28
|
+
run: python3 -m ruff check ./src/solarlog_cli/*
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
name: Testing
|
|
2
|
+
|
|
3
|
+
# yamllint disable-line rule:truthy
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
DEFAULT_PYTHON: "3.12"
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
pytest:
|
|
16
|
+
name: Python ${{ matrix.python }}
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
strategy:
|
|
19
|
+
matrix:
|
|
20
|
+
python: ["3.12"]
|
|
21
|
+
steps:
|
|
22
|
+
- name: ⤵️ Check out code from GitHub
|
|
23
|
+
uses: actions/checkout@v4.1.7
|
|
24
|
+
- name: 🏗 Set up Python ${{ matrix.python }}
|
|
25
|
+
id: python
|
|
26
|
+
uses: actions/setup-python@v5.1.1
|
|
27
|
+
with:
|
|
28
|
+
python-version: ${{ matrix.python }}
|
|
29
|
+
cache: "pip"
|
|
30
|
+
- name: 🏗 Install dependencies
|
|
31
|
+
run: python3 -m pip install -r requirements.txt
|
|
32
|
+
- name: 🏗 Install pytest
|
|
33
|
+
run: python3 -m pip install pytest pytest-cov pytest-aio
|
|
34
|
+
- name: 🚀 Run pytest
|
|
35
|
+
run: pytest --cov src tests
|
|
36
|
+
- name: ⬆️ Upload coverage artifact
|
|
37
|
+
uses: actions/upload-artifact@v4.3.6
|
|
38
|
+
with:
|
|
39
|
+
name: coverage-${{ matrix.python }}
|
|
40
|
+
path: .coverage
|
|
41
|
+
|
|
42
|
+
coverage:
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
needs: pytest
|
|
45
|
+
steps:
|
|
46
|
+
- name: ⤵️ Check out code from GitHub
|
|
47
|
+
uses: actions/checkout@v4.1.7
|
|
48
|
+
with:
|
|
49
|
+
fetch-depth: 0
|
|
50
|
+
- name: ⬇️ Download coverage data
|
|
51
|
+
uses: actions/download-artifact@v4.1.8
|
|
52
|
+
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
53
|
+
id: python
|
|
54
|
+
uses: actions/setup-python@v5.1.1
|
|
55
|
+
with:
|
|
56
|
+
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
57
|
+
cache: "pip"
|
|
58
|
+
- name: 🏗 Install dependencies
|
|
59
|
+
run: python3 -m pip install -r requirements.txt
|
|
60
|
+
- name: 🏗 Install coverage
|
|
61
|
+
run: python3 -m pip install coverage
|
|
62
|
+
- name: 🚀 Process coverage results
|
|
63
|
+
run: |
|
|
64
|
+
coverage combine coverage*/.coverage*
|
|
65
|
+
coverage xml -i
|
|
66
|
+
- name: 🚀 Upload coverage report
|
|
67
|
+
uses: codecov/codecov-action@v4.5.0
|
|
68
|
+
with:
|
|
69
|
+
fail_ci_if_error: true
|
|
70
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Typing
|
|
2
|
+
|
|
3
|
+
# yamllint disable-line rule:truthy
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- main
|
|
11
|
+
|
|
12
|
+
env:
|
|
13
|
+
DEFAULT_PYTHON: "3.12"
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
mypy:
|
|
17
|
+
name: mypy
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- name: ⤵️ Check out code from GitHub
|
|
21
|
+
uses: actions/checkout@v4.1.7
|
|
22
|
+
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
23
|
+
id: python
|
|
24
|
+
uses: actions/setup-python@v5.1.1
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
27
|
+
cache: "pip"
|
|
28
|
+
- name: 🏗 Install dependencies
|
|
29
|
+
run: python3 -m pip install -r requirements.txt
|
|
30
|
+
- name: 🏗 Install mypy
|
|
31
|
+
run: python3 -m pip install mypy
|
|
32
|
+
- name: 🚀 Run mypy
|
|
33
|
+
run: MYPYPATH=src mypy --namespace-packages --explicit-package-bases src tests
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"label": "Pytest",
|
|
6
|
+
"type": "shell",
|
|
7
|
+
"command": "pytest tests",
|
|
8
|
+
"dependsOn": ["Install all Requirements"],
|
|
9
|
+
"group": {
|
|
10
|
+
"kind": "test",
|
|
11
|
+
"isDefault": true
|
|
12
|
+
},
|
|
13
|
+
"presentation": {
|
|
14
|
+
"reveal": "always",
|
|
15
|
+
"panel": "new"
|
|
16
|
+
},
|
|
17
|
+
"problemMatcher": []
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"label": "Ruff",
|
|
21
|
+
"type": "shell",
|
|
22
|
+
"command": "ruff check src/solarlog_cli tests",
|
|
23
|
+
"group": {
|
|
24
|
+
"kind": "test",
|
|
25
|
+
"isDefault": true
|
|
26
|
+
},
|
|
27
|
+
"presentation": {
|
|
28
|
+
"reveal": "always",
|
|
29
|
+
"panel": "new"
|
|
30
|
+
},
|
|
31
|
+
"problemMatcher": []
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"label": "Pylint",
|
|
35
|
+
"type": "shell",
|
|
36
|
+
"command": "pylint src tests",
|
|
37
|
+
"dependsOn": ["Install all Requirements"],
|
|
38
|
+
"group": {
|
|
39
|
+
"kind": "test",
|
|
40
|
+
"isDefault": true
|
|
41
|
+
},
|
|
42
|
+
"presentation": {
|
|
43
|
+
"reveal": "always",
|
|
44
|
+
"panel": "new"
|
|
45
|
+
},
|
|
46
|
+
"problemMatcher": []
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"label": "Code Coverage",
|
|
50
|
+
"detail": "Generate code coverage report.",
|
|
51
|
+
"type": "shell",
|
|
52
|
+
"command": "pytest tests --cov=solarlog_cli --cov-report term-missing --durations-min=1 --durations=0",
|
|
53
|
+
"group": {
|
|
54
|
+
"kind": "test",
|
|
55
|
+
"isDefault": true
|
|
56
|
+
},
|
|
57
|
+
"presentation": {
|
|
58
|
+
"reveal": "always",
|
|
59
|
+
"panel": "new"
|
|
60
|
+
},
|
|
61
|
+
"problemMatcher": []
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"label": "Install all Requirements",
|
|
65
|
+
"type": "shell",
|
|
66
|
+
"command": "pip install -r requirements.txt",
|
|
67
|
+
"group": {
|
|
68
|
+
"kind": "build",
|
|
69
|
+
"isDefault": true
|
|
70
|
+
},
|
|
71
|
+
"presentation": {
|
|
72
|
+
"reveal": "always",
|
|
73
|
+
"panel": "new"
|
|
74
|
+
},
|
|
75
|
+
"problemMatcher": []
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: solarlog_cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -15,6 +15,7 @@ Classifier: Operating System :: OS Independent
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Requires-Python: >=3.12
|
|
17
17
|
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: mashumaro>=3.13
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
# Solar-Log-Cli
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "solarlog_cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="dontinelli", email="73341522+dontinelli@users.noreply.github.com" },
|
|
6
6
|
]
|
|
@@ -9,6 +9,7 @@ readme = "README.md"
|
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
dependencies = [
|
|
11
11
|
"aiohttp",
|
|
12
|
+
"mashumaro>=3.13",
|
|
12
13
|
]
|
|
13
14
|
classifiers = [
|
|
14
15
|
"Programming Language :: Python :: 3",
|
|
File without changes
|
|
@@ -5,17 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
import json
|
|
8
|
-
import logging
|
|
9
8
|
from typing import Any
|
|
10
9
|
|
|
11
|
-
from aiohttp import ClientSession
|
|
10
|
+
from aiohttp import ClientSession, ClientTimeout
|
|
12
11
|
|
|
13
12
|
from .solarlog_exceptions import SolarLogConnectionError, SolarLogUpdateError
|
|
13
|
+
from .solarlog_models import SolarlogData
|
|
14
14
|
|
|
15
15
|
SOLARLOG_REQUEST_PAYLOAD = {801: {170: None}}
|
|
16
16
|
|
|
17
|
-
_LOGGER = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
17
|
|
|
20
18
|
class Client:
|
|
21
19
|
"""Client class to access Solar-Log."""
|
|
@@ -53,10 +51,12 @@ class Client:
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
try:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
response = await self.session.post(
|
|
55
|
+
url=url,
|
|
56
|
+
headers=header,
|
|
57
|
+
json=json_payload,
|
|
58
|
+
timeout=ClientTimeout(total=self.request_timeout)
|
|
59
|
+
)
|
|
60
60
|
except asyncio.TimeoutError as exception:
|
|
61
61
|
msg = f"Timeout occurred while connecting to Solar-Log at {self.host}"
|
|
62
62
|
raise SolarLogConnectionError(msg) from exception
|
|
@@ -85,31 +85,31 @@ class Client:
|
|
|
85
85
|
|
|
86
86
|
return json_response
|
|
87
87
|
|
|
88
|
-
async def get_basic_data(self) ->
|
|
88
|
+
async def get_basic_data(self) -> SolarlogData:
|
|
89
89
|
"""Get basic data from Solar-Log."""
|
|
90
90
|
|
|
91
91
|
raw_data: dict = await self.execute_http_request({801: {170: None}})
|
|
92
92
|
raw_data = raw_data["801"]["170"]
|
|
93
93
|
|
|
94
|
-
data =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
94
|
+
data = SolarlogData(
|
|
95
|
+
last_updated = datetime.strptime(raw_data["100"], "%d.%m.%y %H:%M:%S"),
|
|
96
|
+
power_ac = raw_data["101"],
|
|
97
|
+
power_dc = raw_data["102"],
|
|
98
|
+
voltage_ac = raw_data["103"],
|
|
99
|
+
voltage_dc = raw_data["104"],
|
|
100
|
+
yield_day = raw_data["105"],
|
|
101
|
+
yield_yesterday = raw_data["106"],
|
|
102
|
+
yield_month = raw_data["107"],
|
|
103
|
+
yield_year = raw_data["108"],
|
|
104
|
+
yield_total = raw_data["109"],
|
|
105
|
+
consumption_ac = raw_data["110"],
|
|
106
|
+
consumption_day = raw_data["111"],
|
|
107
|
+
consumption_yesterday = raw_data["112"],
|
|
108
|
+
consumption_month = raw_data["113"],
|
|
109
|
+
consumption_year = raw_data["114"],
|
|
110
|
+
consumption_total = raw_data["115"],
|
|
111
|
+
total_power = raw_data["116"],
|
|
112
|
+
)
|
|
113
113
|
|
|
114
114
|
return data
|
|
115
115
|
|
|
@@ -128,7 +128,7 @@ class Client:
|
|
|
128
128
|
raw_data: dict = await self.execute_http_request({854: None})
|
|
129
129
|
data_list = raw_data["854"][-1][-1]
|
|
130
130
|
|
|
131
|
-
data = {}
|
|
131
|
+
data: dict[int, float] = {}
|
|
132
132
|
|
|
133
133
|
for item in data_list:
|
|
134
134
|
if item != 0:
|
|
@@ -136,33 +136,30 @@ class Client:
|
|
|
136
136
|
|
|
137
137
|
return data
|
|
138
138
|
|
|
139
|
-
async def get_energy(self) ->
|
|
139
|
+
async def get_energy(self, data: SolarlogData) -> SolarlogData:
|
|
140
140
|
"""Get power data from Solar-Log"""
|
|
141
141
|
|
|
142
142
|
raw_data: dict = await self.execute_http_request({878: None})
|
|
143
143
|
|
|
144
|
-
data =
|
|
145
|
-
|
|
146
|
-
# "consumption_year": raw_data["878"][-1][2], # already in basic data
|
|
147
|
-
"self_consumption_year": raw_data["878"][-1][3],
|
|
148
|
-
}
|
|
144
|
+
data.production_year = raw_data["878"][-1][1]
|
|
145
|
+
data.self_consumption_year = raw_data["878"][-1][3]
|
|
149
146
|
|
|
150
147
|
return data
|
|
151
148
|
|
|
152
|
-
async def get_device_list(self) -> dict[int,
|
|
149
|
+
async def get_device_list(self) -> dict[int, str]:
|
|
153
150
|
"""Get list of all connected devices."""
|
|
154
151
|
|
|
155
152
|
# get list of all inverters connected to Solar-Log
|
|
156
153
|
raw_data: dict = await self.execute_http_request({740: None})
|
|
157
154
|
raw_data = raw_data["740"]
|
|
158
155
|
|
|
159
|
-
device_list: dict[int,
|
|
156
|
+
device_list: dict[int, str] = {}
|
|
160
157
|
|
|
161
158
|
for key, value in raw_data.items():
|
|
162
159
|
if value != "Err":
|
|
163
160
|
# get name of the inverter
|
|
164
161
|
raw_data = await self.execute_http_request({141: {key: {119: None}}})
|
|
165
|
-
device_list |= {int(key):
|
|
162
|
+
device_list |= {int(key): raw_data["141"][key]["119"]}
|
|
166
163
|
|
|
167
164
|
return device_list
|
|
168
165
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Connector class to manage access to Solar-Log."""
|
|
2
|
+
|
|
3
|
+
from datetime import timezone, tzinfo
|
|
4
|
+
import logging
|
|
5
|
+
from zoneinfo import ZoneInfo
|
|
6
|
+
|
|
7
|
+
from .solarlog_client import Client
|
|
8
|
+
from .solarlog_exceptions import SolarLogUpdateError
|
|
9
|
+
from .solarlog_models import SolarlogData, InverterData
|
|
10
|
+
|
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SolarLogConnector:
|
|
15
|
+
"""Connector class to access Solar-Log."""
|
|
16
|
+
|
|
17
|
+
def __init__( # pylint: disable=dangerous-default-value
|
|
18
|
+
self,
|
|
19
|
+
host: str,
|
|
20
|
+
extended_data: bool = False,
|
|
21
|
+
tz: str = "",
|
|
22
|
+
device_enabled: dict[int, bool] = {},
|
|
23
|
+
):
|
|
24
|
+
self.client = Client(host)
|
|
25
|
+
self.extended_data: bool = extended_data
|
|
26
|
+
|
|
27
|
+
self._device_list: dict[int, InverterData] = {}
|
|
28
|
+
if device_enabled != {}:
|
|
29
|
+
for key, value in device_enabled.items():
|
|
30
|
+
self._device_list |= {key: InverterData(enabled=value)}
|
|
31
|
+
|
|
32
|
+
self.timezone: tzinfo = timezone.utc if tz == "" else ZoneInfo(tz)
|
|
33
|
+
|
|
34
|
+
async def test_connection(self) -> bool:
|
|
35
|
+
"""Test if connection to Solar-Log works."""
|
|
36
|
+
|
|
37
|
+
return await self.client.test_connection()
|
|
38
|
+
|
|
39
|
+
async def update_data(self) -> SolarlogData:
|
|
40
|
+
"""Get data from Solar-Log."""
|
|
41
|
+
|
|
42
|
+
data: SolarlogData = await self.client.get_basic_data()
|
|
43
|
+
|
|
44
|
+
if data.last_updated.year == 1999:
|
|
45
|
+
raise SolarLogUpdateError(
|
|
46
|
+
"Invalid data returned (can happen after Solarlog restart)."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
data.last_updated = data.last_updated.replace(tzinfo=self.timezone)
|
|
50
|
+
|
|
51
|
+
_LOGGER.debug("Basic data updated: %s",data)
|
|
52
|
+
if self.extended_data:
|
|
53
|
+
data = await self.client.get_energy(data)
|
|
54
|
+
|
|
55
|
+
if self._device_list != {}:
|
|
56
|
+
data.inverter_data = await self.update_inverter_data()
|
|
57
|
+
|
|
58
|
+
_LOGGER.debug("Extended data updated: %s",data)
|
|
59
|
+
|
|
60
|
+
#calculated values (for downward compatibility)
|
|
61
|
+
data.alternator_loss = data.power_dc - data.power_ac
|
|
62
|
+
if data.power_dc != 0:
|
|
63
|
+
data.efficiency = data.power_ac / data.power_dc
|
|
64
|
+
if data.power_ac != 0:
|
|
65
|
+
data.usage = data.consumption_ac / data.power_ac
|
|
66
|
+
data.power_available = data.power_ac - data.consumption_ac
|
|
67
|
+
else:
|
|
68
|
+
data.usage = 0.0
|
|
69
|
+
data.power_available = 0.0
|
|
70
|
+
if data.total_power != 0:
|
|
71
|
+
data.capacity = data.power_dc / data.total_power
|
|
72
|
+
|
|
73
|
+
return data
|
|
74
|
+
|
|
75
|
+
async def update_device_list(self) -> dict[int, InverterData]:
|
|
76
|
+
"""Update list of devices."""
|
|
77
|
+
if not self.extended_data or self._device_list is None:
|
|
78
|
+
return {}
|
|
79
|
+
|
|
80
|
+
devices = await self.client.get_device_list()
|
|
81
|
+
|
|
82
|
+
self._device_list = {
|
|
83
|
+
key: InverterData(name=value,enabled=self.device(key).enabled)
|
|
84
|
+
for key, value in devices.items()
|
|
85
|
+
}
|
|
86
|
+
_LOGGER.debug("Device list: %s",self._device_list)
|
|
87
|
+
|
|
88
|
+
return self._device_list
|
|
89
|
+
|
|
90
|
+
async def update_inverter_data(self) -> dict[int, InverterData]:
|
|
91
|
+
"""Update device specific data."""
|
|
92
|
+
|
|
93
|
+
raw_data = await self.client.get_power_per_inverter()
|
|
94
|
+
for key, value in raw_data.items():
|
|
95
|
+
key = int(key)
|
|
96
|
+
if self._device_list.get(key,InverterData).enabled:
|
|
97
|
+
self._device_list[key].current_power = float(value)
|
|
98
|
+
|
|
99
|
+
raw_data = await self.client.get_energy_per_inverter()
|
|
100
|
+
for key, value in raw_data.items():
|
|
101
|
+
if self._device_list.get(key,InverterData).enabled:
|
|
102
|
+
self._device_list[key].consumption_year = float(value)
|
|
103
|
+
|
|
104
|
+
_LOGGER.debug("Inverter data updated: %s",self._device_list)
|
|
105
|
+
|
|
106
|
+
return self._device_list
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def host(self) -> str:
|
|
110
|
+
"""Host of Solar-Log."""
|
|
111
|
+
return self.client.host
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def device_list(self) -> dict[int, InverterData]:
|
|
115
|
+
"""List of all devices of Solar-Log."""
|
|
116
|
+
return self._device_list
|
|
117
|
+
|
|
118
|
+
def device(self, device_id: int) -> InverterData:
|
|
119
|
+
"""Get device data."""
|
|
120
|
+
return self._device_list.get(device_id, InverterData())
|
|
121
|
+
|
|
122
|
+
def device_name(self, device_id: int) -> str:
|
|
123
|
+
"""Get name of Solar-Log attached device."""
|
|
124
|
+
_LOGGER.debug("Device list: %s; id: %s",self._device_list, device_id)
|
|
125
|
+
|
|
126
|
+
return self._device_list.get(device_id, InverterData()).name
|
|
127
|
+
|
|
128
|
+
def device_enabled(self, device_id: int | None = None) -> bool | dict[int, bool]:
|
|
129
|
+
"""Get if device is enabled (if id is provided) or list of all devices."""
|
|
130
|
+
if device_id is None:
|
|
131
|
+
print("no device_id")
|
|
132
|
+
print(self._device_list)
|
|
133
|
+
return {key: value.enabled for key, value in self._device_list.items()}
|
|
134
|
+
return self._device_list[device_id].enabled
|
|
135
|
+
|
|
136
|
+
def set_enabled_devices(self, device_enabled: dict[int, bool]) -> None:
|
|
137
|
+
"""Set enabled devices."""
|
|
138
|
+
for key, value in device_enabled.items():
|
|
139
|
+
if self._device_list.get(key) is None:
|
|
140
|
+
self._device_list |= {key: InverterData(enabled=value)}
|
|
141
|
+
else:
|
|
142
|
+
self._device_list[key].enabled = value
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Models for SolarLog."""
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from mashumaro import DataClassDictMixin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class InverterData():
|
|
10
|
+
"""Inverter Data model."""
|
|
11
|
+
|
|
12
|
+
name: str = ""
|
|
13
|
+
enabled: bool = False
|
|
14
|
+
current_power: float | None = None
|
|
15
|
+
consumption_year: float | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class SolarlogData(DataClassDictMixin):
|
|
20
|
+
"""Basic Data model."""
|
|
21
|
+
|
|
22
|
+
# pylint: disable=too-many-instance-attributes
|
|
23
|
+
|
|
24
|
+
consumption_ac: float
|
|
25
|
+
consumption_day: float
|
|
26
|
+
consumption_month: float
|
|
27
|
+
consumption_total: float
|
|
28
|
+
consumption_yesterday: float
|
|
29
|
+
consumption_year: float
|
|
30
|
+
last_updated: datetime
|
|
31
|
+
power_ac: float
|
|
32
|
+
power_dc: float
|
|
33
|
+
total_power: float
|
|
34
|
+
voltage_ac: float
|
|
35
|
+
voltage_dc: float
|
|
36
|
+
yield_day: float
|
|
37
|
+
yield_yesterday: float
|
|
38
|
+
yield_month: float
|
|
39
|
+
yield_year: float
|
|
40
|
+
yield_total: float
|
|
41
|
+
|
|
42
|
+
#calculated values
|
|
43
|
+
alternator_loss: float | None = None
|
|
44
|
+
capacity: float | None = None
|
|
45
|
+
efficiency: float | None = None
|
|
46
|
+
power_available: float | None = None
|
|
47
|
+
usage: float | None = None
|
|
48
|
+
|
|
49
|
+
#extended data
|
|
50
|
+
inverter_data: dict[int, InverterData] | None = None
|
|
51
|
+
production_year: float | None = None
|
|
52
|
+
self_consumption_year: float | None = None
|