hyponcloud 0.1.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.
- hyponcloud-0.1.0/LICENSE +21 -0
- hyponcloud-0.1.0/MANIFEST.in +4 -0
- hyponcloud-0.1.0/PKG-INFO +279 -0
- hyponcloud-0.1.0/README.md +247 -0
- hyponcloud-0.1.0/hyponcloud/__init__.py +22 -0
- hyponcloud-0.1.0/hyponcloud/client.py +189 -0
- hyponcloud-0.1.0/hyponcloud/exceptions.py +17 -0
- hyponcloud-0.1.0/hyponcloud/models.py +93 -0
- hyponcloud-0.1.0/hyponcloud/py.typed +0 -0
- hyponcloud-0.1.0/hyponcloud.egg-info/PKG-INFO +279 -0
- hyponcloud-0.1.0/hyponcloud.egg-info/SOURCES.txt +15 -0
- hyponcloud-0.1.0/hyponcloud.egg-info/dependency_links.txt +1 -0
- hyponcloud-0.1.0/hyponcloud.egg-info/requires.txt +8 -0
- hyponcloud-0.1.0/hyponcloud.egg-info/top_level.txt +1 -0
- hyponcloud-0.1.0/pyproject.toml +74 -0
- hyponcloud-0.1.0/setup.cfg +4 -0
- hyponcloud-0.1.0/tests/test_client.py +29 -0
hyponcloud-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 jcisio
|
|
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,279 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyponcloud
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library for Hypontech Cloud API
|
|
5
|
+
Author-email: jcisio <jcisio@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jcisio/hyponcloud
|
|
8
|
+
Project-URL: Repository, https://github.com/jcisio/hyponcloud
|
|
9
|
+
Project-URL: Issues, https://github.com/jcisio/hyponcloud/issues
|
|
10
|
+
Keywords: hypontech,solar,inverter,api,async
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Home Automation
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Hypontech Cloud API Python Library
|
|
34
|
+
|
|
35
|
+
A Python library for interacting with the Hypontech Cloud API for solar inverter monitoring.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Async/await support using aiohttp
|
|
40
|
+
- Get plant overview data (power, energy production, device status)
|
|
41
|
+
- Get plant list
|
|
42
|
+
- Automatic token management and refresh
|
|
43
|
+
- Built-in retry logic for rate limiting
|
|
44
|
+
- Type hints for better IDE support
|
|
45
|
+
- Comprehensive error handling
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install hyponcloud
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
from hyponcloud import HyponCloud
|
|
60
|
+
|
|
61
|
+
async def main():
|
|
62
|
+
# Create client with your credentials
|
|
63
|
+
async with HyponCloud("your_username", "your_password") as client:
|
|
64
|
+
# Connect and authenticate
|
|
65
|
+
if await client.connect():
|
|
66
|
+
# Get overview data
|
|
67
|
+
overview = await client.get_overview()
|
|
68
|
+
print(f"Current power: {overview.power}W")
|
|
69
|
+
print(f"Today's energy: {overview.e_today}kWh")
|
|
70
|
+
print(f"Total energy: {overview.e_total}kWh")
|
|
71
|
+
|
|
72
|
+
# Get plant list
|
|
73
|
+
plants = await client.get_list()
|
|
74
|
+
print(f"Number of plants: {len(plants)}")
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Using with Custom aiohttp Session
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import aiohttp
|
|
83
|
+
from hyponcloud import HyponCloud
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
async with aiohttp.ClientSession() as session:
|
|
87
|
+
client = HyponCloud("your_username", "your_password", session=session)
|
|
88
|
+
|
|
89
|
+
if await client.connect():
|
|
90
|
+
overview = await client.get_overview()
|
|
91
|
+
print(f"Power: {overview.power}W")
|
|
92
|
+
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Error Handling
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from hyponcloud import (
|
|
100
|
+
HyponCloud,
|
|
101
|
+
AuthenticationError,
|
|
102
|
+
ConnectionError,
|
|
103
|
+
RateLimitError,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def main():
|
|
107
|
+
try:
|
|
108
|
+
async with HyponCloud("username", "password") as client:
|
|
109
|
+
await client.connect()
|
|
110
|
+
overview = await client.get_overview()
|
|
111
|
+
print(f"Power: {overview.power}W")
|
|
112
|
+
|
|
113
|
+
except AuthenticationError as e:
|
|
114
|
+
print(f"Authentication failed: {e}")
|
|
115
|
+
except RateLimitError as e:
|
|
116
|
+
print(f"Rate limit exceeded: {e}")
|
|
117
|
+
except ConnectionError as e:
|
|
118
|
+
print(f"Connection error: {e}")
|
|
119
|
+
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### HyponCloud
|
|
126
|
+
|
|
127
|
+
Main client class for interacting with the Hypontech Cloud API.
|
|
128
|
+
|
|
129
|
+
#### Methods
|
|
130
|
+
|
|
131
|
+
##### `__init__(username: str, password: str, session: aiohttp.ClientSession | None = None)`
|
|
132
|
+
|
|
133
|
+
Initialize the client.
|
|
134
|
+
|
|
135
|
+
- `username`: Your Hypontech Cloud username
|
|
136
|
+
- `password`: Your Hypontech Cloud password
|
|
137
|
+
- `session`: Optional aiohttp ClientSession. If not provided, one will be created automatically.
|
|
138
|
+
|
|
139
|
+
##### `async connect() -> bool`
|
|
140
|
+
|
|
141
|
+
Authenticate with the API and retrieve access token.
|
|
142
|
+
|
|
143
|
+
**Returns:** `True` if successful, `False` otherwise
|
|
144
|
+
|
|
145
|
+
**Raises:**
|
|
146
|
+
- `AuthenticationError`: Invalid credentials
|
|
147
|
+
- `ConnectionError`: Network error
|
|
148
|
+
- `RateLimitError`: Too many requests
|
|
149
|
+
|
|
150
|
+
##### `async get_overview(retries: int = 3) -> OverviewData`
|
|
151
|
+
|
|
152
|
+
Get plant overview data including power generation and device status.
|
|
153
|
+
|
|
154
|
+
**Parameters:**
|
|
155
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
156
|
+
|
|
157
|
+
**Returns:** `OverviewData` object
|
|
158
|
+
|
|
159
|
+
**Raises:**
|
|
160
|
+
- `AuthenticationError`: Authentication required
|
|
161
|
+
- `ConnectionError`: Network error
|
|
162
|
+
- `RateLimitError`: Too many requests
|
|
163
|
+
|
|
164
|
+
##### `async get_list(retries: int = 3) -> list[dict]`
|
|
165
|
+
|
|
166
|
+
Get list of plants associated with the account.
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
170
|
+
|
|
171
|
+
**Returns:** List of plant dictionaries
|
|
172
|
+
|
|
173
|
+
**Raises:**
|
|
174
|
+
- `AuthenticationError`: Authentication required
|
|
175
|
+
- `ConnectionError`: Network error
|
|
176
|
+
- `RateLimitError`: Too many requests
|
|
177
|
+
|
|
178
|
+
##### `async close() -> None`
|
|
179
|
+
|
|
180
|
+
Close the aiohttp session (only if created by the library).
|
|
181
|
+
|
|
182
|
+
### OverviewData
|
|
183
|
+
|
|
184
|
+
Data class containing plant overview information.
|
|
185
|
+
|
|
186
|
+
#### Attributes
|
|
187
|
+
|
|
188
|
+
- `capacity` (float): Plant capacity
|
|
189
|
+
- `capacity_company` (str): Capacity unit (e.g., "KW")
|
|
190
|
+
- `power` (int): Current power generation in watts
|
|
191
|
+
- `company` (str): Power unit (e.g., "W")
|
|
192
|
+
- `percent` (int): Percentage value
|
|
193
|
+
- `e_today` (float): Today's energy production in kWh
|
|
194
|
+
- `e_total` (float): Total lifetime energy production in kWh
|
|
195
|
+
- `fault_dev_num` (int): Number of faulty devices
|
|
196
|
+
- `normal_dev_num` (int): Number of normal devices
|
|
197
|
+
- `offline_dev_num` (int): Number of offline devices
|
|
198
|
+
- `wait_dev_num` (int): Number of devices waiting
|
|
199
|
+
- `total_co2` (int): Total CO2 savings
|
|
200
|
+
- `total_tree` (float): Equivalent trees planted
|
|
201
|
+
|
|
202
|
+
### PlantData
|
|
203
|
+
|
|
204
|
+
Data class containing individual plant information.
|
|
205
|
+
|
|
206
|
+
#### Attributes
|
|
207
|
+
|
|
208
|
+
- `city` (str): Plant location city
|
|
209
|
+
- `country` (str): Plant location country
|
|
210
|
+
- `e_today` (float): Today's energy production
|
|
211
|
+
- `e_total` (float): Total energy production
|
|
212
|
+
- `eid` (int): Equipment ID
|
|
213
|
+
- `kwhimp` (int): kWh import
|
|
214
|
+
- `micro` (int): Micro inverter count
|
|
215
|
+
- `plant_id` (str): Unique plant identifier
|
|
216
|
+
- `plant_name` (str): Plant name
|
|
217
|
+
- `plant_type` (str): Plant type
|
|
218
|
+
- `power` (int): Current power
|
|
219
|
+
- `status` (str): Plant status
|
|
220
|
+
|
|
221
|
+
### Exceptions
|
|
222
|
+
|
|
223
|
+
- `HyponCloudError`: Base exception for all library errors
|
|
224
|
+
- `AuthenticationError`: Authentication failed (invalid credentials)
|
|
225
|
+
- `ConnectionError`: Connection to API failed
|
|
226
|
+
- `RateLimitError`: API rate limit exceeded
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
### Setup Development Environment
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Clone the repository
|
|
234
|
+
git clone https://github.com/jcisio/hyponcloud.git
|
|
235
|
+
cd hyponcloud
|
|
236
|
+
|
|
237
|
+
# Install development dependencies
|
|
238
|
+
pip install -e ".[dev]"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Running Tests
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pytest
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Code Formatting
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
ruff check .
|
|
251
|
+
ruff format .
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Type Checking
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
mypy hyponcloud
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Requirements
|
|
261
|
+
|
|
262
|
+
- Python 3.11+
|
|
263
|
+
- aiohttp 3.8.0+
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
272
|
+
|
|
273
|
+
## Disclaimer
|
|
274
|
+
|
|
275
|
+
This library is not officially associated with or endorsed by Hypontech. Use at your own risk.
|
|
276
|
+
|
|
277
|
+
## Support
|
|
278
|
+
|
|
279
|
+
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/jcisio/hyponcloud).
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Hypontech Cloud API Python Library
|
|
2
|
+
|
|
3
|
+
A Python library for interacting with the Hypontech Cloud API for solar inverter monitoring.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Async/await support using aiohttp
|
|
8
|
+
- Get plant overview data (power, energy production, device status)
|
|
9
|
+
- Get plant list
|
|
10
|
+
- Automatic token management and refresh
|
|
11
|
+
- Built-in retry logic for rate limiting
|
|
12
|
+
- Type hints for better IDE support
|
|
13
|
+
- Comprehensive error handling
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install hyponcloud
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Basic Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import asyncio
|
|
27
|
+
from hyponcloud import HyponCloud
|
|
28
|
+
|
|
29
|
+
async def main():
|
|
30
|
+
# Create client with your credentials
|
|
31
|
+
async with HyponCloud("your_username", "your_password") as client:
|
|
32
|
+
# Connect and authenticate
|
|
33
|
+
if await client.connect():
|
|
34
|
+
# Get overview data
|
|
35
|
+
overview = await client.get_overview()
|
|
36
|
+
print(f"Current power: {overview.power}W")
|
|
37
|
+
print(f"Today's energy: {overview.e_today}kWh")
|
|
38
|
+
print(f"Total energy: {overview.e_total}kWh")
|
|
39
|
+
|
|
40
|
+
# Get plant list
|
|
41
|
+
plants = await client.get_list()
|
|
42
|
+
print(f"Number of plants: {len(plants)}")
|
|
43
|
+
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Using with Custom aiohttp Session
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import aiohttp
|
|
51
|
+
from hyponcloud import HyponCloud
|
|
52
|
+
|
|
53
|
+
async def main():
|
|
54
|
+
async with aiohttp.ClientSession() as session:
|
|
55
|
+
client = HyponCloud("your_username", "your_password", session=session)
|
|
56
|
+
|
|
57
|
+
if await client.connect():
|
|
58
|
+
overview = await client.get_overview()
|
|
59
|
+
print(f"Power: {overview.power}W")
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Error Handling
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from hyponcloud import (
|
|
68
|
+
HyponCloud,
|
|
69
|
+
AuthenticationError,
|
|
70
|
+
ConnectionError,
|
|
71
|
+
RateLimitError,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def main():
|
|
75
|
+
try:
|
|
76
|
+
async with HyponCloud("username", "password") as client:
|
|
77
|
+
await client.connect()
|
|
78
|
+
overview = await client.get_overview()
|
|
79
|
+
print(f"Power: {overview.power}W")
|
|
80
|
+
|
|
81
|
+
except AuthenticationError as e:
|
|
82
|
+
print(f"Authentication failed: {e}")
|
|
83
|
+
except RateLimitError as e:
|
|
84
|
+
print(f"Rate limit exceeded: {e}")
|
|
85
|
+
except ConnectionError as e:
|
|
86
|
+
print(f"Connection error: {e}")
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API Reference
|
|
92
|
+
|
|
93
|
+
### HyponCloud
|
|
94
|
+
|
|
95
|
+
Main client class for interacting with the Hypontech Cloud API.
|
|
96
|
+
|
|
97
|
+
#### Methods
|
|
98
|
+
|
|
99
|
+
##### `__init__(username: str, password: str, session: aiohttp.ClientSession | None = None)`
|
|
100
|
+
|
|
101
|
+
Initialize the client.
|
|
102
|
+
|
|
103
|
+
- `username`: Your Hypontech Cloud username
|
|
104
|
+
- `password`: Your Hypontech Cloud password
|
|
105
|
+
- `session`: Optional aiohttp ClientSession. If not provided, one will be created automatically.
|
|
106
|
+
|
|
107
|
+
##### `async connect() -> bool`
|
|
108
|
+
|
|
109
|
+
Authenticate with the API and retrieve access token.
|
|
110
|
+
|
|
111
|
+
**Returns:** `True` if successful, `False` otherwise
|
|
112
|
+
|
|
113
|
+
**Raises:**
|
|
114
|
+
- `AuthenticationError`: Invalid credentials
|
|
115
|
+
- `ConnectionError`: Network error
|
|
116
|
+
- `RateLimitError`: Too many requests
|
|
117
|
+
|
|
118
|
+
##### `async get_overview(retries: int = 3) -> OverviewData`
|
|
119
|
+
|
|
120
|
+
Get plant overview data including power generation and device status.
|
|
121
|
+
|
|
122
|
+
**Parameters:**
|
|
123
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
124
|
+
|
|
125
|
+
**Returns:** `OverviewData` object
|
|
126
|
+
|
|
127
|
+
**Raises:**
|
|
128
|
+
- `AuthenticationError`: Authentication required
|
|
129
|
+
- `ConnectionError`: Network error
|
|
130
|
+
- `RateLimitError`: Too many requests
|
|
131
|
+
|
|
132
|
+
##### `async get_list(retries: int = 3) -> list[dict]`
|
|
133
|
+
|
|
134
|
+
Get list of plants associated with the account.
|
|
135
|
+
|
|
136
|
+
**Parameters:**
|
|
137
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
138
|
+
|
|
139
|
+
**Returns:** List of plant dictionaries
|
|
140
|
+
|
|
141
|
+
**Raises:**
|
|
142
|
+
- `AuthenticationError`: Authentication required
|
|
143
|
+
- `ConnectionError`: Network error
|
|
144
|
+
- `RateLimitError`: Too many requests
|
|
145
|
+
|
|
146
|
+
##### `async close() -> None`
|
|
147
|
+
|
|
148
|
+
Close the aiohttp session (only if created by the library).
|
|
149
|
+
|
|
150
|
+
### OverviewData
|
|
151
|
+
|
|
152
|
+
Data class containing plant overview information.
|
|
153
|
+
|
|
154
|
+
#### Attributes
|
|
155
|
+
|
|
156
|
+
- `capacity` (float): Plant capacity
|
|
157
|
+
- `capacity_company` (str): Capacity unit (e.g., "KW")
|
|
158
|
+
- `power` (int): Current power generation in watts
|
|
159
|
+
- `company` (str): Power unit (e.g., "W")
|
|
160
|
+
- `percent` (int): Percentage value
|
|
161
|
+
- `e_today` (float): Today's energy production in kWh
|
|
162
|
+
- `e_total` (float): Total lifetime energy production in kWh
|
|
163
|
+
- `fault_dev_num` (int): Number of faulty devices
|
|
164
|
+
- `normal_dev_num` (int): Number of normal devices
|
|
165
|
+
- `offline_dev_num` (int): Number of offline devices
|
|
166
|
+
- `wait_dev_num` (int): Number of devices waiting
|
|
167
|
+
- `total_co2` (int): Total CO2 savings
|
|
168
|
+
- `total_tree` (float): Equivalent trees planted
|
|
169
|
+
|
|
170
|
+
### PlantData
|
|
171
|
+
|
|
172
|
+
Data class containing individual plant information.
|
|
173
|
+
|
|
174
|
+
#### Attributes
|
|
175
|
+
|
|
176
|
+
- `city` (str): Plant location city
|
|
177
|
+
- `country` (str): Plant location country
|
|
178
|
+
- `e_today` (float): Today's energy production
|
|
179
|
+
- `e_total` (float): Total energy production
|
|
180
|
+
- `eid` (int): Equipment ID
|
|
181
|
+
- `kwhimp` (int): kWh import
|
|
182
|
+
- `micro` (int): Micro inverter count
|
|
183
|
+
- `plant_id` (str): Unique plant identifier
|
|
184
|
+
- `plant_name` (str): Plant name
|
|
185
|
+
- `plant_type` (str): Plant type
|
|
186
|
+
- `power` (int): Current power
|
|
187
|
+
- `status` (str): Plant status
|
|
188
|
+
|
|
189
|
+
### Exceptions
|
|
190
|
+
|
|
191
|
+
- `HyponCloudError`: Base exception for all library errors
|
|
192
|
+
- `AuthenticationError`: Authentication failed (invalid credentials)
|
|
193
|
+
- `ConnectionError`: Connection to API failed
|
|
194
|
+
- `RateLimitError`: API rate limit exceeded
|
|
195
|
+
|
|
196
|
+
## Development
|
|
197
|
+
|
|
198
|
+
### Setup Development Environment
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Clone the repository
|
|
202
|
+
git clone https://github.com/jcisio/hyponcloud.git
|
|
203
|
+
cd hyponcloud
|
|
204
|
+
|
|
205
|
+
# Install development dependencies
|
|
206
|
+
pip install -e ".[dev]"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Running Tests
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pytest
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Code Formatting
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
ruff check .
|
|
219
|
+
ruff format .
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Type Checking
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
mypy hyponcloud
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Requirements
|
|
229
|
+
|
|
230
|
+
- Python 3.11+
|
|
231
|
+
- aiohttp 3.8.0+
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
240
|
+
|
|
241
|
+
## Disclaimer
|
|
242
|
+
|
|
243
|
+
This library is not officially associated with or endorsed by Hypontech. Use at your own risk.
|
|
244
|
+
|
|
245
|
+
## Support
|
|
246
|
+
|
|
247
|
+
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/jcisio/hyponcloud).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Hypontech Cloud API Python library."""
|
|
2
|
+
|
|
3
|
+
from .client import HyponCloud
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
ConnectionError,
|
|
7
|
+
HyponCloudError,
|
|
8
|
+
RateLimitError,
|
|
9
|
+
)
|
|
10
|
+
from .models import OverviewData, PlantData
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"HyponCloud",
|
|
16
|
+
"HyponCloudError",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"ConnectionError",
|
|
19
|
+
"RateLimitError",
|
|
20
|
+
"OverviewData",
|
|
21
|
+
"PlantData",
|
|
22
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Hypontech Cloud API client."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from time import time
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from .exceptions import AuthenticationError, ConnectionError, RateLimitError
|
|
10
|
+
from .models import OverviewData
|
|
11
|
+
|
|
12
|
+
_LOGGER = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class HyponCloud:
|
|
16
|
+
"""HyponCloud API client."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self, username: str, password: str, session: aiohttp.ClientSession | None = None
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Initialize the HyponCloud class.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
username: The username for Hypon Cloud.
|
|
25
|
+
password: The password for Hypon Cloud.
|
|
26
|
+
session: Optional aiohttp client session. If not provided, a new one will be created.
|
|
27
|
+
"""
|
|
28
|
+
self.base_url = "https://api.hypon.cloud/v2"
|
|
29
|
+
self.token_validity = 3600
|
|
30
|
+
self.timeout = aiohttp.ClientTimeout(total=10)
|
|
31
|
+
|
|
32
|
+
self._session = session
|
|
33
|
+
self._own_session = session is None
|
|
34
|
+
self.__username = username
|
|
35
|
+
self.__password = password
|
|
36
|
+
self.__token = ""
|
|
37
|
+
self.__token_expires_at = 0
|
|
38
|
+
|
|
39
|
+
async def __aenter__(self):
|
|
40
|
+
"""Async context manager entry."""
|
|
41
|
+
if self._own_session:
|
|
42
|
+
self._session = aiohttp.ClientSession()
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
46
|
+
"""Async context manager exit."""
|
|
47
|
+
if self._own_session and self._session:
|
|
48
|
+
await self._session.close()
|
|
49
|
+
|
|
50
|
+
async def close(self) -> None:
|
|
51
|
+
"""Close the session if we own it."""
|
|
52
|
+
if self._own_session and self._session:
|
|
53
|
+
await self._session.close()
|
|
54
|
+
|
|
55
|
+
async def connect(self) -> bool:
|
|
56
|
+
"""Connect to Hypon Cloud and retrieve token.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if connection successful, False otherwise.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
AuthenticationError: If authentication fails.
|
|
63
|
+
ConnectionError: If connection to API fails.
|
|
64
|
+
"""
|
|
65
|
+
if self.__token and self.__token_expires_at > time():
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
if not self._session:
|
|
69
|
+
self._session = aiohttp.ClientSession()
|
|
70
|
+
self._own_session = True
|
|
71
|
+
|
|
72
|
+
url = f"{self.base_url}/login"
|
|
73
|
+
data = {"username": self.__username, "password": self.__password}
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
async with self._session.post(
|
|
77
|
+
url, json=data, timeout=self.timeout
|
|
78
|
+
) as response:
|
|
79
|
+
if response.status == 401:
|
|
80
|
+
raise AuthenticationError("Invalid credentials")
|
|
81
|
+
if response.status == 429:
|
|
82
|
+
raise RateLimitError(
|
|
83
|
+
"Rate limit exceeded. Requests are being sent too fast."
|
|
84
|
+
)
|
|
85
|
+
if response.status != 200:
|
|
86
|
+
_LOGGER.warning(
|
|
87
|
+
"Connection failed with status %s", response.status
|
|
88
|
+
)
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
result = await response.json()
|
|
92
|
+
self.__token = result["data"]["token"]
|
|
93
|
+
self.__token_expires_at = int(time()) + self.token_validity
|
|
94
|
+
return True
|
|
95
|
+
except aiohttp.ClientError as e:
|
|
96
|
+
raise ConnectionError(f"Failed to connect to Hypon Cloud: {e}") from e
|
|
97
|
+
except KeyError as e:
|
|
98
|
+
raise AuthenticationError(
|
|
99
|
+
f"Invalid response from API, missing token: {e}"
|
|
100
|
+
) from e
|
|
101
|
+
|
|
102
|
+
async def get_overview(self, retries: int = 3) -> OverviewData:
|
|
103
|
+
"""Get plant overview.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
retries: Number of retry attempts if request fails.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
OverviewData object containing plant overview information.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
AuthenticationError: If authentication fails.
|
|
113
|
+
ConnectionError: If connection to API fails.
|
|
114
|
+
"""
|
|
115
|
+
if not await self.connect():
|
|
116
|
+
return OverviewData()
|
|
117
|
+
|
|
118
|
+
url = f"{self.base_url}/plant/overview"
|
|
119
|
+
headers = {"authorization": f"Bearer {self.__token}"}
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
async with self._session.get(
|
|
123
|
+
url, headers=headers, timeout=self.timeout
|
|
124
|
+
) as response:
|
|
125
|
+
if response.status == 429:
|
|
126
|
+
if retries > 0:
|
|
127
|
+
await asyncio.sleep(10)
|
|
128
|
+
return await self.get_overview(retries - 1)
|
|
129
|
+
raise RateLimitError("Rate limit exceeded for overview endpoint")
|
|
130
|
+
|
|
131
|
+
if response.status != 200:
|
|
132
|
+
if retries > 0:
|
|
133
|
+
await asyncio.sleep(10)
|
|
134
|
+
return await self.get_overview(retries - 1)
|
|
135
|
+
raise ConnectionError(
|
|
136
|
+
f"Failed to get plant overview: HTTP {response.status}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
result = await response.json()
|
|
140
|
+
data = result["data"]
|
|
141
|
+
return OverviewData(**data)
|
|
142
|
+
except KeyError as e:
|
|
143
|
+
_LOGGER.error("Error parsing plant overview data: %s", e)
|
|
144
|
+
# Unknown error. Try again.
|
|
145
|
+
if retries > 0:
|
|
146
|
+
return await self.get_overview(retries - 1)
|
|
147
|
+
return OverviewData()
|
|
148
|
+
except aiohttp.ClientError as e:
|
|
149
|
+
raise ConnectionError(f"Failed to get plant overview: {e}") from e
|
|
150
|
+
|
|
151
|
+
async def get_list(self, retries: int = 3) -> list[dict]:
|
|
152
|
+
"""Get plant list.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
retries: Number of retry attempts if request fails.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of plant data dictionaries.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
AuthenticationError: If authentication fails.
|
|
162
|
+
ConnectionError: If connection to API fails.
|
|
163
|
+
"""
|
|
164
|
+
url = f"{self.base_url}/plant/list2?page=1&page_size=10&refresh=true"
|
|
165
|
+
headers = {"authorization": f"Bearer {self.__token}"}
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
async with self._session.get(
|
|
169
|
+
url, headers=headers, timeout=self.timeout
|
|
170
|
+
) as response:
|
|
171
|
+
if response.status == 429:
|
|
172
|
+
if retries > 0:
|
|
173
|
+
await asyncio.sleep(10)
|
|
174
|
+
return await self.get_list(retries - 1)
|
|
175
|
+
raise RateLimitError("Rate limit exceeded for plant list endpoint")
|
|
176
|
+
|
|
177
|
+
if response.status != 200:
|
|
178
|
+
if retries > 0:
|
|
179
|
+
await asyncio.sleep(10)
|
|
180
|
+
return await self.get_list(retries - 1)
|
|
181
|
+
|
|
182
|
+
result = await response.json()
|
|
183
|
+
return result["data"]
|
|
184
|
+
except Exception as e:
|
|
185
|
+
_LOGGER.error("Error getting plant list: %s", e)
|
|
186
|
+
# Unknown error. Try again.
|
|
187
|
+
if retries > 0:
|
|
188
|
+
return await self.get_list(retries - 1)
|
|
189
|
+
raise ConnectionError(f"Failed to get plant list: {e}") from e
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Exceptions for Hypontech Cloud API."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HyponCloudError(Exception):
|
|
5
|
+
"""Base exception for Hypontech Cloud API."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthenticationError(HyponCloudError):
|
|
9
|
+
"""Exception raised when authentication fails."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConnectionError(HyponCloudError):
|
|
13
|
+
"""Exception raised when connection to API fails."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RateLimitError(HyponCloudError):
|
|
17
|
+
"""Exception raised when API rate limit is exceeded."""
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Data models for Hypontech Cloud API."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class OverviewData:
|
|
8
|
+
"""Overview data class.
|
|
9
|
+
|
|
10
|
+
This class represents the overview data for a Hypon Cloud plant.
|
|
11
|
+
It contains information about the plant's capacity, power, energy production,
|
|
12
|
+
device status, and environmental impact.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
capacity: float
|
|
16
|
+
capacity_company: str
|
|
17
|
+
power: int
|
|
18
|
+
company: str
|
|
19
|
+
percent: int
|
|
20
|
+
e_today: float
|
|
21
|
+
e_total: float
|
|
22
|
+
fault_dev_num: int
|
|
23
|
+
normal_dev_num: int
|
|
24
|
+
offline_dev_num: int
|
|
25
|
+
wait_dev_num: int
|
|
26
|
+
total_co2: int
|
|
27
|
+
total_tree: float
|
|
28
|
+
|
|
29
|
+
def __init__(self, **data) -> None:
|
|
30
|
+
"""Initialize the OverviewData class with data from the API.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data: Dictionary containing overview data from the API.
|
|
34
|
+
"""
|
|
35
|
+
# The data attribute needs to be set manually because the API
|
|
36
|
+
# may return more results than the existing data attributes.
|
|
37
|
+
self.capacity = data.get("capacity", 0.0)
|
|
38
|
+
self.capacity_company = data.get("capacity_company", "KW")
|
|
39
|
+
self.power = data.get("power", 0)
|
|
40
|
+
self.company = data.get("company", "W")
|
|
41
|
+
self.percent = data.get("percent", 0)
|
|
42
|
+
self.e_today = data.get("e_today", 0.0)
|
|
43
|
+
self.e_total = data.get("e_total", 0.0)
|
|
44
|
+
self.fault_dev_num = data.get("fault_dev_num", 0)
|
|
45
|
+
self.normal_dev_num = data.get("normal_dev_num", 0)
|
|
46
|
+
self.offline_dev_num = data.get("offline_dev_num", 0)
|
|
47
|
+
self.wait_dev_num = data.get("wait_dev_num", 0)
|
|
48
|
+
self.total_co2 = data.get("total_co2", 0)
|
|
49
|
+
self.total_tree = data.get("total_tree", 0.0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class PlantData:
|
|
54
|
+
"""Plant data class.
|
|
55
|
+
|
|
56
|
+
This class represents the data for a Hypon Cloud plant.
|
|
57
|
+
It contains information about the plant's location, energy production,
|
|
58
|
+
identifiers, and status.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
city: str
|
|
62
|
+
country: str
|
|
63
|
+
e_today: float
|
|
64
|
+
e_total: float
|
|
65
|
+
eid: int
|
|
66
|
+
kwhimp: int
|
|
67
|
+
micro: int
|
|
68
|
+
plant_id: str
|
|
69
|
+
plant_name: str
|
|
70
|
+
plant_type: str
|
|
71
|
+
power: int
|
|
72
|
+
status: str
|
|
73
|
+
|
|
74
|
+
def __init__(self, **data) -> None:
|
|
75
|
+
"""Initialize the PlantData class with data from the API.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
data: Dictionary containing plant data from the API.
|
|
79
|
+
"""
|
|
80
|
+
# The data attribute needs to be set manually because the API
|
|
81
|
+
# may return more results than the existing data attributes.
|
|
82
|
+
self.city = data.get("city", "")
|
|
83
|
+
self.country = data.get("country", "")
|
|
84
|
+
self.e_today = data.get("e_today", 0.0)
|
|
85
|
+
self.e_total = data.get("e_total", 0.0)
|
|
86
|
+
self.eid = data.get("eid", 0)
|
|
87
|
+
self.kwhimp = data.get("kwhimp", 0)
|
|
88
|
+
self.micro = data.get("micro", 0)
|
|
89
|
+
self.plant_id = data.get("plant_id", "")
|
|
90
|
+
self.plant_name = data.get("plant_name", "")
|
|
91
|
+
self.plant_type = data.get("plant_type", "")
|
|
92
|
+
self.power = data.get("power", 0)
|
|
93
|
+
self.status = data.get("status", "")
|
|
File without changes
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyponcloud
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library for Hypontech Cloud API
|
|
5
|
+
Author-email: jcisio <jcisio@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jcisio/hyponcloud
|
|
8
|
+
Project-URL: Repository, https://github.com/jcisio/hyponcloud
|
|
9
|
+
Project-URL: Issues, https://github.com/jcisio/hyponcloud/issues
|
|
10
|
+
Keywords: hypontech,solar,inverter,api,async
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Home Automation
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Hypontech Cloud API Python Library
|
|
34
|
+
|
|
35
|
+
A Python library for interacting with the Hypontech Cloud API for solar inverter monitoring.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Async/await support using aiohttp
|
|
40
|
+
- Get plant overview data (power, energy production, device status)
|
|
41
|
+
- Get plant list
|
|
42
|
+
- Automatic token management and refresh
|
|
43
|
+
- Built-in retry logic for rate limiting
|
|
44
|
+
- Type hints for better IDE support
|
|
45
|
+
- Comprehensive error handling
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install hyponcloud
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Basic Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
from hyponcloud import HyponCloud
|
|
60
|
+
|
|
61
|
+
async def main():
|
|
62
|
+
# Create client with your credentials
|
|
63
|
+
async with HyponCloud("your_username", "your_password") as client:
|
|
64
|
+
# Connect and authenticate
|
|
65
|
+
if await client.connect():
|
|
66
|
+
# Get overview data
|
|
67
|
+
overview = await client.get_overview()
|
|
68
|
+
print(f"Current power: {overview.power}W")
|
|
69
|
+
print(f"Today's energy: {overview.e_today}kWh")
|
|
70
|
+
print(f"Total energy: {overview.e_total}kWh")
|
|
71
|
+
|
|
72
|
+
# Get plant list
|
|
73
|
+
plants = await client.get_list()
|
|
74
|
+
print(f"Number of plants: {len(plants)}")
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Using with Custom aiohttp Session
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import aiohttp
|
|
83
|
+
from hyponcloud import HyponCloud
|
|
84
|
+
|
|
85
|
+
async def main():
|
|
86
|
+
async with aiohttp.ClientSession() as session:
|
|
87
|
+
client = HyponCloud("your_username", "your_password", session=session)
|
|
88
|
+
|
|
89
|
+
if await client.connect():
|
|
90
|
+
overview = await client.get_overview()
|
|
91
|
+
print(f"Power: {overview.power}W")
|
|
92
|
+
|
|
93
|
+
asyncio.run(main())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Error Handling
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from hyponcloud import (
|
|
100
|
+
HyponCloud,
|
|
101
|
+
AuthenticationError,
|
|
102
|
+
ConnectionError,
|
|
103
|
+
RateLimitError,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def main():
|
|
107
|
+
try:
|
|
108
|
+
async with HyponCloud("username", "password") as client:
|
|
109
|
+
await client.connect()
|
|
110
|
+
overview = await client.get_overview()
|
|
111
|
+
print(f"Power: {overview.power}W")
|
|
112
|
+
|
|
113
|
+
except AuthenticationError as e:
|
|
114
|
+
print(f"Authentication failed: {e}")
|
|
115
|
+
except RateLimitError as e:
|
|
116
|
+
print(f"Rate limit exceeded: {e}")
|
|
117
|
+
except ConnectionError as e:
|
|
118
|
+
print(f"Connection error: {e}")
|
|
119
|
+
|
|
120
|
+
asyncio.run(main())
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API Reference
|
|
124
|
+
|
|
125
|
+
### HyponCloud
|
|
126
|
+
|
|
127
|
+
Main client class for interacting with the Hypontech Cloud API.
|
|
128
|
+
|
|
129
|
+
#### Methods
|
|
130
|
+
|
|
131
|
+
##### `__init__(username: str, password: str, session: aiohttp.ClientSession | None = None)`
|
|
132
|
+
|
|
133
|
+
Initialize the client.
|
|
134
|
+
|
|
135
|
+
- `username`: Your Hypontech Cloud username
|
|
136
|
+
- `password`: Your Hypontech Cloud password
|
|
137
|
+
- `session`: Optional aiohttp ClientSession. If not provided, one will be created automatically.
|
|
138
|
+
|
|
139
|
+
##### `async connect() -> bool`
|
|
140
|
+
|
|
141
|
+
Authenticate with the API and retrieve access token.
|
|
142
|
+
|
|
143
|
+
**Returns:** `True` if successful, `False` otherwise
|
|
144
|
+
|
|
145
|
+
**Raises:**
|
|
146
|
+
- `AuthenticationError`: Invalid credentials
|
|
147
|
+
- `ConnectionError`: Network error
|
|
148
|
+
- `RateLimitError`: Too many requests
|
|
149
|
+
|
|
150
|
+
##### `async get_overview(retries: int = 3) -> OverviewData`
|
|
151
|
+
|
|
152
|
+
Get plant overview data including power generation and device status.
|
|
153
|
+
|
|
154
|
+
**Parameters:**
|
|
155
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
156
|
+
|
|
157
|
+
**Returns:** `OverviewData` object
|
|
158
|
+
|
|
159
|
+
**Raises:**
|
|
160
|
+
- `AuthenticationError`: Authentication required
|
|
161
|
+
- `ConnectionError`: Network error
|
|
162
|
+
- `RateLimitError`: Too many requests
|
|
163
|
+
|
|
164
|
+
##### `async get_list(retries: int = 3) -> list[dict]`
|
|
165
|
+
|
|
166
|
+
Get list of plants associated with the account.
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
- `retries`: Number of retry attempts on failure (default: 3)
|
|
170
|
+
|
|
171
|
+
**Returns:** List of plant dictionaries
|
|
172
|
+
|
|
173
|
+
**Raises:**
|
|
174
|
+
- `AuthenticationError`: Authentication required
|
|
175
|
+
- `ConnectionError`: Network error
|
|
176
|
+
- `RateLimitError`: Too many requests
|
|
177
|
+
|
|
178
|
+
##### `async close() -> None`
|
|
179
|
+
|
|
180
|
+
Close the aiohttp session (only if created by the library).
|
|
181
|
+
|
|
182
|
+
### OverviewData
|
|
183
|
+
|
|
184
|
+
Data class containing plant overview information.
|
|
185
|
+
|
|
186
|
+
#### Attributes
|
|
187
|
+
|
|
188
|
+
- `capacity` (float): Plant capacity
|
|
189
|
+
- `capacity_company` (str): Capacity unit (e.g., "KW")
|
|
190
|
+
- `power` (int): Current power generation in watts
|
|
191
|
+
- `company` (str): Power unit (e.g., "W")
|
|
192
|
+
- `percent` (int): Percentage value
|
|
193
|
+
- `e_today` (float): Today's energy production in kWh
|
|
194
|
+
- `e_total` (float): Total lifetime energy production in kWh
|
|
195
|
+
- `fault_dev_num` (int): Number of faulty devices
|
|
196
|
+
- `normal_dev_num` (int): Number of normal devices
|
|
197
|
+
- `offline_dev_num` (int): Number of offline devices
|
|
198
|
+
- `wait_dev_num` (int): Number of devices waiting
|
|
199
|
+
- `total_co2` (int): Total CO2 savings
|
|
200
|
+
- `total_tree` (float): Equivalent trees planted
|
|
201
|
+
|
|
202
|
+
### PlantData
|
|
203
|
+
|
|
204
|
+
Data class containing individual plant information.
|
|
205
|
+
|
|
206
|
+
#### Attributes
|
|
207
|
+
|
|
208
|
+
- `city` (str): Plant location city
|
|
209
|
+
- `country` (str): Plant location country
|
|
210
|
+
- `e_today` (float): Today's energy production
|
|
211
|
+
- `e_total` (float): Total energy production
|
|
212
|
+
- `eid` (int): Equipment ID
|
|
213
|
+
- `kwhimp` (int): kWh import
|
|
214
|
+
- `micro` (int): Micro inverter count
|
|
215
|
+
- `plant_id` (str): Unique plant identifier
|
|
216
|
+
- `plant_name` (str): Plant name
|
|
217
|
+
- `plant_type` (str): Plant type
|
|
218
|
+
- `power` (int): Current power
|
|
219
|
+
- `status` (str): Plant status
|
|
220
|
+
|
|
221
|
+
### Exceptions
|
|
222
|
+
|
|
223
|
+
- `HyponCloudError`: Base exception for all library errors
|
|
224
|
+
- `AuthenticationError`: Authentication failed (invalid credentials)
|
|
225
|
+
- `ConnectionError`: Connection to API failed
|
|
226
|
+
- `RateLimitError`: API rate limit exceeded
|
|
227
|
+
|
|
228
|
+
## Development
|
|
229
|
+
|
|
230
|
+
### Setup Development Environment
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Clone the repository
|
|
234
|
+
git clone https://github.com/jcisio/hyponcloud.git
|
|
235
|
+
cd hyponcloud
|
|
236
|
+
|
|
237
|
+
# Install development dependencies
|
|
238
|
+
pip install -e ".[dev]"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Running Tests
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pytest
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Code Formatting
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
ruff check .
|
|
251
|
+
ruff format .
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Type Checking
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
mypy hyponcloud
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Requirements
|
|
261
|
+
|
|
262
|
+
- Python 3.11+
|
|
263
|
+
- aiohttp 3.8.0+
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
268
|
+
|
|
269
|
+
## Contributing
|
|
270
|
+
|
|
271
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
272
|
+
|
|
273
|
+
## Disclaimer
|
|
274
|
+
|
|
275
|
+
This library is not officially associated with or endorsed by Hypontech. Use at your own risk.
|
|
276
|
+
|
|
277
|
+
## Support
|
|
278
|
+
|
|
279
|
+
For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/jcisio/hyponcloud).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
hyponcloud/__init__.py
|
|
6
|
+
hyponcloud/client.py
|
|
7
|
+
hyponcloud/exceptions.py
|
|
8
|
+
hyponcloud/models.py
|
|
9
|
+
hyponcloud/py.typed
|
|
10
|
+
hyponcloud.egg-info/PKG-INFO
|
|
11
|
+
hyponcloud.egg-info/SOURCES.txt
|
|
12
|
+
hyponcloud.egg-info/dependency_links.txt
|
|
13
|
+
hyponcloud.egg-info/requires.txt
|
|
14
|
+
hyponcloud.egg-info/top_level.txt
|
|
15
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hyponcloud
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hyponcloud"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python library for Hypontech Cloud API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "jcisio", email = "jcisio@gmail.com"}
|
|
12
|
+
]
|
|
13
|
+
license = {text = "MIT"}
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Home Automation",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
keywords = ["hypontech", "solar", "inverter", "api", "async"]
|
|
27
|
+
requires-python = ">=3.11"
|
|
28
|
+
dependencies = [
|
|
29
|
+
"aiohttp>=3.8.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=7.0.0",
|
|
35
|
+
"pytest-asyncio>=0.21.0",
|
|
36
|
+
"pytest-cov>=4.0.0",
|
|
37
|
+
"ruff>=0.1.0",
|
|
38
|
+
"mypy>=1.0.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/jcisio/hyponcloud"
|
|
43
|
+
Repository = "https://github.com/jcisio/hyponcloud"
|
|
44
|
+
Issues = "https://github.com/jcisio/hyponcloud/issues"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools]
|
|
47
|
+
packages = ["hyponcloud"]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.package-data]
|
|
50
|
+
hyponcloud = ["py.typed"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
asyncio_mode = "auto"
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
|
|
56
|
+
[tool.ruff]
|
|
57
|
+
line-length = 88
|
|
58
|
+
target-version = "py311"
|
|
59
|
+
|
|
60
|
+
[tool.ruff.lint]
|
|
61
|
+
select = [
|
|
62
|
+
"E", # pycodestyle errors
|
|
63
|
+
"W", # pycodestyle warnings
|
|
64
|
+
"F", # pyflakes
|
|
65
|
+
"I", # isort
|
|
66
|
+
"B", # flake8-bugbear
|
|
67
|
+
"C4", # flake8-comprehensions
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[tool.mypy]
|
|
71
|
+
python_version = "3.11"
|
|
72
|
+
warn_return_any = true
|
|
73
|
+
warn_unused_configs = true
|
|
74
|
+
disallow_untyped_defs = true
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Tests for HyponCloud client."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from aiohttp import ClientSession
|
|
5
|
+
|
|
6
|
+
from hyponcloud import AuthenticationError, ConnectionError, HyponCloud
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
async def test_client_initialization():
|
|
11
|
+
"""Test client initialization."""
|
|
12
|
+
client = HyponCloud("test_user", "test_pass")
|
|
13
|
+
assert client.base_url == "https://api.hypon.cloud/v2"
|
|
14
|
+
await client.close()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.asyncio
|
|
18
|
+
async def test_client_with_session():
|
|
19
|
+
"""Test client with custom session."""
|
|
20
|
+
async with ClientSession() as session:
|
|
21
|
+
client = HyponCloud("test_user", "test_pass", session=session)
|
|
22
|
+
assert client._session == session
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_context_manager():
|
|
27
|
+
"""Test client as context manager."""
|
|
28
|
+
async with HyponCloud("test_user", "test_pass") as client:
|
|
29
|
+
assert client._session is not None
|