hyxi-cloud-api 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Veldkornet
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,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyxi-cloud-api
3
+ Version: 0.1.0
4
+ Summary: An async API client for HYXi Cloud.
5
+ Author-email: Veldkornet <veldkornet@outlook.com>
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: aiohttp>=3.8.0
10
+ Dynamic: license-file
11
+
12
+ # hyxi-cloud-api
13
+
14
+ An asynchronous Python client for interacting with the HYXi Cloud API.
15
+
16
+ This library was primarily built to power the [HYXi Cloud Home Assistant Integration](LINK_TO_YOUR_HA_REPO_HERE), but it can be used in any Python 3.11+ project to fetch telemetry data from HYXi solar inverters and battery systems.
17
+
18
+ ## 📦 Installation
19
+
20
+ You can install the package directly from PyPI:
21
+
22
+ ```bash
23
+ pip install hyxi-cloud-api
24
+ ```
25
+
26
+ ## 🚀 Quick Start
27
+
28
+ This library uses `aiohttp` for non-blocking network requests. You will need to provide your HYXi Cloud Access Key and Secret Key, along with an active `aiohttp.ClientSession`.
29
+
30
+ ```python
31
+ import asyncio
32
+ import aiohttp
33
+ from hyxi_cloud_api import HyxiApiClient
34
+
35
+ async def main():
36
+ # Replace with your actual HYXi Cloud credentials
37
+ ACCESS_KEY = "your_access_key"
38
+ SECRET_KEY = "your_secret_key"
39
+ BASE_URL = "[https://open.hyxicloud.com](https://open.hyxicloud.com)"
40
+
41
+ async with aiohttp.ClientSession() as session:
42
+ # 1. Initialize the client
43
+ client = HyxiApiClient(
44
+ access_key=ACCESS_KEY,
45
+ secret_key=SECRET_KEY,
46
+ base_url=BASE_URL,
47
+ session=session
48
+ )
49
+
50
+ # 2. Fetch device data
51
+ try:
52
+ device_data = await client.get_all_device_data()
53
+ print("Successfully fetched HYXi data:")
54
+ print(device_data)
55
+ except Exception as e:
56
+ print(f"Error communicating with HYXi Cloud: {e}")
57
+
58
+ if __name__ == "__main__":
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ## 🛠️ Requirements
63
+ * Python 3.11 or newer
64
+ * `aiohttp` >= 3.8.0
65
+
66
+ ## ⚠️ Disclaimer
67
+ This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or connected to HYXiPower in any official capacity. Use this software at your own risk.
@@ -0,0 +1,56 @@
1
+ # hyxi-cloud-api
2
+
3
+ An asynchronous Python client for interacting with the HYXi Cloud API.
4
+
5
+ This library was primarily built to power the [HYXi Cloud Home Assistant Integration](LINK_TO_YOUR_HA_REPO_HERE), but it can be used in any Python 3.11+ project to fetch telemetry data from HYXi solar inverters and battery systems.
6
+
7
+ ## 📦 Installation
8
+
9
+ You can install the package directly from PyPI:
10
+
11
+ ```bash
12
+ pip install hyxi-cloud-api
13
+ ```
14
+
15
+ ## 🚀 Quick Start
16
+
17
+ This library uses `aiohttp` for non-blocking network requests. You will need to provide your HYXi Cloud Access Key and Secret Key, along with an active `aiohttp.ClientSession`.
18
+
19
+ ```python
20
+ import asyncio
21
+ import aiohttp
22
+ from hyxi_cloud_api import HyxiApiClient
23
+
24
+ async def main():
25
+ # Replace with your actual HYXi Cloud credentials
26
+ ACCESS_KEY = "your_access_key"
27
+ SECRET_KEY = "your_secret_key"
28
+ BASE_URL = "[https://open.hyxicloud.com](https://open.hyxicloud.com)"
29
+
30
+ async with aiohttp.ClientSession() as session:
31
+ # 1. Initialize the client
32
+ client = HyxiApiClient(
33
+ access_key=ACCESS_KEY,
34
+ secret_key=SECRET_KEY,
35
+ base_url=BASE_URL,
36
+ session=session
37
+ )
38
+
39
+ # 2. Fetch device data
40
+ try:
41
+ device_data = await client.get_all_device_data()
42
+ print("Successfully fetched HYXi data:")
43
+ print(device_data)
44
+ except Exception as e:
45
+ print(f"Error communicating with HYXi Cloud: {e}")
46
+
47
+ if __name__ == "__main__":
48
+ asyncio.run(main())
49
+ ```
50
+
51
+ ## 🛠️ Requirements
52
+ * Python 3.11 or newer
53
+ * `aiohttp` >= 3.8.0
54
+
55
+ ## ⚠️ Disclaimer
56
+ This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or connected to HYXiPower in any official capacity. Use this software at your own risk.
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hyxi-cloud-api"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Veldkornet", email="veldkornet@outlook.com" },
10
+ ]
11
+ description = "An async API client for HYXi Cloud."
12
+ readme = "README.md"
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "aiohttp>=3.8.0",
16
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from .api import HyxiApiClient
@@ -0,0 +1,423 @@
1
+ import asyncio
2
+ import base64
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ import logging
7
+ import os
8
+ import time
9
+ from datetime import UTC
10
+ from datetime import datetime
11
+
12
+ import aiohttp
13
+
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+ # Retry configuration
17
+ MAX_RETRIES = 3
18
+ RETRY_DELAY = 2 # Seconds to wait between retries (multiplied by attempt number)
19
+
20
+
21
+ class HyxiApiClient:
22
+ def __init__(
23
+ self, access_key, secret_key, base_url, session: aiohttp.ClientSession
24
+ ):
25
+ self.access_key = access_key
26
+ self.secret_key = secret_key
27
+ self.base_url = base_url.rstrip("/")
28
+ self.session = session
29
+ self.token = None
30
+ self.token_expires_at = 0
31
+
32
+ def _generate_headers(self, path, method, is_token_request=False):
33
+ """Generates headers matching HYXi's official Java SDK implementation."""
34
+ now_ms = int(time.time() * 1000)
35
+ timestamp = str(now_ms)
36
+
37
+ # 🚀 Generate a truly unique Nonce for concurrent requests
38
+ nonce = os.urandom(4).hex()
39
+
40
+ content_str = "grantType:1" if is_token_request else ""
41
+ hex_hash = hashlib.sha512(content_str.encode("utf-8")).hexdigest()
42
+
43
+ string_to_sign = f"{path}\n{method.upper()}\n{hex_hash}\n"
44
+
45
+ # 🚀 Do not poison the signature with an expired token!
46
+ if is_token_request:
47
+ token_str = ""
48
+ else:
49
+ token_str = self.token if self.token else ""
50
+
51
+ # Build the final string
52
+ sign_string = f"{self.access_key}{token_str}{timestamp}{nonce}{string_to_sign}"
53
+ hmac_bytes = hmac.new(
54
+ self.secret_key.encode("utf-8"), sign_string.encode("utf-8"), hashlib.sha512
55
+ ).digest()
56
+ signature = base64.b64encode(hmac_bytes).decode("utf-8")
57
+
58
+ headers = {
59
+ "accessKey": self.access_key,
60
+ "timestamp": timestamp,
61
+ "nonce": nonce,
62
+ "sign": signature,
63
+ "Content-Type": "application/json",
64
+ }
65
+
66
+ if is_token_request:
67
+ headers["sign-headers"] = "grantType"
68
+ elif token_str:
69
+ headers["Authorization"] = token_str
70
+
71
+ return headers
72
+
73
+ async def _refresh_token(self):
74
+ """Async version of token refresh."""
75
+ if self.token and time.time() < self.token_expires_at:
76
+ return True
77
+
78
+ path = "/api/authorization/v1/token"
79
+
80
+ try:
81
+ async with self.session.post(
82
+ f"{self.base_url}{path}",
83
+ json={"grantType": 1},
84
+ headers=self._generate_headers(path, "POST", is_token_request=True),
85
+ timeout=15,
86
+ ) as response:
87
+ if response.status in [401, 403]:
88
+ _LOGGER.error("HYXi API: Token request unauthorized (401/403)")
89
+ return "auth_failed"
90
+
91
+ response.raise_for_status()
92
+ res = await response.json()
93
+
94
+ if not res.get("success"):
95
+ _LOGGER.error("HYXi API Token Rejected: %s", res)
96
+ if res.get("code") in [401, 403, "401", "403"]:
97
+ return "auth_failed"
98
+ return False
99
+
100
+ data = res.get("data", {})
101
+ token_val = data.get("token") or data.get("access_token")
102
+
103
+ if token_val:
104
+ self.token = f"Bearer {token_val}"
105
+
106
+ # 1. Grab the raw expiration value exactly as the API sent it
107
+ raw_expires_in = data.get("expiresIn") or data.get("expires_in")
108
+ _LOGGER.debug(
109
+ "HYXi API returned raw token expiration: %s seconds",
110
+ raw_expires_in,
111
+ )
112
+
113
+ # 2. Default to 6600 if the API didn't provide one
114
+ expires_in = raw_expires_in or 6600
115
+
116
+ # 3. Apply the 5-minute (300s) safety buffer
117
+ buffer_secs = 300
118
+ self.token_expires_at = time.time() + int(expires_in) - buffer_secs
119
+
120
+ # 4. Log the actual scheduled refresh time
121
+ refresh_time_str = datetime.fromtimestamp(
122
+ self.token_expires_at
123
+ ).strftime("%Y-%m-%d %H:%M:%S")
124
+ _LOGGER.debug(
125
+ "HYXi Token proactive refresh scheduled in %s seconds (at %s)",
126
+ int(expires_in) - buffer_secs,
127
+ refresh_time_str,
128
+ )
129
+ return True
130
+ except Exception as e:
131
+ _LOGGER.error("HYXi Token Request Failed: %s", e)
132
+ return False
133
+
134
+ async def _fetch_device_metrics(self, sn, entry):
135
+ """Helper to fetch detailed metrics for a single device."""
136
+ q_path = "/api/device/v1/queryDeviceData"
137
+ try:
138
+ async with self.session.get(
139
+ f"{self.base_url}{q_path}?deviceSn={sn}",
140
+ headers=self._generate_headers(q_path, "GET"),
141
+ timeout=15,
142
+ ) as resp_q:
143
+ resp_q.raise_for_status()
144
+ res_q = await resp_q.json()
145
+
146
+ if res_q.get("success"):
147
+ data_list = res_q.get("data", [])
148
+ m_raw = {
149
+ item.get("dataKey"): item.get("dataValue")
150
+ for item in data_list
151
+ if isinstance(item, dict) and item.get("dataKey")
152
+ }
153
+ _LOGGER.debug(
154
+ "HYXi Raw Metrics for %s (%s): %s",
155
+ sn,
156
+ entry.get("device_type_code"),
157
+ m_raw,
158
+ )
159
+ entry["metrics"].update(m_raw)
160
+
161
+ def get_f(key, data_map, mult=1.0):
162
+ try:
163
+ val = data_map.get(key)
164
+ if val is None or val == "":
165
+ return 0.0
166
+ return round(float(val) * mult, 2)
167
+ except (ValueError, TypeError):
168
+ return 0.0
169
+
170
+ if "gridP" in m_raw or "pbat" in m_raw:
171
+ grid = get_f("gridP", m_raw, 1000.0)
172
+ pbat = get_f("pbat", m_raw)
173
+
174
+ entry["metrics"].update(
175
+ {
176
+ "home_load": get_f("ph1Loadp", m_raw)
177
+ + get_f("ph2Loadp", m_raw)
178
+ + get_f("ph3Loadp", m_raw),
179
+ "grid_import": abs(grid) if grid < 0 else 0,
180
+ "grid_export": grid if grid > 0 else 0,
181
+ "bat_charging": abs(pbat) if pbat < 0 else 0,
182
+ "bat_discharging": pbat if pbat > 0 else 0,
183
+ "bat_charge_total": get_f("batCharge", m_raw),
184
+ "bat_discharge_total": get_f("batDisCharge", m_raw),
185
+ }
186
+ )
187
+ else:
188
+ _LOGGER.warning(
189
+ "HYXi API metrics rejected for %s: %s", sn, res_q.get("message")
190
+ )
191
+ except Exception as e:
192
+ _LOGGER.error("Error fetching metrics for %s: %s", sn, e)
193
+
194
+ async def _fetch_device_info(self, sn, entry):
195
+ """Helper to fetch static device info (firmware, capacity, limits)."""
196
+ i_path = "/api/device/v1/queryDeviceInfo"
197
+ try:
198
+ async with self.session.get(
199
+ f"{self.base_url}{i_path}?deviceSn={sn}",
200
+ headers=self._generate_headers(i_path, "GET"),
201
+ timeout=15,
202
+ ) as resp_i:
203
+ res_i = await resp_i.json()
204
+
205
+ if res_i.get("success"):
206
+ data_list = res_i.get("data", [])
207
+ i_raw = {
208
+ item.get("dataKey"): item.get("dataValue")
209
+ for item in data_list
210
+ if isinstance(item, dict) and item.get("dataKey")
211
+ }
212
+
213
+ # 👇 This will dump the EXACT info the cloud sends back
214
+ _LOGGER.debug("HYXi Raw INFO for %s: %s", sn, i_raw)
215
+
216
+ # Smart Firmware Finder
217
+ sw_ver = (
218
+ i_raw.get("swVerSys")
219
+ or i_raw.get("swVerMaster")
220
+ or i_raw.get("swVer")
221
+ )
222
+ if sw_ver:
223
+ entry["sw_version"] = sw_ver
224
+
225
+ # Merge static info into metrics
226
+ entry["metrics"].update(
227
+ {
228
+ "signalIntensity": i_raw.get("signalIntensity"),
229
+ "signalVal": i_raw.get("signalVal"),
230
+ "wifiVer": i_raw.get("wifiVer"),
231
+ "comMode": i_raw.get("comMode"),
232
+ "batCap": i_raw.get("batCap"),
233
+ "maxChargePower": i_raw.get("maxChargePower")
234
+ or i_raw.get("maxChargingDischargingPower"),
235
+ "maxDischargePower": i_raw.get("maxDischargePower")
236
+ or i_raw.get("maxChargingDischargingPower"),
237
+ }
238
+ )
239
+ else:
240
+ _LOGGER.warning(
241
+ "HYXi INFO API Rejected for %s: %s", sn, res_i.get("message")
242
+ )
243
+
244
+ except Exception as e:
245
+ _LOGGER.error("Error fetching device info for %s: %s", sn, e)
246
+
247
+ async def _fetch_all_for_device(self, sn, entry, dev_type):
248
+ """Fires off concurrent requests for Data and Info, merging the results."""
249
+ tasks = [self._fetch_device_info(sn, entry)]
250
+
251
+ # Only fetch metrics for devices that actually generate live power data
252
+ if dev_type != "COLLECTOR":
253
+ tasks.append(self._fetch_device_metrics(sn, entry))
254
+
255
+ await asyncio.gather(*tasks)
256
+ return sn, entry
257
+
258
+ async def get_all_device_data(self):
259
+ """Fetches data with built-in retry logic and returns attempt count."""
260
+
261
+ for attempt in range(1, MAX_RETRIES + 1):
262
+ try:
263
+ data = await self._execute_fetch_all()
264
+ if data == "auth_failed":
265
+ return None # Hard fail, don't retry bad credentials
266
+ if data:
267
+ # ✅ Success
268
+ return {"data": data, "attempts": attempt}
269
+
270
+ # If we get here, data was None (soft failure). Trigger a retry manually.
271
+ raise aiohttp.ClientError("Fetch returned None, triggering retry.")
272
+
273
+ except (aiohttp.ClientError, TimeoutError) as err:
274
+ if attempt < MAX_RETRIES:
275
+ wait_time = attempt * RETRY_DELAY
276
+ _LOGGER.debug(
277
+ "HYXi Connection attempt %s/%s failed. Retrying in %ss... (Error: %s)",
278
+ attempt,
279
+ MAX_RETRIES,
280
+ wait_time,
281
+ err,
282
+ )
283
+ await asyncio.sleep(wait_time)
284
+ else:
285
+ _LOGGER.error(
286
+ "HYXi Cloud connection failed after %s attempts: %s",
287
+ MAX_RETRIES,
288
+ err,
289
+ )
290
+ except Exception:
291
+ _LOGGER.exception("HYXi Unexpected Code Crash:")
292
+ break
293
+
294
+ return None
295
+
296
+ async def _execute_fetch_all(self):
297
+ """The actual fetching logic moved to a private method for the retry loop."""
298
+
299
+ # 🧪 MOCK OVERRIDE START
300
+ mock_file = os.path.join(os.path.dirname(__file__), "mock_data.json")
301
+
302
+ # Helper function to read the file synchronously
303
+ def load_mock():
304
+ if os.path.exists(mock_file):
305
+ with open(mock_file, encoding="utf-8") as f:
306
+ return json.load(f)
307
+ return "NOT_FOUND"
308
+
309
+ try:
310
+ mock_data = await asyncio.to_thread(load_mock)
311
+ if mock_data != "NOT_FOUND":
312
+ _LOGGER.warning(
313
+ "HYXi API 🧪: MOCK MODE ACTIVE - Successfully loaded %s", mock_file
314
+ )
315
+ return mock_data
316
+ except json.JSONDecodeError as e:
317
+ _LOGGER.error(
318
+ "HYXi API 🧪: MOCK FILE FOUND, BUT JSON IS INVALID! Error: %s", e
319
+ )
320
+ return None
321
+ except Exception as e:
322
+ _LOGGER.error("HYXi API 🧪: Unexpected error reading mock file: %s", e)
323
+ return None
324
+ # 🧪 MOCK OVERRIDE END
325
+
326
+ token_status = await self._refresh_token()
327
+
328
+ if token_status == "auth_failed":
329
+ return "auth_failed"
330
+ if not token_status:
331
+ return None
332
+
333
+ results = {}
334
+ now = datetime.now(UTC).isoformat()
335
+
336
+ # 1. Get Plants
337
+ p_path = "/api/plant/v1/page"
338
+ async with self.session.post(
339
+ f"{self.base_url}{p_path}",
340
+ json={"pageSize": 10, "currentPage": 1},
341
+ headers=self._generate_headers(p_path, "POST"),
342
+ timeout=15,
343
+ ) as resp_p:
344
+ resp_p.raise_for_status()
345
+ res_p = await resp_p.json()
346
+
347
+ if not res_p.get("success"):
348
+ # 🚀 If the server rejects the token, wipe it and force a retry!
349
+ if res_p.get("code") in ["A000002", "A000005"]:
350
+ _LOGGER.debug(
351
+ "HYXi Server rejected our token (A000002/A000005). Forcing immediate token refresh..."
352
+ )
353
+ self.token = None
354
+ self.token_expires_at = 0
355
+ # Raising this error kicks it back up to the retry loop
356
+ raise aiohttp.ClientError("Server rejected token")
357
+
358
+ _LOGGER.error("HYXi API Plant Fetch Rejected: %s", res_p)
359
+ return None
360
+
361
+ data_p = res_p.get("data", {})
362
+ plants = data_p.get("list", []) if isinstance(data_p, dict) else []
363
+ metric_tasks = []
364
+
365
+ for p in plants:
366
+ plant_id = p.get("plantId")
367
+ if not plant_id:
368
+ continue
369
+
370
+ # 2. Get Devices
371
+ d_path = "/api/plant/v1/devicePage"
372
+ async with self.session.post(
373
+ f"{self.base_url}{d_path}",
374
+ json={"plantId": plant_id, "pageSize": 50, "currentPage": 1},
375
+ headers=self._generate_headers(d_path, "POST"),
376
+ timeout=15,
377
+ ) as resp_d:
378
+ resp_d.raise_for_status()
379
+ res_d = await resp_d.json()
380
+
381
+ if not res_d.get("success"):
382
+ _LOGGER.error(
383
+ "HYXi API Device Fetch Rejected for Plant %s: %s", plant_id, res_d
384
+ )
385
+ continue
386
+
387
+ data_val = res_d.get("data", {})
388
+ devices = (
389
+ data_val
390
+ if isinstance(data_val, list)
391
+ else data_val.get("deviceList", [])
392
+ if isinstance(data_val, dict)
393
+ else []
394
+ )
395
+
396
+ for d in devices:
397
+ sn = d.get("deviceSn")
398
+ if not sn:
399
+ continue
400
+
401
+ dev_type = d.get("deviceType") or "UNKNOWN"
402
+ friendly_name = dev_type.replace("_", " ").title()
403
+
404
+ entry = {
405
+ "sn": sn,
406
+ "device_name": d.get("deviceName") or f"{friendly_name} {sn}",
407
+ "model": friendly_name,
408
+ "device_type_code": dev_type,
409
+ "sw_version": d.get("swVer"),
410
+ "hw_version": d.get("hwVer"),
411
+ "metrics": {"last_seen": now},
412
+ }
413
+
414
+ metric_tasks.append(self._fetch_all_for_device(sn, entry, dev_type))
415
+
416
+ # 3. Concurrent Metrics
417
+ if metric_tasks:
418
+ updated_entries = await asyncio.gather(*metric_tasks)
419
+ for sn, entry in updated_entries:
420
+ if sn:
421
+ results[sn] = entry
422
+
423
+ return results
@@ -0,0 +1,67 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyxi-cloud-api
3
+ Version: 0.1.0
4
+ Summary: An async API client for HYXi Cloud.
5
+ Author-email: Veldkornet <veldkornet@outlook.com>
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: aiohttp>=3.8.0
10
+ Dynamic: license-file
11
+
12
+ # hyxi-cloud-api
13
+
14
+ An asynchronous Python client for interacting with the HYXi Cloud API.
15
+
16
+ This library was primarily built to power the [HYXi Cloud Home Assistant Integration](LINK_TO_YOUR_HA_REPO_HERE), but it can be used in any Python 3.11+ project to fetch telemetry data from HYXi solar inverters and battery systems.
17
+
18
+ ## 📦 Installation
19
+
20
+ You can install the package directly from PyPI:
21
+
22
+ ```bash
23
+ pip install hyxi-cloud-api
24
+ ```
25
+
26
+ ## 🚀 Quick Start
27
+
28
+ This library uses `aiohttp` for non-blocking network requests. You will need to provide your HYXi Cloud Access Key and Secret Key, along with an active `aiohttp.ClientSession`.
29
+
30
+ ```python
31
+ import asyncio
32
+ import aiohttp
33
+ from hyxi_cloud_api import HyxiApiClient
34
+
35
+ async def main():
36
+ # Replace with your actual HYXi Cloud credentials
37
+ ACCESS_KEY = "your_access_key"
38
+ SECRET_KEY = "your_secret_key"
39
+ BASE_URL = "[https://open.hyxicloud.com](https://open.hyxicloud.com)"
40
+
41
+ async with aiohttp.ClientSession() as session:
42
+ # 1. Initialize the client
43
+ client = HyxiApiClient(
44
+ access_key=ACCESS_KEY,
45
+ secret_key=SECRET_KEY,
46
+ base_url=BASE_URL,
47
+ session=session
48
+ )
49
+
50
+ # 2. Fetch device data
51
+ try:
52
+ device_data = await client.get_all_device_data()
53
+ print("Successfully fetched HYXi data:")
54
+ print(device_data)
55
+ except Exception as e:
56
+ print(f"Error communicating with HYXi Cloud: {e}")
57
+
58
+ if __name__ == "__main__":
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ## 🛠️ Requirements
63
+ * Python 3.11 or newer
64
+ * `aiohttp` >= 3.8.0
65
+
66
+ ## ⚠️ Disclaimer
67
+ This is an unofficial, community-driven project. It is not affiliated with, endorsed by, or connected to HYXiPower in any official capacity. Use this software at your own risk.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/hyxi_cloud_api/__init__.py
5
+ src/hyxi_cloud_api/api.py
6
+ src/hyxi_cloud_api.egg-info/PKG-INFO
7
+ src/hyxi_cloud_api.egg-info/SOURCES.txt
8
+ src/hyxi_cloud_api.egg-info/dependency_links.txt
9
+ src/hyxi_cloud_api.egg-info/requires.txt
10
+ src/hyxi_cloud_api.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ aiohttp>=3.8.0
@@ -0,0 +1 @@
1
+ hyxi_cloud_api