daybetter-services-python 1.0.7__tar.gz → 1.0.9__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.
Files changed (19) hide show
  1. daybetter_services_python-1.0.9/PKG-INFO +127 -0
  2. daybetter_services_python-1.0.9/README.md +92 -0
  3. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_python/__init__.py +1 -1
  4. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_python/client.py +78 -89
  5. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_python/exceptions.py +6 -6
  6. daybetter_services_python-1.0.9/daybetter_services_python.egg-info/PKG-INFO +127 -0
  7. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_services_python.egg-info/SOURCES.txt +2 -2
  8. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/pyproject.toml +9 -5
  9. daybetter_services_python-1.0.9/tests/test_placeholder.py +7 -0
  10. daybetter_services_python-1.0.7/PKG-INFO +0 -99
  11. daybetter_services_python-1.0.7/README.md +0 -59
  12. daybetter_services_python-1.0.7/daybetter_services_python.egg-info/PKG-INFO +0 -99
  13. daybetter_services_python-1.0.7/setup.py +0 -38
  14. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/LICENSE +0 -0
  15. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_python/py.typed +0 -0
  16. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_services_python.egg-info/dependency_links.txt +0 -0
  17. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_services_python.egg-info/requires.txt +0 -0
  18. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/daybetter_services_python.egg-info/top_level.txt +0 -0
  19. {daybetter_services_python-1.0.7 → daybetter_services_python-1.0.9}/setup.cfg +0 -0
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: daybetter-services-python
3
+ Version: 1.0.9
4
+ Summary: Python client for DayBetter devices and services
5
+ Author-email: THDayBetter <chenp2368@163.com>
6
+ Maintainer-email: THDayBetter <chenp2368@163.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/THDayBetter/daybetter-services-python
9
+ Project-URL: Documentation, https://github.com/THDayBetter/daybetter-services-python#readme
10
+ Project-URL: Repository, https://github.com/THDayBetter/daybetter-services-python.git
11
+ Project-URL: Bug Tracker, https://github.com/THDayBetter/daybetter-services-python/issues
12
+ Keywords: daybetter,iot,home automation,mqtt
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.8.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=6.0; extra == "dev"
29
+ Requires-Dist: pytest-asyncio; extra == "dev"
30
+ Requires-Dist: black; extra == "dev"
31
+ Requires-Dist: isort; extra == "dev"
32
+ Requires-Dist: flake8; extra == "dev"
33
+ Requires-Dist: mypy; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # DayBetter Services Python Client
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
39
+ [![Python Versions](https://img.shields.io/pypi/pyversions/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
40
+ [![License](https://img.shields.io/pypi/l/daybetter-services-python.svg)](LICENSE)
41
+
42
+ Asynchronous client library used by the [Home Assistant DayBetter Services integration](https://github.com/home-assistant/core/pull/154677).
43
+ It handles authentication, device discovery, status polling, PID metadata and device control against the DayBetter cloud API.
44
+
45
+ ## Features
46
+
47
+ - Fully async `DayBetterClient` with context-manager support
48
+ - Automatic environment selection (test/prod) based on `hass_code`
49
+ - Convenience helpers for common API endpoints (devices, statuses, MQTT config)
50
+ - Sensor-focused workflow `fetch_sensor_data()` that merges devices, statuses and PID filters
51
+ - Type hints + `py.typed` for first-class editor / mypy support
52
+ - Published on PyPI for Home Assistant and other integrations
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install daybetter-services-python
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ ```python
63
+ import asyncio
64
+ from daybetter_python import DayBetterClient
65
+
66
+ async def main() -> None:
67
+ async with DayBetterClient(token="YOUR_TOKEN", hass_code="db-xxxx") as client:
68
+ # Fetch merged sensor payloads (used by Home Assistant)
69
+ sensors = await client.fetch_sensor_data()
70
+ for item in sensors:
71
+ print(item["deviceName"], item.get("temp"), item.get("humi"))
72
+
73
+ # Control a device (brightness example)
74
+ await client.control_device(
75
+ device_name="device_001",
76
+ action=True,
77
+ brightness=180,
78
+ )
79
+
80
+ if __name__ == "__main__":
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ## Home Assistant Integration
85
+
86
+ The official integration PR can be followed here: [home-assistant/core#154677](https://github.com/home-assistant/core/pull/154677).
87
+ The integration imports this library and simply calls `client.fetch_sensor_data()` inside a data coordinator, so all business logic lives in this package.
88
+
89
+ See `docs/homeassistant.md` for:
90
+ - Installation instructions (pip, custom component)
91
+ - How to obtain the DayBetter token and `hass_code`
92
+ - Sample `configuration.yaml` snippets
93
+ - Known limitations and future roadmap
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ git clone https://github.com/THDayBetter/daybetter-services-python.git
99
+ cd daybetter-services-python
100
+ python3 -m venv .venv
101
+ source .venv/bin/activate
102
+ pip install -e .[dev]
103
+
104
+ # Quality checks
105
+ flake8 daybetter_python
106
+ mypy daybetter_python
107
+ pytest
108
+ ```
109
+
110
+ Pull requests are welcome! Please open an issue if you encounter problems with the DayBetter cloud API.
111
+
112
+ ## Release Process
113
+
114
+ 1. Update `pyproject.toml` and `daybetter_python/__init__.py` with the new version.
115
+ 2. Document changes in `CHANGELOG.md`.
116
+ 3. Build and upload:
117
+ ```bash
118
+ rm -rf dist build *.egg-info
119
+ python -m build
120
+ twine check dist/*
121
+ twine upload dist/*
122
+ ```
123
+ 4. Create a Git tag (e.g., `git tag v1.0.7 && git push origin v1.0.7`).
124
+
125
+ ## License
126
+
127
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,92 @@
1
+ # DayBetter Services Python Client
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
4
+ [![Python Versions](https://img.shields.io/pypi/pyversions/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
5
+ [![License](https://img.shields.io/pypi/l/daybetter-services-python.svg)](LICENSE)
6
+
7
+ Asynchronous client library used by the [Home Assistant DayBetter Services integration](https://github.com/home-assistant/core/pull/154677).
8
+ It handles authentication, device discovery, status polling, PID metadata and device control against the DayBetter cloud API.
9
+
10
+ ## Features
11
+
12
+ - Fully async `DayBetterClient` with context-manager support
13
+ - Automatic environment selection (test/prod) based on `hass_code`
14
+ - Convenience helpers for common API endpoints (devices, statuses, MQTT config)
15
+ - Sensor-focused workflow `fetch_sensor_data()` that merges devices, statuses and PID filters
16
+ - Type hints + `py.typed` for first-class editor / mypy support
17
+ - Published on PyPI for Home Assistant and other integrations
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install daybetter-services-python
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ import asyncio
29
+ from daybetter_python import DayBetterClient
30
+
31
+ async def main() -> None:
32
+ async with DayBetterClient(token="YOUR_TOKEN", hass_code="db-xxxx") as client:
33
+ # Fetch merged sensor payloads (used by Home Assistant)
34
+ sensors = await client.fetch_sensor_data()
35
+ for item in sensors:
36
+ print(item["deviceName"], item.get("temp"), item.get("humi"))
37
+
38
+ # Control a device (brightness example)
39
+ await client.control_device(
40
+ device_name="device_001",
41
+ action=True,
42
+ brightness=180,
43
+ )
44
+
45
+ if __name__ == "__main__":
46
+ asyncio.run(main())
47
+ ```
48
+
49
+ ## Home Assistant Integration
50
+
51
+ The official integration PR can be followed here: [home-assistant/core#154677](https://github.com/home-assistant/core/pull/154677).
52
+ The integration imports this library and simply calls `client.fetch_sensor_data()` inside a data coordinator, so all business logic lives in this package.
53
+
54
+ See `docs/homeassistant.md` for:
55
+ - Installation instructions (pip, custom component)
56
+ - How to obtain the DayBetter token and `hass_code`
57
+ - Sample `configuration.yaml` snippets
58
+ - Known limitations and future roadmap
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ git clone https://github.com/THDayBetter/daybetter-services-python.git
64
+ cd daybetter-services-python
65
+ python3 -m venv .venv
66
+ source .venv/bin/activate
67
+ pip install -e .[dev]
68
+
69
+ # Quality checks
70
+ flake8 daybetter_python
71
+ mypy daybetter_python
72
+ pytest
73
+ ```
74
+
75
+ Pull requests are welcome! Please open an issue if you encounter problems with the DayBetter cloud API.
76
+
77
+ ## Release Process
78
+
79
+ 1. Update `pyproject.toml` and `daybetter_python/__init__.py` with the new version.
80
+ 2. Document changes in `CHANGELOG.md`.
81
+ 3. Build and upload:
82
+ ```bash
83
+ rm -rf dist build *.egg-info
84
+ python -m build
85
+ twine check dist/*
86
+ twine upload dist/*
87
+ ```
88
+ 4. Create a Git tag (e.g., `git tag v1.0.7 && git push origin v1.0.7`).
89
+
90
+ ## License
91
+
92
+ This project is licensed under the [MIT License](LICENSE).
@@ -3,5 +3,5 @@
3
3
  from .client import DayBetterClient
4
4
  from .exceptions import DayBetterError, AuthenticationError, APIError
5
5
 
6
- __version__ = "1.0.7"
6
+ __version__ = "1.0.8"
7
7
  __all__ = ["DayBetterClient", "DayBetterError", "AuthenticationError", "APIError"]
@@ -1,61 +1,61 @@
1
1
  """DayBetter API client."""
2
2
 
3
- import aiohttp
4
3
  import logging
5
4
  from typing import Any, Dict, List, Optional, Tuple, Type
6
5
 
7
- from .exceptions import DayBetterError, AuthenticationError, APIError
6
+ import aiohttp
7
+
8
+ from .exceptions import APIError, AuthenticationError, DayBetterError
8
9
 
9
10
  _LOGGER = logging.getLogger(__name__)
10
11
 
11
12
 
12
13
  class DayBetterClient:
13
14
  """DayBetter API client."""
14
-
15
- # 测试环境URL
15
+
16
16
  TEST_BASE_URL = "https://cloud.v2.dbiot.link/daybetter/hass/api/v1.0/"
17
- # 正式环境URL
18
17
  PROD_BASE_URL = "https://a.dbiot.org/daybetter/hass/api/v1.0/"
19
-
18
+
20
19
  def __init__(
21
- self,
22
- token: str,
20
+ self,
21
+ token: str,
23
22
  base_url: Optional[str] = None,
24
- hass_code: Optional[str] = None
23
+ hass_code: Optional[str] = None,
24
+ session: Optional[aiohttp.ClientSession] = None,
25
25
  ):
26
26
  """Initialize the client.
27
-
27
+
28
28
  Args:
29
29
  token: Authentication token
30
- base_url: Base URL for the API (optional, will be determined by hass_code if not provided)
31
- hass_code: Home Assistant integration code (optional, if provided and starts with "db-",
32
- will use production environment)
30
+ base_url: Base URL for the API (optional, determined by hass_code if not provided)
31
+ hass_code: Home Assistant integration code (optional, if provided and starts
32
+ with "db-", will use production environment)
33
+ session: Optional aiohttp ClientSession. If provided, the client will use it
34
+ and will not close it when close() is called (caller owns the session).
33
35
  """
34
36
  self.token = token
35
-
36
- # 根据 hass_code 或 base_url 确定使用的环境
37
+
37
38
  if base_url is not None:
38
- # 如果明确指定了 base_url,使用指定的 URL
39
39
  self.base_url = base_url
40
40
  elif hass_code is not None and hass_code.startswith("db-"):
41
- # 如果 hass_code 以 "db-" 开头,使用正式环境
42
41
  self.base_url = self.PROD_BASE_URL
43
42
  _LOGGER.debug("Using production environment based on hass_code")
44
43
  else:
45
- # 默认使用测试环境
46
44
  self.base_url = self.TEST_BASE_URL
47
45
  _LOGGER.debug("Using test environment")
48
-
49
- self._session: Optional[aiohttp.ClientSession] = None
46
+
47
+ self._session: Optional[aiohttp.ClientSession] = session
48
+ self._own_session: bool = session is None
50
49
  self._auth_valid = True
51
50
  self._devices: List[Dict[str, Any]] = []
52
51
  self._pids: Dict[str, Any] = {}
53
-
52
+
54
53
  async def __aenter__(self):
55
54
  """Async context manager entry."""
56
- self._session = aiohttp.ClientSession()
55
+ if self._session is None:
56
+ self._session = aiohttp.ClientSession()
57
57
  return self
58
-
58
+
59
59
  async def __aexit__(
60
60
  self,
61
61
  exc_type: Optional[Type[BaseException]],
@@ -63,26 +63,26 @@ class DayBetterClient:
63
63
  exc_tb: Optional[Any],
64
64
  ) -> None:
65
65
  """Async context manager exit."""
66
- if self._session:
66
+ if self._own_session and self._session is not None:
67
67
  await self._session.close()
68
68
  self._session = None
69
-
69
+
70
70
  def _get_session(self) -> aiohttp.ClientSession:
71
71
  """Get or create aiohttp session."""
72
- if not self._session:
72
+ if self._session is None:
73
73
  self._session = aiohttp.ClientSession()
74
74
  return self._session
75
-
75
+
76
76
  def _get_headers(self) -> Dict[str, str]:
77
77
  """Get request headers."""
78
78
  return {"Authorization": f"Bearer {self.token}"}
79
-
79
+
80
80
  async def fetch_devices(self) -> List[Dict[str, Any]]:
81
81
  """Fetch devices from API.
82
-
82
+
83
83
  Returns:
84
84
  List of device dictionaries
85
-
85
+
86
86
  Raises:
87
87
  AuthenticationError: If authentication fails
88
88
  APIError: If API request fails
@@ -91,7 +91,7 @@ class DayBetterClient:
91
91
  session = self._get_session()
92
92
  url = f"{self.base_url}hass/devices"
93
93
  headers = self._get_headers()
94
-
94
+
95
95
  async with session.post(url, headers=headers) as resp:
96
96
  if resp.status == 200:
97
97
  data = await resp.json()
@@ -113,13 +113,13 @@ class DayBetterClient:
113
113
  except Exception as e:
114
114
  _LOGGER.exception("Exception while fetching devices: %s", e)
115
115
  raise DayBetterError(f"Unexpected error: {e}")
116
-
116
+
117
117
  async def fetch_pids(self) -> Dict[str, Any]:
118
118
  """Fetch device type PIDs.
119
-
119
+
120
120
  Returns:
121
121
  Dictionary of device type PIDs
122
-
122
+
123
123
  Raises:
124
124
  AuthenticationError: If authentication fails
125
125
  APIError: If API request fails
@@ -128,7 +128,7 @@ class DayBetterClient:
128
128
  session = self._get_session()
129
129
  url = f"{self.base_url}hass/pids"
130
130
  headers = self._get_headers()
131
-
131
+
132
132
  async with session.post(url, headers=headers) as resp:
133
133
  if resp.status == 200:
134
134
  data = await resp.json()
@@ -148,7 +148,7 @@ class DayBetterClient:
148
148
  except Exception as e:
149
149
  _LOGGER.exception("Exception while fetching PIDs: %s", e)
150
150
  raise DayBetterError(f"Unexpected error: {e}")
151
-
151
+
152
152
  async def control_device(
153
153
  self,
154
154
  device_name: str,
@@ -158,17 +158,17 @@ class DayBetterClient:
158
158
  color_temp: Optional[int] = None,
159
159
  ) -> Dict[str, Any]:
160
160
  """Control a device.
161
-
161
+
162
162
  Args:
163
163
  device_name: Name of the device to control
164
164
  action: Switch action (True/False)
165
165
  brightness: Brightness value (0-255)
166
166
  hs_color: Hue and saturation tuple (hue, saturation)
167
167
  color_temp: Color temperature in mireds
168
-
168
+
169
169
  Returns:
170
170
  Control result dictionary
171
-
171
+
172
172
  Raises:
173
173
  AuthenticationError: If authentication fails
174
174
  APIError: If API request fails
@@ -176,10 +176,8 @@ class DayBetterClient:
176
176
  session = self._get_session()
177
177
  url = f"{self.base_url}hass/control"
178
178
  headers = self._get_headers()
179
-
180
- # Priority: color temperature > color > brightness > switch
179
+
181
180
  if color_temp is not None:
182
- # Convert mireds to Kelvin
183
181
  kelvin = int(1000000 / color_temp)
184
182
  payload = {
185
183
  "deviceName": device_name,
@@ -198,18 +196,17 @@ class DayBetterClient:
198
196
  }
199
197
  elif brightness is not None:
200
198
  payload = {
201
- "deviceName": device_name,
202
- "type": 2,
199
+ "deviceName": device_name,
200
+ "type": 2,
203
201
  "brightness": brightness
204
202
  }
205
203
  else:
206
- # Type 1 control switch is used by default
207
204
  payload = {
208
- "deviceName": device_name,
209
- "type": 1,
205
+ "deviceName": device_name,
206
+ "type": 1,
210
207
  "on": action
211
208
  }
212
-
209
+
213
210
  try:
214
211
  async with session.post(url, headers=headers, json=payload) as resp:
215
212
  if resp.status == 200:
@@ -222,7 +219,7 @@ class DayBetterClient:
222
219
  else:
223
220
  error_text = await resp.text()
224
221
  _LOGGER.error(
225
- "Failed to control device %s: HTTP %d - %s",
222
+ "Failed to control device %s: HTTP %d - %s",
226
223
  device_name, resp.status, error_text
227
224
  )
228
225
  raise APIError(f"API error {resp.status}: {error_text}")
@@ -236,13 +233,13 @@ class DayBetterClient:
236
233
  "Exception while controlling device %s: %s", device_name, e
237
234
  )
238
235
  raise DayBetterError(f"Unexpected error: {e}")
239
-
236
+
240
237
  async def fetch_mqtt_config(self) -> Dict[str, Any]:
241
238
  """Fetch MQTT connection configuration.
242
-
239
+
243
240
  Returns:
244
241
  MQTT configuration dictionary
245
-
242
+
246
243
  Raises:
247
244
  AuthenticationError: If authentication fails
248
245
  APIError: If API request fails
@@ -251,11 +248,11 @@ class DayBetterClient:
251
248
  url = f"{self.base_url}hass/cert"
252
249
  headers = self._get_headers()
253
250
  _LOGGER.debug("Requesting MQTT configuration URL: %s", url)
254
-
251
+
255
252
  try:
256
253
  async with session.post(url, headers=headers) as resp:
257
254
  _LOGGER.debug("MQTT configuration API response status: %d", resp.status)
258
-
255
+
259
256
  if resp.status == 200:
260
257
  data = await resp.json()
261
258
  _LOGGER.debug("MQTT configuration API raw response: %s", data)
@@ -275,10 +272,10 @@ class DayBetterClient:
275
272
  except Exception as e:
276
273
  _LOGGER.exception("Exception while fetching MQTT config: %s", e)
277
274
  raise DayBetterError(f"Unexpected error: {e}")
278
-
275
+
279
276
  async def fetch_device_statuses(self) -> List[Dict[str, Any]]:
280
277
  """Fetch statuses for all devices.
281
-
278
+
282
279
  Returns:
283
280
  List of device status dictionaries. Example item:
284
281
  {
@@ -289,7 +286,7 @@ class DayBetterClient:
289
286
  "humi": int,
290
287
  "bettery": int
291
288
  }
292
-
289
+
293
290
  Raises:
294
291
  AuthenticationError: If authentication fails
295
292
  APIError: If API request fails
@@ -298,12 +295,11 @@ class DayBetterClient:
298
295
  session = self._get_session()
299
296
  url = f"{self.base_url}hass/status"
300
297
  headers = self._get_headers()
301
-
298
+
302
299
  async with session.post(url, headers=headers) as resp:
303
300
  if resp.status == 200:
304
301
  data = await resp.json()
305
302
  self._auth_valid = True
306
- # API expected to return { "data": [...] }
307
303
  return data.get("data", [])
308
304
  elif resp.status == 401:
309
305
  _LOGGER.error("Authentication failed - token may be expired")
@@ -319,21 +315,19 @@ class DayBetterClient:
319
315
  except Exception as e:
320
316
  _LOGGER.exception("Exception while fetching device statuses: %s", e)
321
317
  raise DayBetterError(f"Unexpected error: {e}")
322
-
318
+
323
319
  async def integrate(self, hass_code: str) -> Dict[str, Any]:
324
320
  """Integrate with Home Assistant using hassCode.
325
-
321
+
326
322
  Args:
327
323
  hass_code: Home Assistant integration code from APP
328
-
324
+
329
325
  Returns:
330
326
  Integration result dictionary
331
-
327
+
332
328
  Raises:
333
329
  APIError: If API request fails
334
330
  """
335
- # 根据 hass_code 动态更新 base_url(如果之前没有明确指定)
336
- # 如果 hass_code 以 "db-" 开头,切换到正式环境
337
331
  if hass_code.startswith("db-") and self.base_url != self.PROD_BASE_URL:
338
332
  old_url = self.base_url
339
333
  self.base_url = self.PROD_BASE_URL
@@ -342,19 +336,18 @@ class DayBetterClient:
342
336
  "URL changed from %s to %s", old_url, self.base_url
343
337
  )
344
338
  elif not hass_code.startswith("db-") and self.base_url != self.TEST_BASE_URL:
345
- # 如果 hass_code 不以 "db-" 开头,且当前不是测试环境,切换到测试环境
346
339
  old_url = self.base_url
347
340
  self.base_url = self.TEST_BASE_URL
348
341
  _LOGGER.info(
349
342
  "Switching to test environment based on hass_code. "
350
343
  "URL changed from %s to %s", old_url, self.base_url
351
344
  )
352
-
345
+
353
346
  try:
354
347
  session = self._get_session()
355
348
  url = f"{self.base_url}hass/integrate"
356
349
  payload = {"hassCode": hass_code}
357
-
350
+
358
351
  async with session.post(url, json=payload) as resp:
359
352
  if resp.status == 200:
360
353
  data = await resp.json()
@@ -370,23 +363,23 @@ class DayBetterClient:
370
363
  except Exception as e:
371
364
  _LOGGER.exception("Exception while integrating: %s", e)
372
365
  raise DayBetterError(f"Unexpected error: {e}")
373
-
366
+
374
367
  @property
375
368
  def is_authenticated(self) -> bool:
376
369
  """Check if the API client is authenticated."""
377
370
  return self._auth_valid
378
-
371
+
379
372
  def filter_sensor_devices(
380
373
  self,
381
374
  devices: List[Dict[str, Any]],
382
375
  pids: Dict[str, Any],
383
376
  ) -> List[Dict[str, Any]]:
384
377
  """Filter devices to only include sensors based on PID.
385
-
378
+
386
379
  Args:
387
380
  devices: List of all devices
388
381
  pids: Dictionary containing device type PIDs
389
-
382
+
390
383
  Returns:
391
384
  List of sensor devices only
392
385
  """
@@ -401,18 +394,18 @@ class DayBetterClient:
401
394
  for device in devices
402
395
  if device.get("deviceMoldPid", "") in sensor_pids
403
396
  ]
404
-
397
+
405
398
  def merge_device_status(
406
399
  self,
407
400
  devices: List[Dict[str, Any]],
408
401
  statuses: List[Dict[str, Any]],
409
402
  ) -> List[Dict[str, Any]]:
410
403
  """Merge device info with status info.
411
-
404
+
412
405
  Args:
413
406
  devices: List of device info dictionaries
414
407
  statuses: List of device status dictionaries
415
-
408
+
416
409
  Returns:
417
410
  List of merged device dictionaries
418
411
  """
@@ -429,38 +422,34 @@ class DayBetterClient:
429
422
  merged.append(merged_device)
430
423
 
431
424
  return merged
432
-
425
+
433
426
  async def fetch_sensor_data(self) -> List[Dict[str, Any]]:
434
427
  """Fetch and process sensor data in one call.
435
-
428
+
436
429
  This method fetches device statuses, devices list, and PIDs,
437
430
  filters for sensor devices, and merges the data.
438
-
431
+
439
432
  Returns:
440
433
  List of sensor devices with merged status data
441
-
434
+
442
435
  Raises:
443
436
  AuthenticationError: If authentication fails
444
437
  APIError: If API request fails
445
438
  """
446
- # Fetch current statuses
447
439
  statuses = await self.fetch_device_statuses()
448
-
449
- # Fetch devices and PIDs if not cached
440
+
450
441
  if not self._devices or not self._pids:
451
442
  self._devices = await self.fetch_devices()
452
443
  self._pids = await self.fetch_pids()
453
-
454
- # Filter to sensor devices only
444
+
455
445
  sensor_devices = self.filter_sensor_devices(self._devices, self._pids)
456
-
457
- # Merge with current status
446
+
458
447
  merged = self.merge_device_status(sensor_devices, statuses)
459
448
  _LOGGER.debug("Fetched %d sensor devices", len(merged))
460
449
  return merged
461
-
450
+
462
451
  async def close(self) -> None:
463
- """Close the client session."""
464
- if self._session:
452
+ """Close the client session if this client created it."""
453
+ if self._own_session and self._session is not None:
465
454
  await self._session.close()
466
455
  self._session = None
@@ -3,10 +3,10 @@
3
3
 
4
4
  class DayBetterError(Exception):
5
5
  """Base exception for DayBetter client."""
6
-
6
+
7
7
  def __init__(self, message: str) -> None:
8
8
  """Initialize the exception.
9
-
9
+
10
10
  Args:
11
11
  message: Error message
12
12
  """
@@ -16,10 +16,10 @@ class DayBetterError(Exception):
16
16
 
17
17
  class AuthenticationError(DayBetterError):
18
18
  """Authentication failed."""
19
-
19
+
20
20
  def __init__(self, message: str = "Authentication failed") -> None:
21
21
  """Initialize the authentication error.
22
-
22
+
23
23
  Args:
24
24
  message: Error message
25
25
  """
@@ -28,10 +28,10 @@ class AuthenticationError(DayBetterError):
28
28
 
29
29
  class APIError(DayBetterError):
30
30
  """API request failed."""
31
-
31
+
32
32
  def __init__(self, message: str) -> None:
33
33
  """Initialize the API error.
34
-
34
+
35
35
  Args:
36
36
  message: Error message
37
37
  """
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: daybetter-services-python
3
+ Version: 1.0.9
4
+ Summary: Python client for DayBetter devices and services
5
+ Author-email: THDayBetter <chenp2368@163.com>
6
+ Maintainer-email: THDayBetter <chenp2368@163.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/THDayBetter/daybetter-services-python
9
+ Project-URL: Documentation, https://github.com/THDayBetter/daybetter-services-python#readme
10
+ Project-URL: Repository, https://github.com/THDayBetter/daybetter-services-python.git
11
+ Project-URL: Bug Tracker, https://github.com/THDayBetter/daybetter-services-python/issues
12
+ Keywords: daybetter,iot,home automation,mqtt
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: aiohttp>=3.8.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=6.0; extra == "dev"
29
+ Requires-Dist: pytest-asyncio; extra == "dev"
30
+ Requires-Dist: black; extra == "dev"
31
+ Requires-Dist: isort; extra == "dev"
32
+ Requires-Dist: flake8; extra == "dev"
33
+ Requires-Dist: mypy; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # DayBetter Services Python Client
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
39
+ [![Python Versions](https://img.shields.io/pypi/pyversions/daybetter-services-python.svg)](https://pypi.org/project/daybetter-services-python/)
40
+ [![License](https://img.shields.io/pypi/l/daybetter-services-python.svg)](LICENSE)
41
+
42
+ Asynchronous client library used by the [Home Assistant DayBetter Services integration](https://github.com/home-assistant/core/pull/154677).
43
+ It handles authentication, device discovery, status polling, PID metadata and device control against the DayBetter cloud API.
44
+
45
+ ## Features
46
+
47
+ - Fully async `DayBetterClient` with context-manager support
48
+ - Automatic environment selection (test/prod) based on `hass_code`
49
+ - Convenience helpers for common API endpoints (devices, statuses, MQTT config)
50
+ - Sensor-focused workflow `fetch_sensor_data()` that merges devices, statuses and PID filters
51
+ - Type hints + `py.typed` for first-class editor / mypy support
52
+ - Published on PyPI for Home Assistant and other integrations
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install daybetter-services-python
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ ```python
63
+ import asyncio
64
+ from daybetter_python import DayBetterClient
65
+
66
+ async def main() -> None:
67
+ async with DayBetterClient(token="YOUR_TOKEN", hass_code="db-xxxx") as client:
68
+ # Fetch merged sensor payloads (used by Home Assistant)
69
+ sensors = await client.fetch_sensor_data()
70
+ for item in sensors:
71
+ print(item["deviceName"], item.get("temp"), item.get("humi"))
72
+
73
+ # Control a device (brightness example)
74
+ await client.control_device(
75
+ device_name="device_001",
76
+ action=True,
77
+ brightness=180,
78
+ )
79
+
80
+ if __name__ == "__main__":
81
+ asyncio.run(main())
82
+ ```
83
+
84
+ ## Home Assistant Integration
85
+
86
+ The official integration PR can be followed here: [home-assistant/core#154677](https://github.com/home-assistant/core/pull/154677).
87
+ The integration imports this library and simply calls `client.fetch_sensor_data()` inside a data coordinator, so all business logic lives in this package.
88
+
89
+ See `docs/homeassistant.md` for:
90
+ - Installation instructions (pip, custom component)
91
+ - How to obtain the DayBetter token and `hass_code`
92
+ - Sample `configuration.yaml` snippets
93
+ - Known limitations and future roadmap
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ git clone https://github.com/THDayBetter/daybetter-services-python.git
99
+ cd daybetter-services-python
100
+ python3 -m venv .venv
101
+ source .venv/bin/activate
102
+ pip install -e .[dev]
103
+
104
+ # Quality checks
105
+ flake8 daybetter_python
106
+ mypy daybetter_python
107
+ pytest
108
+ ```
109
+
110
+ Pull requests are welcome! Please open an issue if you encounter problems with the DayBetter cloud API.
111
+
112
+ ## Release Process
113
+
114
+ 1. Update `pyproject.toml` and `daybetter_python/__init__.py` with the new version.
115
+ 2. Document changes in `CHANGELOG.md`.
116
+ 3. Build and upload:
117
+ ```bash
118
+ rm -rf dist build *.egg-info
119
+ python -m build
120
+ twine check dist/*
121
+ twine upload dist/*
122
+ ```
123
+ 4. Create a Git tag (e.g., `git tag v1.0.7 && git push origin v1.0.7`).
124
+
125
+ ## License
126
+
127
+ This project is licensed under the [MIT License](LICENSE).
@@ -1,7 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- setup.py
5
4
  daybetter_python/__init__.py
6
5
  daybetter_python/client.py
7
6
  daybetter_python/exceptions.py
@@ -10,4 +9,5 @@ daybetter_services_python.egg-info/PKG-INFO
10
9
  daybetter_services_python.egg-info/SOURCES.txt
11
10
  daybetter_services_python.egg-info/dependency_links.txt
12
11
  daybetter_services_python.egg-info/requires.txt
13
- daybetter_services_python.egg-info/top_level.txt
12
+ daybetter_services_python.egg-info/top_level.txt
13
+ tests/test_placeholder.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "daybetter-services-python"
7
- version = "1.0.7"
7
+ version = "1.0.9"
8
8
  description = "Python client for DayBetter devices and services"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -33,10 +33,10 @@ dependencies = [
33
33
  ]
34
34
 
35
35
  [project.urls]
36
- Homepage = "https://github.com/THDayBetter/daybetter-python"
37
- Documentation = "https://github.com/THDayBetter/daybetter-python#readme"
38
- Repository = "https://github.com/THDayBetter/daybetter-python.git"
39
- "Bug Tracker" = "https://github.com/THDayBetter/daybetter-python/issues"
36
+ Homepage = "https://github.com/THDayBetter/daybetter-services-python"
37
+ Documentation = "https://github.com/THDayBetter/daybetter-services-python#readme"
38
+ Repository = "https://github.com/THDayBetter/daybetter-services-python.git"
39
+ "Bug Tracker" = "https://github.com/THDayBetter/daybetter-services-python/issues"
40
40
 
41
41
  [project.optional-dependencies]
42
42
  dev = [
@@ -57,5 +57,9 @@ target-version = ['py38']
57
57
  profile = "black"
58
58
  line_length = 88
59
59
 
60
+ [tool.flake8]
61
+ max-line-length = 100
62
+ extend-ignore = ["W503"]
63
+
60
64
  [tool.setuptools.package-data]
61
65
  daybetter_python = ["py.typed"]
@@ -0,0 +1,7 @@
1
+ from daybetter_python import DayBetterClient
2
+
3
+
4
+ def test_client_attributes() -> None:
5
+ client = DayBetterClient(token="abc123")
6
+ assert client.token == "abc123"
7
+
@@ -1,99 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: daybetter-services-python
3
- Version: 1.0.7
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
- Classifier: Typing :: Typed
25
- Requires-Python: >=3.8
26
- Description-Content-Type: text/markdown
27
- License-File: LICENSE
28
- Requires-Dist: aiohttp>=3.8.0
29
- Provides-Extra: dev
30
- Requires-Dist: pytest>=6.0; extra == "dev"
31
- Requires-Dist: pytest-asyncio; extra == "dev"
32
- Requires-Dist: black; extra == "dev"
33
- Requires-Dist: isort; extra == "dev"
34
- Requires-Dist: flake8; extra == "dev"
35
- Requires-Dist: mypy; extra == "dev"
36
- Dynamic: author
37
- Dynamic: home-page
38
- Dynamic: license-file
39
- Dynamic: requires-python
40
-
41
- # DayBetter Python Client
42
-
43
- A Python client library for interacting with DayBetter devices and services.
44
-
45
- ## Features
46
-
47
- - Device management and control
48
- - MQTT configuration retrieval
49
- - Authentication handling
50
- - Async/await support
51
-
52
- ## Installation
53
-
54
- ```bash
55
- pip install daybetter-services-python
56
- ```
57
-
58
- ## Usage
59
-
60
- ```python
61
- import asyncio
62
- from daybetter_python import DayBetterClient
63
-
64
- async def main():
65
- async with DayBetterClient(token="your_token") as client:
66
- # Fetch devices
67
- devices = await client.fetch_devices()
68
- print(f"Found {len(devices)} devices")
69
-
70
- # Control a device
71
- result = await client.control_device(
72
- device_name="device_001",
73
- action=True,
74
- brightness=80
75
- )
76
- print(f"Control result: {result}")
77
-
78
- if __name__ == "__main__":
79
- asyncio.run(main())
80
- ```
81
-
82
- ## API Reference
83
-
84
- ### DayBetterClient
85
-
86
- #### Methods
87
-
88
- - `fetch_devices()`: Get list of devices
89
- - `fetch_pids()`: Get device type PIDs
90
- - `control_device(device_name, action, brightness, hs_color, color_temp)`: Control a device
91
- - `fetch_mqtt_config()`: Get MQTT configuration
92
-
93
- ## License
94
-
95
- MIT License
96
-
97
- ## Contributing
98
-
99
- Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1,59 +0,0 @@
1
- # DayBetter Python Client
2
-
3
- A Python client library for interacting with DayBetter devices and services.
4
-
5
- ## Features
6
-
7
- - Device management and control
8
- - MQTT configuration retrieval
9
- - Authentication handling
10
- - Async/await support
11
-
12
- ## Installation
13
-
14
- ```bash
15
- pip install daybetter-services-python
16
- ```
17
-
18
- ## Usage
19
-
20
- ```python
21
- import asyncio
22
- from daybetter_python import DayBetterClient
23
-
24
- async def main():
25
- async with DayBetterClient(token="your_token") as client:
26
- # Fetch devices
27
- devices = await client.fetch_devices()
28
- print(f"Found {len(devices)} devices")
29
-
30
- # Control a device
31
- result = await client.control_device(
32
- device_name="device_001",
33
- action=True,
34
- brightness=80
35
- )
36
- print(f"Control result: {result}")
37
-
38
- if __name__ == "__main__":
39
- asyncio.run(main())
40
- ```
41
-
42
- ## API Reference
43
-
44
- ### DayBetterClient
45
-
46
- #### Methods
47
-
48
- - `fetch_devices()`: Get list of devices
49
- - `fetch_pids()`: Get device type PIDs
50
- - `control_device(device_name, action, brightness, hs_color, color_temp)`: Control a device
51
- - `fetch_mqtt_config()`: Get MQTT configuration
52
-
53
- ## License
54
-
55
- MIT License
56
-
57
- ## Contributing
58
-
59
- Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1,99 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: daybetter-services-python
3
- Version: 1.0.7
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
- Classifier: Typing :: Typed
25
- Requires-Python: >=3.8
26
- Description-Content-Type: text/markdown
27
- License-File: LICENSE
28
- Requires-Dist: aiohttp>=3.8.0
29
- Provides-Extra: dev
30
- Requires-Dist: pytest>=6.0; extra == "dev"
31
- Requires-Dist: pytest-asyncio; extra == "dev"
32
- Requires-Dist: black; extra == "dev"
33
- Requires-Dist: isort; extra == "dev"
34
- Requires-Dist: flake8; extra == "dev"
35
- Requires-Dist: mypy; extra == "dev"
36
- Dynamic: author
37
- Dynamic: home-page
38
- Dynamic: license-file
39
- Dynamic: requires-python
40
-
41
- # DayBetter Python Client
42
-
43
- A Python client library for interacting with DayBetter devices and services.
44
-
45
- ## Features
46
-
47
- - Device management and control
48
- - MQTT configuration retrieval
49
- - Authentication handling
50
- - Async/await support
51
-
52
- ## Installation
53
-
54
- ```bash
55
- pip install daybetter-services-python
56
- ```
57
-
58
- ## Usage
59
-
60
- ```python
61
- import asyncio
62
- from daybetter_python import DayBetterClient
63
-
64
- async def main():
65
- async with DayBetterClient(token="your_token") as client:
66
- # Fetch devices
67
- devices = await client.fetch_devices()
68
- print(f"Found {len(devices)} devices")
69
-
70
- # Control a device
71
- result = await client.control_device(
72
- device_name="device_001",
73
- action=True,
74
- brightness=80
75
- )
76
- print(f"Control result: {result}")
77
-
78
- if __name__ == "__main__":
79
- asyncio.run(main())
80
- ```
81
-
82
- ## API Reference
83
-
84
- ### DayBetterClient
85
-
86
- #### Methods
87
-
88
- - `fetch_devices()`: Get list of devices
89
- - `fetch_pids()`: Get device type PIDs
90
- - `control_device(device_name, action, brightness, hs_color, color_temp)`: Control a device
91
- - `fetch_mqtt_config()`: Get MQTT configuration
92
-
93
- ## License
94
-
95
- MIT License
96
-
97
- ## Contributing
98
-
99
- Contributions are welcome! Please feel free to submit a Pull Request.
@@ -1,38 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- with open("README.md", "r", encoding="utf-8") as fh:
4
- long_description = fh.read()
5
-
6
- setup(
7
- name="daybetter-services-python",
8
- version="1.0.7",
9
- author="THDayBetter",
10
- author_email="chenp2368@163.com",
11
- description="Python client for DayBetter devices and services",
12
- long_description=long_description,
13
- long_description_content_type="text/markdown",
14
- url="https://github.com/THDayBetter/daybetter-python",
15
- packages=find_packages(),
16
- package_data={
17
- "daybetter_python": ["py.typed"],
18
- },
19
- classifiers=[
20
- "Development Status :: 4 - Beta",
21
- "Intended Audience :: Developers",
22
- "Operating System :: OS Independent",
23
- "Programming Language :: Python :: 3",
24
- "Programming Language :: Python :: 3.8",
25
- "Programming Language :: Python :: 3.9",
26
- "Programming Language :: Python :: 3.10",
27
- "Programming Language :: Python :: 3.11",
28
- "Programming Language :: Python :: 3.12",
29
- "Typing :: Typed",
30
- ],
31
- python_requires=">=3.8",
32
- install_requires=["aiohttp>=3.8.0"],
33
- keywords="daybetter, iot, home automation, mqtt",
34
- project_urls={
35
- "Bug Reports": "https://github.com/THDayBetter/daybetter-python/issues",
36
- "Source": "https://github.com/THDayBetter/daybetter-python",
37
- },
38
- )