daybetter-services-python 1.0.0__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.
@@ -0,0 +1,7 @@
1
+ """DayBetter Python client library."""
2
+
3
+ from .client import DayBetterClient
4
+ from .exceptions import DayBetterError, AuthenticationError, APIError
5
+
6
+ __version__ = "1.0.0"
7
+ __all__ = ["DayBetterClient", "DayBetterError", "AuthenticationError", "APIError"]
@@ -0,0 +1,260 @@
1
+ """DayBetter API client."""
2
+
3
+ import aiohttp
4
+ import logging
5
+ from typing import Any, Dict, List, Optional, Tuple
6
+
7
+ from .exceptions import DayBetterError, AuthenticationError, APIError
8
+
9
+ _LOGGER = logging.getLogger(__name__)
10
+
11
+
12
+ class DayBetterClient:
13
+ """DayBetter API client."""
14
+
15
+ def __init__(
16
+ self,
17
+ token: str,
18
+ base_url: str = "https://a.dbiot.org/daybetter/hass/api/v1.0/"
19
+ ):
20
+ """Initialize the client.
21
+
22
+ Args:
23
+ token: Authentication token
24
+ base_url: Base URL for the API
25
+ """
26
+ self.token = token
27
+ self.base_url = base_url
28
+ self._session: Optional[aiohttp.ClientSession] = None
29
+ self._auth_valid = True
30
+
31
+ async def __aenter__(self):
32
+ """Async context manager entry."""
33
+ self._session = aiohttp.ClientSession()
34
+ return self
35
+
36
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
37
+ """Async context manager exit."""
38
+ if self._session:
39
+ await self._session.close()
40
+ self._session = None
41
+
42
+ def _get_session(self) -> aiohttp.ClientSession:
43
+ """Get or create aiohttp session."""
44
+ if not self._session:
45
+ self._session = aiohttp.ClientSession()
46
+ return self._session
47
+
48
+ def _get_headers(self) -> Dict[str, str]:
49
+ """Get request headers."""
50
+ return {"Authorization": f"Bearer {self.token}"}
51
+
52
+ async def fetch_devices(self) -> List[Dict[str, Any]]:
53
+ """Fetch devices from API.
54
+
55
+ Returns:
56
+ List of device dictionaries
57
+
58
+ Raises:
59
+ AuthenticationError: If authentication fails
60
+ APIError: If API request fails
61
+ """
62
+ try:
63
+ session = self._get_session()
64
+ url = f"{self.base_url}hass/devices"
65
+ headers = self._get_headers()
66
+
67
+ async with session.post(url, headers=headers) as resp:
68
+ if resp.status == 200:
69
+ data = await resp.json()
70
+ devices = data.get("data", [])
71
+ _LOGGER.debug("Fetched devices: %s", devices)
72
+ self._auth_valid = True
73
+ return devices
74
+ elif resp.status == 401:
75
+ _LOGGER.error("Authentication failed - token may be expired")
76
+ self._auth_valid = False
77
+ raise AuthenticationError("Authentication failed - token may be expired")
78
+ else:
79
+ error_text = await resp.text()
80
+ _LOGGER.error("Failed to fetch devices: %s", error_text)
81
+ raise APIError(f"API error {resp.status}: {error_text}")
82
+ except aiohttp.ClientError as e:
83
+ _LOGGER.exception("Client error while fetching devices: %s", e)
84
+ raise APIError(f"Client error: {e}")
85
+ except Exception as e:
86
+ _LOGGER.exception("Exception while fetching devices: %s", e)
87
+ raise DayBetterError(f"Unexpected error: {e}")
88
+
89
+ async def fetch_pids(self) -> Dict[str, Any]:
90
+ """Fetch device type PIDs.
91
+
92
+ Returns:
93
+ Dictionary of device type PIDs
94
+
95
+ Raises:
96
+ AuthenticationError: If authentication fails
97
+ APIError: If API request fails
98
+ """
99
+ try:
100
+ session = self._get_session()
101
+ url = f"{self.base_url}hass/pids"
102
+ headers = self._get_headers()
103
+
104
+ async with session.post(url, headers=headers) as resp:
105
+ if resp.status == 200:
106
+ data = await resp.json()
107
+ self._auth_valid = True
108
+ return data.get("data", {})
109
+ elif resp.status == 401:
110
+ _LOGGER.error("Authentication failed - token may be expired")
111
+ self._auth_valid = False
112
+ raise AuthenticationError("Authentication failed - token may be expired")
113
+ else:
114
+ error_text = await resp.text()
115
+ _LOGGER.error("Failed to fetch PIDs: %s", error_text)
116
+ raise APIError(f"API error {resp.status}: {error_text}")
117
+ except aiohttp.ClientError as e:
118
+ _LOGGER.exception("Client error while fetching PIDs: %s", e)
119
+ raise APIError(f"Client error: {e}")
120
+ except Exception as e:
121
+ _LOGGER.exception("Exception while fetching PIDs: %s", e)
122
+ raise DayBetterError(f"Unexpected error: {e}")
123
+
124
+ async def control_device(
125
+ self,
126
+ device_name: str,
127
+ action: bool,
128
+ brightness: Optional[int] = None,
129
+ hs_color: Optional[Tuple[float, float]] = None,
130
+ color_temp: Optional[int] = None,
131
+ ) -> Dict[str, Any]:
132
+ """Control a device.
133
+
134
+ Args:
135
+ device_name: Name of the device to control
136
+ action: Switch action (True/False)
137
+ brightness: Brightness value (0-255)
138
+ hs_color: Hue and saturation tuple (hue, saturation)
139
+ color_temp: Color temperature in mireds
140
+
141
+ Returns:
142
+ Control result dictionary
143
+
144
+ Raises:
145
+ AuthenticationError: If authentication fails
146
+ APIError: If API request fails
147
+ """
148
+ session = self._get_session()
149
+ url = f"{self.base_url}hass/control"
150
+ headers = self._get_headers()
151
+
152
+ # Priority: color temperature > color > brightness > switch
153
+ if color_temp is not None:
154
+ # Convert mireds to Kelvin
155
+ kelvin = int(1000000 / color_temp)
156
+ payload = {
157
+ "deviceName": device_name,
158
+ "type": 4, # Type 4 is color temperature control
159
+ "kelvin": kelvin,
160
+ }
161
+ elif hs_color is not None:
162
+ h, s = hs_color
163
+ v = (brightness / 255) if brightness is not None else 1.0
164
+ payload = {
165
+ "deviceName": device_name,
166
+ "type": 3,
167
+ "hue": h,
168
+ "saturation": s / 100,
169
+ "brightness": v,
170
+ }
171
+ elif brightness is not None:
172
+ payload = {
173
+ "deviceName": device_name,
174
+ "type": 2,
175
+ "brightness": brightness
176
+ }
177
+ else:
178
+ # Type 1 control switch is used by default
179
+ payload = {
180
+ "deviceName": device_name,
181
+ "type": 1,
182
+ "on": action
183
+ }
184
+
185
+ try:
186
+ async with session.post(url, headers=headers, json=payload) as resp:
187
+ if resp.status == 200:
188
+ self._auth_valid = True
189
+ return await resp.json()
190
+ elif resp.status == 401:
191
+ _LOGGER.error("Authentication failed - token may be expired")
192
+ self._auth_valid = False
193
+ raise AuthenticationError("Authentication failed - token may be expired")
194
+ else:
195
+ error_text = await resp.text()
196
+ _LOGGER.error(
197
+ "Failed to control device %s: HTTP %d - %s",
198
+ device_name, resp.status, error_text
199
+ )
200
+ raise APIError(f"API error {resp.status}: {error_text}")
201
+ except aiohttp.ClientError as e:
202
+ _LOGGER.exception(
203
+ "Client error while controlling device %s: %s", device_name, e
204
+ )
205
+ raise APIError(f"Client error: {e}")
206
+ except Exception as e:
207
+ _LOGGER.exception(
208
+ "Exception while controlling device %s: %s", device_name, e
209
+ )
210
+ raise DayBetterError(f"Unexpected error: {e}")
211
+
212
+ async def fetch_mqtt_config(self) -> Dict[str, Any]:
213
+ """Fetch MQTT connection configuration.
214
+
215
+ Returns:
216
+ MQTT configuration dictionary
217
+
218
+ Raises:
219
+ AuthenticationError: If authentication fails
220
+ APIError: If API request fails
221
+ """
222
+ session = self._get_session()
223
+ url = f"{self.base_url}hass/cert"
224
+ headers = self._get_headers()
225
+ _LOGGER.debug("Requesting MQTT configuration URL: %s", url)
226
+
227
+ try:
228
+ async with session.post(url, headers=headers) as resp:
229
+ _LOGGER.debug("MQTT configuration API response status: %d", resp.status)
230
+
231
+ if resp.status == 200:
232
+ data = await resp.json()
233
+ _LOGGER.debug("MQTT configuration API raw response: %s", data)
234
+ self._auth_valid = True
235
+ return data.get("data", {})
236
+ elif resp.status == 401:
237
+ _LOGGER.error("Authentication failed - token may be expired")
238
+ self._auth_valid = False
239
+ raise AuthenticationError("Authentication failed - token may be expired")
240
+ else:
241
+ error_text = await resp.text()
242
+ _LOGGER.error("Failed to fetch MQTT config: %s", error_text)
243
+ raise APIError(f"API error {resp.status}: {error_text}")
244
+ except aiohttp.ClientError as e:
245
+ _LOGGER.exception("Client error while fetching MQTT config: %s", e)
246
+ raise APIError(f"Client error: {e}")
247
+ except Exception as e:
248
+ _LOGGER.exception("Exception while fetching MQTT config: %s", e)
249
+ raise DayBetterError(f"Unexpected error: {e}")
250
+
251
+ @property
252
+ def is_authenticated(self) -> bool:
253
+ """Check if the API client is authenticated."""
254
+ return self._auth_valid
255
+
256
+ async def close(self):
257
+ """Close the client session."""
258
+ if self._session:
259
+ await self._session.close()
260
+ self._session = None
@@ -0,0 +1,13 @@
1
+ """DayBetter client exceptions."""
2
+
3
+
4
+ class DayBetterError(Exception):
5
+ """Base exception for DayBetter client."""
6
+
7
+
8
+ class AuthenticationError(DayBetterError):
9
+ """Authentication failed."""
10
+
11
+
12
+ class APIError(DayBetterError):
13
+ """API request failed."""
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: daybetter-services-python
3
+ Version: 1.0.0
4
+ Summary: Python client for DayBetter devices and services
5
+ Home-page: https://github.com/THDayBetter/daybetter-python
6
+ Author: THDayBetter
7
+ Author-email: THDayBetter <chenp2368@163.com>
8
+ Maintainer-email: THDayBetter <chenp2368@163.com>
9
+ License-Expression: MIT
10
+ Project-URL: Homepage, https://github.com/THDayBetter/daybetter-python
11
+ Project-URL: Documentation, https://github.com/THDayBetter/daybetter-python#readme
12
+ Project-URL: Repository, https://github.com/THDayBetter/daybetter-python.git
13
+ Project-URL: Bug Tracker, https://github.com/THDayBetter/daybetter-python/issues
14
+ Keywords: daybetter,iot,home automation,mqtt
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: aiohttp>=3.8.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=6.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio; extra == "dev"
31
+ Requires-Dist: black; extra == "dev"
32
+ Requires-Dist: isort; extra == "dev"
33
+ Requires-Dist: flake8; extra == "dev"
34
+ Requires-Dist: mypy; extra == "dev"
35
+ Dynamic: author
36
+ Dynamic: home-page
37
+ Dynamic: license-file
38
+ Dynamic: requires-python
39
+
40
+ # DayBetter Python Client
41
+
42
+ A Python client library for interacting with DayBetter devices and services.
43
+
44
+ ## Features
45
+
46
+ - Device management and control
47
+ - MQTT configuration retrieval
48
+ - Authentication handling
49
+ - Async/await support
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install daybetter-services-python
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ```python
60
+ import asyncio
61
+ from daybetter_python import DayBetterClient
62
+
63
+ async def main():
64
+ async with DayBetterClient(token="your_token") as client:
65
+ # Fetch devices
66
+ devices = await client.fetch_devices()
67
+ print(f"Found {len(devices)} devices")
68
+
69
+ # Control a device
70
+ result = await client.control_device(
71
+ device_name="device_001",
72
+ action=True,
73
+ brightness=80
74
+ )
75
+ print(f"Control result: {result}")
76
+
77
+ if __name__ == "__main__":
78
+ asyncio.run(main())
79
+ ```
80
+
81
+ ## API Reference
82
+
83
+ ### DayBetterClient
84
+
85
+ #### Methods
86
+
87
+ - `fetch_devices()`: Get list of devices
88
+ - `fetch_pids()`: Get device type PIDs
89
+ - `control_device(device_name, action, brightness, hs_color, color_temp)`: Control a device
90
+ - `fetch_mqtt_config()`: Get MQTT configuration
91
+
92
+ ## License
93
+
94
+ MIT License
95
+
96
+ ## Contributing
97
+
98
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,8 @@
1
+ daybetter_python/__init__.py,sha256=8LEDwEsef-VkjE4G2OT-AqYHgk3A3rwv-00Is7glG5I,252
2
+ daybetter_python/client.py,sha256=49l-o-jQBZOIMG9-5pNfUXxKeKCs3h9JCRpATpFIJTk,9957
3
+ daybetter_python/exceptions.py,sha256=G8pURDQVfvCvgsWVi6980CrKDpPwN2aYqQkS4zFpvYM,259
4
+ daybetter_services_python-1.0.0.dist-info/licenses/LICENSE,sha256=DbDD4ZqrGs-Zj3ffqcf35iwo9gM2bBc4WSCYbohM1-M,1068
5
+ daybetter_services_python-1.0.0.dist-info/METADATA,sha256=r3c_4VdJXBnkgCu-zeMT6lIofRkkwqEGenR5lFEefX8,2817
6
+ daybetter_services_python-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ daybetter_services_python-1.0.0.dist-info/top_level.txt,sha256=cRqnqd_T8N2nPkR6x6fqB-DkUgXQ0baNqayVn6RdDFo,17
8
+ daybetter_services_python-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 THDayBetter
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 @@
1
+ daybetter_python