megalion-http 0.0.1__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.
- megalion_http-0.0.1/LICENSE +21 -0
- megalion_http-0.0.1/PKG-INFO +17 -0
- megalion_http-0.0.1/README.md +3 -0
- megalion_http-0.0.1/build/lib/megalion-http/__init__.py +0 -0
- megalion_http-0.0.1/build/lib/megalion-http/auth.py +23 -0
- megalion_http-0.0.1/build/lib/megalion-http/battery.py +146 -0
- megalion_http-0.0.1/build/lib/megalion-http/megalion.py +20 -0
- megalion_http-0.0.1/pyproject.toml +23 -0
- megalion_http-0.0.1/requirements.txt +1 -0
- megalion_http-0.0.1/src/megalion_http/__init__.py +1 -0
- megalion_http-0.0.1/src/megalion_http/auth.py +36 -0
- megalion_http-0.0.1/src/megalion_http/battery.py +149 -0
- megalion_http-0.0.1/src/megalion_http/megalion.py +29 -0
- megalion_http-0.0.1/src/megalion_http/user.py +27 -0
- megalion_http-0.0.1/src/megalion_http.egg-info/PKG-INFO +18 -0
- megalion_http-0.0.1/src/megalion_http.egg-info/SOURCES.txt +11 -0
- megalion_http-0.0.1/src/megalion_http.egg-info/dependency_links.txt +1 -0
- megalion_http-0.0.1/src/megalion_http.egg-info/top_level.txt +1 -0
- megalion_http-0.0.1/test.py +22 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Klara Modin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: megalion-http
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Expose the Megalion HTTP API to python
|
|
5
|
+
Project-URL: Homepage, https://github.com/klarasm/megalion-http
|
|
6
|
+
Project-URL: Issues, https://github.com/klarasm/megalion-http/issues
|
|
7
|
+
Author-email: Klara Modin <klarasmodin@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.14
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Megalion-HTTP
|
|
16
|
+
|
|
17
|
+
This exposes the Megalion HTTP API to python, for use with HomeAssistant.
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from aiohttp import ClientResponse, ClientSession
|
|
2
|
+
|
|
3
|
+
class Auth:
|
|
4
|
+
"""Make authenticated requests to the API."""
|
|
5
|
+
|
|
6
|
+
def __init__(
|
|
7
|
+
self, session: ClientSession, host: str, access_token: str
|
|
8
|
+
) -> None:
|
|
9
|
+
"""Initialize the auth."""
|
|
10
|
+
self.session = session
|
|
11
|
+
self.host = host
|
|
12
|
+
self.access_token = access_token
|
|
13
|
+
|
|
14
|
+
async def request(
|
|
15
|
+
self, method: str, path: str, **kwargs
|
|
16
|
+
) -> ClientResponse:
|
|
17
|
+
"""Make a request."""
|
|
18
|
+
headers = dict(kwargs.pop("headers", None) or {})
|
|
19
|
+
headers["authorization"] = f"Bearer {self.access_token}"
|
|
20
|
+
|
|
21
|
+
return await self.session.request(
|
|
22
|
+
method, f"{self.host}/{path}", **kwargs, headers=headers
|
|
23
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .auth import Auth
|
|
4
|
+
|
|
5
|
+
class Battery:
|
|
6
|
+
"""A Megalion Battery (ESS)"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, sn: str, auth: Auth) -> None:
|
|
9
|
+
"""Initialize the battery"""
|
|
10
|
+
self.device_sn = sn
|
|
11
|
+
self.auth = auth
|
|
12
|
+
|
|
13
|
+
async def async_update(self) -> None:
|
|
14
|
+
response = await self.auth.request("get", f"system/stationDevice/list?sn={self.device_sn}")
|
|
15
|
+
response.raise_for_status()
|
|
16
|
+
raw_data = await response.json()
|
|
17
|
+
self.raw_data = raw_data['rows'][0]
|
|
18
|
+
response = await self.auth.request("get", f"system/deviceLatestData/getLatestDataTable?sn={self.device_sn}")
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
raw_detail = await response.json()
|
|
21
|
+
self.raw_detail = raw_detail['data']
|
|
22
|
+
response = await self.auth.request("get", f"system/deviceLatestData/getCellVoltages?sn={self.device_sn}")
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
raw_voltages = await response.json()
|
|
25
|
+
self.raw_voltages = raw_voltages['data']
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def batchName(self) -> str:
|
|
29
|
+
return self.raw_data['batchName']
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def batteryRatedEnergy(self) -> float:
|
|
33
|
+
return self.raw_data['batteryRatedEnergy']
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def batteryTemperature(self) -> float:
|
|
37
|
+
return self.raw_data['batteryTemperature']
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def chargeStatus(self) -> str:
|
|
41
|
+
return self.raw_data['chargeStatus']
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def createBy(self) -> str:
|
|
45
|
+
return self.raw_data['createBy']
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def createTime(self) -> str:
|
|
49
|
+
return self.raw_data['createTime']
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def createTimeTimezone(self) -> str:
|
|
53
|
+
return self.raw_data['createTimeTimezone']
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def current(self) -> float:
|
|
57
|
+
return self.raw_data['current']
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def dayCharge(self) -> float:
|
|
61
|
+
return self.raw_data['dayCharge']
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def dayDischarge(self) -> float:
|
|
65
|
+
return self.raw_data['dayDischarge']
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def deviceId(self) -> int:
|
|
69
|
+
return self.raw_data['deviceId']
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def deviceModel(self) -> str:
|
|
73
|
+
return self.raw_data['deviceModel']
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def deviceName(self) -> str:
|
|
77
|
+
return self.raw_data['deviceName']
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def deviceStatus(self) -> str:
|
|
81
|
+
return self.raw_data['deviceStatus']
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def deviceType(self) -> str:
|
|
85
|
+
return self.raw_data['deviceType']
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def deviceTypeStr(self) -> str:
|
|
89
|
+
return self.raw_data['deviceTypeStr']
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def dynamicLinkedDeviceSn(self) -> str:
|
|
93
|
+
return self.raw_data['dynamicLinkedDeviceSn']
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def inverterBrand(self) -> str:
|
|
97
|
+
return self.raw_data['inverterBrand']
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def lastOfflineTimeUtc(self) -> str:
|
|
101
|
+
return self.raw_data['lastOfflineTimeUtc']
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def lastOnlineTimeUtc(self) -> str:
|
|
105
|
+
return self.raw_data['lastOnlineTimeUtc']
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def realtimePower(self) -> float:
|
|
109
|
+
return self.raw_data['realtimePower']
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def sn(self) -> str:
|
|
113
|
+
return self.raw_data['sn']
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def soc(self) -> float:
|
|
117
|
+
return self.raw_data['soc']
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def staticLinkedDeviceSn(self) -> str:
|
|
121
|
+
return self.raw_data['staticLinkedDeviceSn']
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def stations(self) -> dict[str, Any]:
|
|
125
|
+
return self.raw_data['stations']
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def totalCharge(self) -> float:
|
|
129
|
+
return self.raw_data['totalCharge']
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def totalDischarge(self) -> float:
|
|
133
|
+
return self.raw_data['totalDischarge']
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def userId(self) -> str:
|
|
137
|
+
return self.raw_data['userId']
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def voltage(self) -> float:
|
|
141
|
+
return self.raw_data['voltage']
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def cellVoltages(self) -> list[int]:
|
|
145
|
+
return self.raw_voltages['cellVoltages']
|
|
146
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .auth import Auth
|
|
2
|
+
from .battery import Battery
|
|
3
|
+
|
|
4
|
+
class MegalionHubAPI:
|
|
5
|
+
def __init__(self, auth: Auth) -> None:
|
|
6
|
+
self.auth = auth
|
|
7
|
+
|
|
8
|
+
async def async_get_batteries(self) -> list[Battery]:
|
|
9
|
+
response = await self.auth.request("get", f"system/stationDevice/list")
|
|
10
|
+
response.raise_for_status()
|
|
11
|
+
raw_data = await response.json()
|
|
12
|
+
if raw_data['code'] != 200:
|
|
13
|
+
raise MegalionHubError
|
|
14
|
+
batteries = []
|
|
15
|
+
for device in raw_data['rows']:
|
|
16
|
+
if device['deviceType'] == "ENERGY_STORAGE_EQUIPMENT":
|
|
17
|
+
battery = Battery(device['sn'], self.auth)
|
|
18
|
+
batteries.append(battery)
|
|
19
|
+
await battery.async_update()
|
|
20
|
+
return batteries
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "megalion-http"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Klara Modin", email="klarasmodin@gmail.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "Expose the Megalion HTTP API to python"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/klarasm/megalion-http"
|
|
19
|
+
Issues = "https://github.com/klarasm/megalion-http/issues"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling >= 1.26"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aiohttp==3.14.5
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .megalion import *
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from aiohttp import ClientResponse, ClientSession
|
|
2
|
+
|
|
3
|
+
class Auth:
|
|
4
|
+
"""Make authenticated requests to the API."""
|
|
5
|
+
|
|
6
|
+
def __init__(self, session: ClientSession, host: str,
|
|
7
|
+
access_token: str) -> None:
|
|
8
|
+
"""Initialize the auth."""
|
|
9
|
+
self.session = session
|
|
10
|
+
self.host = host
|
|
11
|
+
self.access_token = access_token
|
|
12
|
+
|
|
13
|
+
async def update_token(self, username: str, password: str) -> None:
|
|
14
|
+
response = await self.session.request("post",
|
|
15
|
+
f"https://{self.host}/auth/login",
|
|
16
|
+
json={'username': username,
|
|
17
|
+
'password': password})
|
|
18
|
+
response.raise_for_status()
|
|
19
|
+
data = await response.json()
|
|
20
|
+
if (data['code'] != 200):
|
|
21
|
+
raise AuthError(str(data))
|
|
22
|
+
self.access_token = data['data']['access_token']
|
|
23
|
+
|
|
24
|
+
async def request(
|
|
25
|
+
self, method: str, path: str, **kwargs
|
|
26
|
+
) -> ClientResponse:
|
|
27
|
+
"""Make a request."""
|
|
28
|
+
headers = dict(kwargs.pop("headers", None) or {})
|
|
29
|
+
headers["authorization"] = f"Bearer {self.access_token}"
|
|
30
|
+
|
|
31
|
+
return await self.session.request(
|
|
32
|
+
method, f"https://{self.host}/{path}", **kwargs, headers=headers
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
class AuthError(Exception):
|
|
36
|
+
pass
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .auth import Auth
|
|
4
|
+
|
|
5
|
+
class Battery:
|
|
6
|
+
"""A Megalion Battery (ESS)"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, sn: str, auth: Auth) -> None:
|
|
9
|
+
"""Initialize the battery"""
|
|
10
|
+
self.device_sn = sn
|
|
11
|
+
self.auth = auth
|
|
12
|
+
|
|
13
|
+
async def async_update(self) -> None:
|
|
14
|
+
response = await self.auth.request("get", f"system/stationDevice/list?sn={self.device_sn}")
|
|
15
|
+
response.raise_for_status()
|
|
16
|
+
raw_data = await response.json()
|
|
17
|
+
self.raw_data = raw_data['rows'][0]
|
|
18
|
+
response = await self.auth.request("get", f"system/deviceLatestData/getLatestDataTable?sn={self.device_sn}")
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
raw_detail = await response.json()
|
|
21
|
+
self.raw_detail = raw_detail['data']
|
|
22
|
+
response = await self.auth.request("get", f"system/deviceLatestData/getCellVoltages?sn={self.device_sn}")
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
raw_voltages = await response.json()
|
|
25
|
+
self.raw_voltages = raw_voltages['data']
|
|
26
|
+
response = await self.auth.request("get", f"system/deviceLatestData/getCellTemperatures?sn={self.device_sn}")
|
|
27
|
+
response.raise_for_status()
|
|
28
|
+
raw_temperatures = await response.json()
|
|
29
|
+
self.raw_temperatures = raw_temperatures['data']
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def batchName(self) -> str:
|
|
33
|
+
return self.raw_data['batchName']
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def batteryRatedEnergy(self) -> float:
|
|
37
|
+
return self.raw_data['batteryRatedEnergy']
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def batteryTemperature(self) -> float:
|
|
41
|
+
return self.raw_data['batteryTemperature']
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def chargeStatus(self) -> str:
|
|
45
|
+
return self.raw_data['chargeStatus']
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def createBy(self) -> str:
|
|
49
|
+
return self.raw_data['createBy']
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def createTime(self) -> str:
|
|
53
|
+
return self.raw_data['createTime']
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def createTimeTimezone(self) -> str:
|
|
57
|
+
return self.raw_data['createTimeTimezone']
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def current(self) -> float:
|
|
61
|
+
return self.raw_data['current']
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def dayCharge(self) -> float:
|
|
65
|
+
return self.raw_data['dayCharge']
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def dayDischarge(self) -> float:
|
|
69
|
+
return self.raw_data['dayDischarge']
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def deviceId(self) -> int:
|
|
73
|
+
return self.raw_data['deviceId']
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def deviceModel(self) -> str:
|
|
77
|
+
return self.raw_data['deviceModel']
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def deviceName(self) -> str:
|
|
81
|
+
return self.raw_data['deviceName']
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def deviceStatus(self) -> str:
|
|
85
|
+
return self.raw_data['deviceStatus']
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def deviceType(self) -> str:
|
|
89
|
+
return self.raw_data['deviceType']
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def deviceTypeStr(self) -> str:
|
|
93
|
+
return self.raw_data['deviceTypeStr']
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def dynamicLinkedDeviceSn(self) -> str:
|
|
97
|
+
return self.raw_data['dynamicLinkedDeviceSn']
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def inverterBrand(self) -> str:
|
|
101
|
+
return self.raw_data['inverterBrand']
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def lastOfflineTimeUtc(self) -> str:
|
|
105
|
+
return self.raw_data['lastOfflineTimeUtc']
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def lastOnlineTimeUtc(self) -> str:
|
|
109
|
+
return self.raw_data['lastOnlineTimeUtc']
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def realtimePower(self) -> float:
|
|
113
|
+
return self.raw_data['realtimePower']
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def sn(self) -> str:
|
|
117
|
+
return self.raw_data['sn']
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def soc(self) -> float:
|
|
121
|
+
return self.raw_data['soc']
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def staticLinkedDeviceSn(self) -> str:
|
|
125
|
+
return self.raw_data['staticLinkedDeviceSn']
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def totalCharge(self) -> float:
|
|
129
|
+
return self.raw_data['totalCharge']
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def totalDischarge(self) -> float:
|
|
133
|
+
return self.raw_data['totalDischarge']
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def userId(self) -> str:
|
|
137
|
+
return self.raw_data['userId']
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def voltage(self) -> float:
|
|
141
|
+
return self.raw_data['voltage']
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def cellVoltages(self) -> list[int]:
|
|
145
|
+
return self.raw_voltages['cellVoltages']
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def cellTemperatures(self) -> list[int]:
|
|
149
|
+
return self.raw_temperatures['cellTemperatures']
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .auth import Auth, AuthError
|
|
2
|
+
from .battery import Battery
|
|
3
|
+
from .user import User
|
|
4
|
+
|
|
5
|
+
class MegalionHubAPI:
|
|
6
|
+
def __init__(self, auth: Auth) -> None:
|
|
7
|
+
self.auth = auth
|
|
8
|
+
|
|
9
|
+
async def async_get_user(self) -> User:
|
|
10
|
+
user = User(self.auth)
|
|
11
|
+
await user.async_update()
|
|
12
|
+
return user
|
|
13
|
+
|
|
14
|
+
async def async_get_batteries(self) -> list[Battery]:
|
|
15
|
+
response = await self.auth.request("get", f"system/stationDevice/list")
|
|
16
|
+
response.raise_for_status()
|
|
17
|
+
raw_data = await response.json()
|
|
18
|
+
if raw_data['code'] != 200:
|
|
19
|
+
raise MegalionHubError(str(raw_data))
|
|
20
|
+
batteries = []
|
|
21
|
+
for device in raw_data['rows']:
|
|
22
|
+
if device['deviceType'] == "ENERGY_STORAGE_EQUIPMENT":
|
|
23
|
+
battery = Battery(device['sn'], self.auth)
|
|
24
|
+
batteries.append(battery)
|
|
25
|
+
await battery.async_update()
|
|
26
|
+
return batteries
|
|
27
|
+
|
|
28
|
+
class MegalionHubError(Exception):
|
|
29
|
+
pass
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .auth import Auth
|
|
4
|
+
|
|
5
|
+
class User:
|
|
6
|
+
"""A Megalion API User"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, auth: Auth) -> None:
|
|
9
|
+
"""Initialize the battery"""
|
|
10
|
+
self.auth = auth
|
|
11
|
+
|
|
12
|
+
async def async_update(self) -> None:
|
|
13
|
+
response = await self.auth.request("get", f"system/user/getInfo")
|
|
14
|
+
response.raise_for_status()
|
|
15
|
+
self.raw_data = await response.json()
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def userId(self) -> int:
|
|
19
|
+
return self.raw_data['user']['userId']
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def email(self) -> str:
|
|
23
|
+
return self.raw_data['user']['email']
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def userName(self) -> str:
|
|
27
|
+
return self.raw_data['user']['userName']
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: megalion-http
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Expose the Megalion HTTP API to python
|
|
5
|
+
Author-email: Klara Modin <klarasmodin@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/klarasm/megalion-http
|
|
8
|
+
Project-URL: Issues, https://github.com/klarasm/megalion-http/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.14
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Megalion-HTTP
|
|
17
|
+
|
|
18
|
+
This exposes the Megalion HTTP API to python, for use with HomeAssistant.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/megalion-http/__init__.py
|
|
5
|
+
src/megalion-http/auth.py
|
|
6
|
+
src/megalion-http/battery.py
|
|
7
|
+
src/megalion-http/megalion.py
|
|
8
|
+
src/megalion_http.egg-info/PKG-INFO
|
|
9
|
+
src/megalion_http.egg-info/SOURCES.txt
|
|
10
|
+
src/megalion_http.egg-info/dependency_links.txt
|
|
11
|
+
src/megalion_http.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
megalion-http
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
|
|
6
|
+
from megalion_http import MegalionHubAPI
|
|
7
|
+
from megalion_http import Battery
|
|
8
|
+
from megalion_http import Auth
|
|
9
|
+
|
|
10
|
+
async def main() -> None:
|
|
11
|
+
async with aiohttp.ClientSession() as session:
|
|
12
|
+
# auth = Auth(session, "https://server.zh.megalion.cn/", "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoyNjksInVzZXJfa2V5IjoiNjNlYWE4ODMtMjAwNy00OWVlLTk1MTMtNDk5YWUwZjE5NDAxIiwidXNlcm5hbWUiOiJrbGFyYSJ9.evLT0eGsPinTchgDEzP-MYME4JpmadDG0tYLQHiYbVG6nfrlHZLwH7aUyF-BMVpec8Iw9aP1X2019THGzGUJ_A")
|
|
13
|
+
auth = Auth(session, "server.zh.megalion.cn", "")
|
|
14
|
+
await auth.update_token("klarasmodin@gmail.com", "ahQBU3rBXPqOiXrKgnPt")
|
|
15
|
+
|
|
16
|
+
hub = MegalionHubAPI(auth)
|
|
17
|
+
batteries = await hub.async_get_batteries()
|
|
18
|
+
for battery in batteries:
|
|
19
|
+
print(battery.cellVoltages)
|
|
20
|
+
print(battery.cellTemperatures)
|
|
21
|
+
|
|
22
|
+
asyncio.run(main())
|