aiorexense 0.1.1__py3-none-any.whl
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.
- aiorexense/__init__.py +86 -0
- aiorexense/api.py +61 -0
- aiorexense/const.py +42 -0
- aiorexense/ws_client.py +197 -0
- aiorexense-0.1.1.dist-info/METADATA +47 -0
- aiorexense-0.1.1.dist-info/RECORD +9 -0
- aiorexense-0.1.1.dist-info/WHEEL +5 -0
- aiorexense-0.1.1.dist-info/licenses/LICENSE +21 -0
- aiorexense-0.1.1.dist-info/top_level.txt +4 -0
aiorexense/__init__.py
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Rexense WS client library init.
|
3
|
+
"""
|
4
|
+
|
5
|
+
__version__ = "0.1.1"
|
6
|
+
|
7
|
+
from .api import get_basic_info
|
8
|
+
from .const import (
|
9
|
+
DEFAULT_PORT,
|
10
|
+
VENDOR_CODE,
|
11
|
+
API_VERSION,
|
12
|
+
FUNCTION_GET_BASIC_INFO,
|
13
|
+
FUNCTION_NOTIFY_STATUS,
|
14
|
+
FUNCTION_INVOKE_CMD,
|
15
|
+
REXENSE_SENSOR_CURRENT,
|
16
|
+
REXENSE_SENSOR_VOLTAGE,
|
17
|
+
REXENSE_SENSOR_POWER_FACTOR,
|
18
|
+
REXENSE_SENSOR_ACTIVE_POWER,
|
19
|
+
REXENSE_SENSOR_APPARENT_POWER,
|
20
|
+
REXENSE_SENSOR_B_CURRENT,
|
21
|
+
REXENSE_SENSOR_B_VOLTAGE,
|
22
|
+
REXENSE_SENSOR_B_POWER_FACTOR,
|
23
|
+
REXENSE_SENSOR_B_ACTIVE_POWER,
|
24
|
+
REXENSE_SENSOR_B_APPARENT_POWER,
|
25
|
+
REXENSE_SENSOR_C_CURRENT,
|
26
|
+
REXENSE_SENSOR_C_VOLTAGE,
|
27
|
+
REXENSE_SENSOR_C_POWER_FACTOR,
|
28
|
+
REXENSE_SENSOR_C_ACTIVE_POWER,
|
29
|
+
REXENSE_SENSOR_C_APPARENT_POWER,
|
30
|
+
REXENSE_SENSOR_TOTAL_ACTIVE_POWER,
|
31
|
+
REXENSE_SENSOR_TOTAL_APPARENT_POWER,
|
32
|
+
REXENSE_SENSOR_CEI,
|
33
|
+
REXENSE_SENSOR_CEE,
|
34
|
+
REXENSE_SENSOR_A_CEI,
|
35
|
+
REXENSE_SENSOR_A_CEE,
|
36
|
+
REXENSE_SENSOR_B_CEI,
|
37
|
+
REXENSE_SENSOR_B_CEE,
|
38
|
+
REXENSE_SENSOR_C_CEI,
|
39
|
+
REXENSE_SENSOR_C_CEE,
|
40
|
+
REXENSE_SENSOR_TEMPERATURE,
|
41
|
+
REXENSE_SENSOR_BATTERY_PERCENTAGE,
|
42
|
+
REXENSE_SENSOR_BATTERY_VOLTAGE,
|
43
|
+
REXENSE_SWITCH_ONOFF,
|
44
|
+
)
|
45
|
+
from .ws_client import RexenseWebsocketClient
|
46
|
+
|
47
|
+
__all__ = [
|
48
|
+
"get_basic_info",
|
49
|
+
"RexenseWebsocketClient",
|
50
|
+
# constants
|
51
|
+
"DEFAULT_PORT",
|
52
|
+
"VENDOR_CODE",
|
53
|
+
"API_VERSION",
|
54
|
+
"FUNCTION_GET_BASIC_INFO",
|
55
|
+
"FUNCTION_NOTIFY_STATUS",
|
56
|
+
"FUNCTION_INVOKE_CMD",
|
57
|
+
"REXENSE_SENSOR_CURRENT",
|
58
|
+
"REXENSE_SENSOR_VOLTAGE",
|
59
|
+
"REXENSE_SENSOR_POWER_FACTOR",
|
60
|
+
"REXENSE_SENSOR_ACTIVE_POWER",
|
61
|
+
"REXENSE_SENSOR_APPARENT_POWER",
|
62
|
+
"REXENSE_SENSOR_B_CURRENT",
|
63
|
+
"REXENSE_SENSOR_B_VOLTAGE",
|
64
|
+
"REXENSE_SENSOR_B_POWER_FACTOR",
|
65
|
+
"REXENSE_SENSOR_B_ACTIVE_POWER",
|
66
|
+
"REXENSE_SENSOR_B_APPARENT_POWER",
|
67
|
+
"REXENSE_SENSOR_C_CURRENT",
|
68
|
+
"REXENSE_SENSOR_C_VOLTAGE",
|
69
|
+
"REXENSE_SENSOR_C_POWER_FACTOR",
|
70
|
+
"REXENSE_SENSOR_C_ACTIVE_POWER",
|
71
|
+
"REXENSE_SENSOR_C_APPARENT_POWER",
|
72
|
+
"REXENSE_SENSOR_TOTAL_ACTIVE_POWER",
|
73
|
+
"REXENSE_SENSOR_TOTAL_APPARENT_POWER",
|
74
|
+
"REXENSE_SENSOR_CEI",
|
75
|
+
"REXENSE_SENSOR_CEE",
|
76
|
+
"REXENSE_SENSOR_A_CEI",
|
77
|
+
"REXENSE_SENSOR_A_CEE",
|
78
|
+
"REXENSE_SENSOR_B_CEI",
|
79
|
+
"REXENSE_SENSOR_B_CEE",
|
80
|
+
"REXENSE_SENSOR_C_CEI",
|
81
|
+
"REXENSE_SENSOR_C_CEE",
|
82
|
+
"REXENSE_SENSOR_TEMPERATURE",
|
83
|
+
"REXENSE_SENSOR_BATTERY_PERCENTAGE",
|
84
|
+
"REXENSE_SENSOR_BATTERY_VOLTAGE",
|
85
|
+
"REXENSE_SWITCH_ONOFF",
|
86
|
+
]
|
aiorexense/api.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
"""
|
2
|
+
HTTP API for Rexense device basic info
|
3
|
+
"""
|
4
|
+
import asyncio
|
5
|
+
import logging
|
6
|
+
from typing import Any, Tuple
|
7
|
+
|
8
|
+
from aiohttp import ClientSession, ClientError
|
9
|
+
|
10
|
+
from .const import (
|
11
|
+
API_VERSION,
|
12
|
+
VENDOR_CODE,
|
13
|
+
FUNCTION_GET_BASIC_INFO,
|
14
|
+
)
|
15
|
+
|
16
|
+
_LOGGER = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
async def get_basic_info(
|
20
|
+
host: str,
|
21
|
+
port: int,
|
22
|
+
session: ClientSession,
|
23
|
+
timeout: int = 5,
|
24
|
+
) -> Tuple[str, str, str, list[dict[str, Any]]]:
|
25
|
+
"""
|
26
|
+
Send an HTTP request to the device, query device_id, model, sw_build_id, feature_map。
|
27
|
+
"""
|
28
|
+
url = f"http://{host}:{port}/rex/device/v1/operate"
|
29
|
+
payload = {
|
30
|
+
"Version": API_VERSION,
|
31
|
+
"VendorCode": VENDOR_CODE,
|
32
|
+
"Timestamp": "0",
|
33
|
+
"Seq": "0",
|
34
|
+
"DeviceId": "",
|
35
|
+
"FunctionCode": FUNCTION_GET_BASIC_INFO,
|
36
|
+
"Payload": {},
|
37
|
+
}
|
38
|
+
try:
|
39
|
+
async with asyncio.timeout(timeout):
|
40
|
+
resp = await session.get(url, json=payload)
|
41
|
+
except (asyncio.TimeoutError, ClientError) as err:
|
42
|
+
_LOGGER.error("HTTP get_basic_info failed: %s", err)
|
43
|
+
raise
|
44
|
+
|
45
|
+
if resp.status != 200:
|
46
|
+
_LOGGER.error("Device %s:%s HTTP status %s", host, port, resp.status)
|
47
|
+
raise ClientError(f"Status {resp.status}")
|
48
|
+
|
49
|
+
data = await resp.json()
|
50
|
+
|
51
|
+
if data.get("FunctionCode") != FUNCTION_GET_BASIC_INFO.replace("GetBasic", "ReportBasic") or not data.get("Payload"):
|
52
|
+
_LOGGER.error("Invalid response format: %s", data)
|
53
|
+
raise ClientError("Invalid response format")
|
54
|
+
|
55
|
+
device_id = data.get("DeviceId", "")
|
56
|
+
payload = data["Payload"]
|
57
|
+
model = payload.get("ModelId", "")
|
58
|
+
sw_build_id = payload.get("SwBuildId", "")
|
59
|
+
feature_map = payload.get("FeatureMap", [])
|
60
|
+
|
61
|
+
return device_id, model, sw_build_id, feature_map
|
aiorexense/const.py
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
"""
|
2
|
+
Rexense const & sensor config
|
3
|
+
"""
|
4
|
+
|
5
|
+
DEFAULT_PORT = 80
|
6
|
+
|
7
|
+
API_VERSION = "1.0"
|
8
|
+
VENDOR_CODE = "Rexense"
|
9
|
+
FUNCTION_GET_BASIC_INFO = "GetBasicInfo"
|
10
|
+
FUNCTION_NOTIFY_STATUS = "NotifyStatus"
|
11
|
+
FUNCTION_INVOKE_CMD = "InvokeCmd"
|
12
|
+
|
13
|
+
REXENSE_SENSOR_CURRENT = {"name":"Current","unit":"A"}
|
14
|
+
REXENSE_SENSOR_VOLTAGE = {"name":"Voltage","unit":"V"}
|
15
|
+
REXENSE_SENSOR_POWER_FACTOR = {"name":"PowerFactor","unit":"%"}
|
16
|
+
REXENSE_SENSOR_ACTIVE_POWER = {"name":"ActivePower","unit":"W"}
|
17
|
+
REXENSE_SENSOR_APPARENT_POWER = {"name":"AprtPower","unit":"VA"}
|
18
|
+
REXENSE_SENSOR_B_CURRENT = {"name":"B_Current","unit":"A"}
|
19
|
+
REXENSE_SENSOR_B_VOLTAGE = {"name":"B_Voltage","unit":"V"}
|
20
|
+
REXENSE_SENSOR_B_POWER_FACTOR = {"name":"B_PowerFactor","unit":"%"}
|
21
|
+
REXENSE_SENSOR_B_ACTIVE_POWER = {"name":"B_ActivePower","unit":"W"}
|
22
|
+
REXENSE_SENSOR_B_APPARENT_POWER = {"name":"B_AprtPower","unit":"VA"}
|
23
|
+
REXENSE_SENSOR_C_CURRENT = {"name":"C_Current","unit":"A"}
|
24
|
+
REXENSE_SENSOR_C_VOLTAGE = {"name":"C_Voltage","unit":"V"}
|
25
|
+
REXENSE_SENSOR_C_POWER_FACTOR = {"name":"C_PowerFactor","unit":"%"}
|
26
|
+
REXENSE_SENSOR_C_ACTIVE_POWER = {"name":"C_ActivePower","unit":"W"}
|
27
|
+
REXENSE_SENSOR_C_APPARENT_POWER = {"name":"C_AprtPower","unit":"VA"}
|
28
|
+
REXENSE_SENSOR_TOTAL_ACTIVE_POWER = {"name":"TotalActivePower","unit":"W"}
|
29
|
+
REXENSE_SENSOR_TOTAL_APPARENT_POWER = {"name":"TotalAprtPower","unit":"VA"}
|
30
|
+
REXENSE_SENSOR_CEI = {"name":"CEI","unit":"Wh"}
|
31
|
+
REXENSE_SENSOR_CEE = {"name":"CEE","unit":"Wh"}
|
32
|
+
REXENSE_SENSOR_A_CEI = {"name":"A_CEI","unit":"Wh"}
|
33
|
+
REXENSE_SENSOR_A_CEE = {"name":"A_CEE","unit":"Wh"}
|
34
|
+
REXENSE_SENSOR_B_CEI = {"name":"B_CEI","unit":"Wh"}
|
35
|
+
REXENSE_SENSOR_B_CEE = {"name":"B_CEE","unit":"Wh"}
|
36
|
+
REXENSE_SENSOR_C_CEI = {"name":"C_CEI","unit":"Wh"}
|
37
|
+
REXENSE_SENSOR_C_CEE = {"name":"C_CEE","unit":"Wh"}
|
38
|
+
REXENSE_SENSOR_TEMPERATURE = {"name":"Temperature","unit":"°C"}
|
39
|
+
REXENSE_SENSOR_BATTERY_PERCENTAGE = {"name":"BatteryPercentage","unit":"%"}
|
40
|
+
REXENSE_SENSOR_BATTERY_VOLTAGE = {"name":"BatteryVoltage","unit":"V"}
|
41
|
+
|
42
|
+
REXENSE_SWITCH_ONOFF = {"name":"PowerSwitch","unit":""}
|
aiorexense/ws_client.py
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
"""
|
2
|
+
WebSocket client for Rexense devices, independent of Home Assistant.
|
3
|
+
"""
|
4
|
+
import asyncio
|
5
|
+
import logging
|
6
|
+
from typing import Any, Callable, Optional
|
7
|
+
|
8
|
+
from aiohttp import ClientSession, ClientWebSocketResponse, ClientWSTimeout, WSMsgType
|
9
|
+
from .const import REXENSE_SWITCH_ONOFF
|
10
|
+
|
11
|
+
_LOGGER = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
|
14
|
+
class RexenseWebsocketClient:
|
15
|
+
"""
|
16
|
+
Manages WebSocket connection to a Rexense device.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
device_id: str,
|
22
|
+
model: str,
|
23
|
+
url: str,
|
24
|
+
sw_build_id: str,
|
25
|
+
feature_map: list[dict[str, Any]],
|
26
|
+
session: Optional[ClientSession] = None,
|
27
|
+
on_update: Optional[Callable[[], None]] = None,
|
28
|
+
) -> None:
|
29
|
+
"""Initialize the WebSocket client."""
|
30
|
+
self.device_id = device_id
|
31
|
+
self.model = model
|
32
|
+
self.sw_build_id = sw_build_id
|
33
|
+
self.feature_map = feature_map
|
34
|
+
self.url = url
|
35
|
+
self.ws: Optional[ClientWebSocketResponse] = None
|
36
|
+
self.connected: bool = False
|
37
|
+
self._running: bool = False
|
38
|
+
self._task: Optional[asyncio.Task] = None
|
39
|
+
self.last_values: dict[str, Any] = {}
|
40
|
+
self.switch_state: Optional[bool] = None
|
41
|
+
self.ping_interval: int = 30
|
42
|
+
|
43
|
+
self.on_update = on_update
|
44
|
+
self._session = session
|
45
|
+
self.signal_update = f"{device_id}_update"
|
46
|
+
|
47
|
+
async def connect(self) -> None:
|
48
|
+
"""Connect to the device and start listening."""
|
49
|
+
if self._running:
|
50
|
+
return
|
51
|
+
# Prepare session
|
52
|
+
if self._session is None:
|
53
|
+
self._session = ClientSession()
|
54
|
+
|
55
|
+
_LOGGER.debug("Attempting WebSocket connection to %s", self.url)
|
56
|
+
try:
|
57
|
+
ws = await self._session.ws_connect(
|
58
|
+
self.url,
|
59
|
+
timeout=ClientWSTimeout(ws_close=10),
|
60
|
+
heartbeat=self.ping_interval,
|
61
|
+
autoping=True,
|
62
|
+
)
|
63
|
+
except Exception as err:
|
64
|
+
_LOGGER.error(
|
65
|
+
"Initial WebSocket connect failed for %s: %s", self.device_id, err
|
66
|
+
)
|
67
|
+
self._running = False
|
68
|
+
raise
|
69
|
+
else:
|
70
|
+
self._running = True
|
71
|
+
self.ws = ws
|
72
|
+
self.connected = True
|
73
|
+
_LOGGER.info("WebSocket connected to device %s", self.device_id)
|
74
|
+
self._task = asyncio.create_task(self._run_loop())
|
75
|
+
|
76
|
+
async def _run_loop(self) -> None:
|
77
|
+
"""Run the WebSocket listen and auto-reconnect loop."""
|
78
|
+
first_try = True
|
79
|
+
while self._running:
|
80
|
+
try:
|
81
|
+
if not first_try:
|
82
|
+
_LOGGER.info("Reconnecting to device %s", self.device_id)
|
83
|
+
ws = await self._session.ws_connect(
|
84
|
+
self.url,
|
85
|
+
timeout=ClientWSTimeout(ws_close=10),
|
86
|
+
heartbeat=self.ping_interval,
|
87
|
+
autoping=True,
|
88
|
+
)
|
89
|
+
self.ws = ws
|
90
|
+
self.connected = True
|
91
|
+
_LOGGER.info("WebSocket reconnected to device %s", self.device_id)
|
92
|
+
else:
|
93
|
+
first_try = False
|
94
|
+
|
95
|
+
assert self.ws is not None
|
96
|
+
async for msg in self.ws:
|
97
|
+
if msg.type == WSMsgType.TEXT:
|
98
|
+
try:
|
99
|
+
data = msg.json()
|
100
|
+
except ValueError as e:
|
101
|
+
_LOGGER.error("Received invalid JSON: %s, data: %s", e, msg.data)
|
102
|
+
continue
|
103
|
+
_LOGGER.debug("Received message: %s", data)
|
104
|
+
self._handle_message(data)
|
105
|
+
elif msg.type == WSMsgType.ERROR:
|
106
|
+
assert self.ws is not None
|
107
|
+
_LOGGER.error(
|
108
|
+
"WebSocket error for %s: %s",
|
109
|
+
self.device_id,
|
110
|
+
self.ws.exception(),
|
111
|
+
)
|
112
|
+
break
|
113
|
+
elif msg.type in (WSMsgType.CLOSED, WSMsgType.CLOSING):
|
114
|
+
_LOGGER.warning(
|
115
|
+
"WebSocket connection closed for %s", self.device_id
|
116
|
+
)
|
117
|
+
break
|
118
|
+
except Exception as err:
|
119
|
+
_LOGGER.error(
|
120
|
+
"WebSocket connection failed for %s: %s", self.device_id, err
|
121
|
+
)
|
122
|
+
# Clean up and maybe reconnect
|
123
|
+
self.connected = False
|
124
|
+
if self.ws is not None:
|
125
|
+
try:
|
126
|
+
await self.ws.close()
|
127
|
+
except Exception:
|
128
|
+
pass
|
129
|
+
finally:
|
130
|
+
self.ws = None
|
131
|
+
|
132
|
+
if self._running:
|
133
|
+
await asyncio.sleep(5)
|
134
|
+
continue
|
135
|
+
|
136
|
+
def _handle_message(self, data: dict[str, Any]) -> None:
|
137
|
+
"""Process incoming message from WebSocket."""
|
138
|
+
func = (data.get("FunctionCode") or data.get("function") or data.get("func"))
|
139
|
+
if isinstance(func, str):
|
140
|
+
func = func.lower()
|
141
|
+
|
142
|
+
if func == "notifystatus":
|
143
|
+
payload = data.get("Payload") or {}
|
144
|
+
_LOGGER.debug("Received payload: %s", payload)
|
145
|
+
for k, v in payload.items():
|
146
|
+
key = k.replace("_1", "")
|
147
|
+
if key == REXENSE_SWITCH_ONOFF['name']:
|
148
|
+
self.switch_state = v not in ("0", False)
|
149
|
+
_LOGGER.debug("Update switch state: %s", self.switch_state)
|
150
|
+
else:
|
151
|
+
_LOGGER.debug("Update sensor %s: %s", key, v)
|
152
|
+
self.last_values[key] = v
|
153
|
+
# Trigger update callback
|
154
|
+
if self.on_update:
|
155
|
+
try:
|
156
|
+
self.on_update()
|
157
|
+
except Exception as e:
|
158
|
+
_LOGGER.error("Error in on_update callback: %s", e)
|
159
|
+
else:
|
160
|
+
_LOGGER.debug("Unhandled function %s: %s", func, data)
|
161
|
+
|
162
|
+
async def async_set_switch(self, on: bool) -> None:
|
163
|
+
"""Send ON/OFF command to device via WebSocket."""
|
164
|
+
if not self.connected or self.ws is None:
|
165
|
+
raise RuntimeError("WebSocket is not connected.")
|
166
|
+
control = "On" if on else "Off"
|
167
|
+
payload = {
|
168
|
+
"FunctionCode": "InvokeCmd",
|
169
|
+
"Payload": {control: {}},
|
170
|
+
}
|
171
|
+
try:
|
172
|
+
await self.ws.send_json(payload)
|
173
|
+
except Exception as err:
|
174
|
+
_LOGGER.error("Failed to send switch command: %s", err)
|
175
|
+
raise
|
176
|
+
|
177
|
+
async def disconnect(self) -> None:
|
178
|
+
"""Disconnect and stop the WebSocket client."""
|
179
|
+
_LOGGER.info("Disconnecting WebSocket for device %s", self.device_id)
|
180
|
+
self._running = False
|
181
|
+
if self.ws is not None:
|
182
|
+
await self.ws.close()
|
183
|
+
if self._task:
|
184
|
+
await self._task
|
185
|
+
self.ws = None
|
186
|
+
self.connected = False
|
187
|
+
|
188
|
+
async def disconnect(self) -> None:
|
189
|
+
"""Disconnect and stop the WebSocket client."""
|
190
|
+
_LOGGER.info("Disconnecting WebSocket for device %s", self.device_id)
|
191
|
+
self._running = False
|
192
|
+
if self.ws is not None:
|
193
|
+
await self.ws.close()
|
194
|
+
if self._task:
|
195
|
+
await self._task
|
196
|
+
self.ws = None
|
197
|
+
self.connected = False
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: aiorexense
|
3
|
+
Version: 0.1.1
|
4
|
+
Summary: Rexense device client library: HTTP API + WebSocket
|
5
|
+
Home-page: https://github.com/RexenseIoT/aiorexense.git
|
6
|
+
Author: Zhejiang Rexense IoT Technology Co., Ltd
|
7
|
+
Author-email: RexenseIoT <yuxiaoqiang@rexense.com>
|
8
|
+
License: MIT License
|
9
|
+
|
10
|
+
Copyright (c) 2025 Zhejiang Rexense IoT Technology Co., Ltd
|
11
|
+
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
13
|
+
of this software and associated documentation files (the “Software”), to deal
|
14
|
+
in the Software without restriction, including without limitation the rights
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
17
|
+
furnished to do so, subject to the following conditions:
|
18
|
+
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
20
|
+
copies or substantial portions of the Software.
|
21
|
+
|
22
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
28
|
+
SOFTWARE.
|
29
|
+
|
30
|
+
Project-URL: Homepage, https://github.com/RexenseIoT/aiorexense.git
|
31
|
+
Project-URL: Repository, https://github.com/RexenseIoT/aiorexense.git
|
32
|
+
Requires-Python: >=3.8
|
33
|
+
Description-Content-Type: text/markdown
|
34
|
+
License-File: LICENSE
|
35
|
+
Requires-Dist: aiohttp>=3.8.0
|
36
|
+
Dynamic: author
|
37
|
+
Dynamic: home-page
|
38
|
+
Dynamic: license-file
|
39
|
+
Dynamic: requires-python
|
40
|
+
|
41
|
+
# aiorexense
|
42
|
+
|
43
|
+
A Rexense device client library featuring an HTTP API for retrieving basic information and WebSocket-based status push functionality.
|
44
|
+
|
45
|
+
## Install
|
46
|
+
```bash
|
47
|
+
pip install aiorexense
|
@@ -0,0 +1,9 @@
|
|
1
|
+
aiorexense/__init__.py,sha256=PSsMF6Ub1HJ5uyxbkTme2Cqt0UQe5e4fcxaA7lAZPL8,2486
|
2
|
+
aiorexense/api.py,sha256=h4ZcySrbgoul5gtd7k6FDF0Ph5S-SZFBd18h5rRnaKw,1817
|
3
|
+
aiorexense/const.py,sha256=fzU3gjbKKNR9zDlhmkA4Qua89NNkeIh5jWu1K8Mg3_s,2036
|
4
|
+
aiorexense/ws_client.py,sha256=IkZX6vd3HDTn0xFjVw_Q2lYJtfPtuLapjCgYzkpM6Cw,7453
|
5
|
+
aiorexense-0.1.1.dist-info/licenses/LICENSE,sha256=BAzGiyUG1qUIc93Ns3z14Ttl2wCHtUortRWIP_rU_Wg,1126
|
6
|
+
aiorexense-0.1.1.dist-info/METADATA,sha256=Zuo8HPKFgCRRDbUzPzgzuBBqX-z3ThS5WGeYEvPSrlo,2128
|
7
|
+
aiorexense-0.1.1.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
8
|
+
aiorexense-0.1.1.dist-info/top_level.txt,sha256=G9Xrl6LXJ6WXbprRvKbytk3-bbNQSwHoK0p0yYXBs5I,31
|
9
|
+
aiorexense-0.1.1.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Zhejiang Rexense IoT Technology Co., Ltd
|
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.
|