daybetter-ble-client 1.0.0rc1__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
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Siddhant Kameswar
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,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: daybetter-ble-client
3
+ Version: 1.0.0rc1
4
+ Summary: Async Bluetooth Low Energy control for Daybetter P031 and P065 LED strips
5
+ Author-email: Siddhant Kameswar <61r7jw39@kameswar.com>, Jack Stockley <jack@jstockley.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/jnstockley/daybetter-led-strip
8
+ Project-URL: Repository, https://github.com/jnstockley/daybetter-led-strip.git
9
+ Project-URL: Issues, https://github.com/jnstockley/daybetter-led-strip/issues
10
+ Project-URL: Upstream, https://github.com/grimsteel/daybetter-led-strip
11
+ Requires-Python: <3.16,>=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: bleak<4,>=3.1.0
15
+ Requires-Dist: bleak-retry-connector<5,>=4.7.1
16
+ Dynamic: license-file
17
+
18
+ # daybetter-ble-client
19
+
20
+ ![GitHub License](https://img.shields.io/github/license/jnstockley/daybetter-led-strip)
21
+ ![CI](https://img.shields.io/github/actions/workflow/status/jnstockley/daybetter-led-strip/ci-cd.yml)
22
+
23
+ Async Python control for Daybetter P031 and P065 RGB LED strips over Bluetooth
24
+ Low Energy, using [Bleak](https://bleak.readthedocs.io/en/latest/). Requires
25
+ Python 3.11–3.15. Models are detected from advertised services, with device names
26
+ as a fallback.
27
+
28
+ This project is forked from [grimsteel/daybetter-led-strip](https://github.com/grimsteel/daybetter-led-strip),
29
+ originally created by **Siddhant Kameswar** ([grimsteel](https://github.com/grimsteel)).
30
+ Credit for the original library and documented BLE protocol belongs to the
31
+ original author. The original MIT license and copyright notice are preserved
32
+ in [LICENSE](LICENSE).
33
+
34
+ ## Installation and migration
35
+
36
+ The new distribution name is **`daybetter-ble-client`**, and the import package
37
+ is **`daybetter_ble_client`**. This intentionally changes the old
38
+ `daybetter_led_strip` import path; class and method names remain the same:
39
+
40
+ ```python
41
+ from daybetter_ble_client import DaybetterLedStrip
42
+ from daybetter_ble_client.const import SUPPORTED_DISCOVERY_SERVICES
43
+ ```
44
+
45
+ Until the first release of this fork is published, install this checkout with
46
+ `uv sync --locked`. Once published, install the distribution with
47
+ `uv pip install daybetter-ble-client` (or `pip install daybetter-ble-client`).
48
+ The upstream `daybetter-led-strip` distribution does not include this fork's
49
+ changes.
50
+
51
+ ## Local development
52
+
53
+ Install [uv](https://docs.astral.sh/uv/) and run:
54
+
55
+ ```sh
56
+ uv sync --locked --all-groups
57
+ uv run --locked pytest
58
+ uv run --locked pytest tests/examples
59
+ uv run --locked ruff check
60
+ uv run --locked ruff format --check
61
+ uv build --no-sources
62
+ ```
63
+
64
+ `.python-version` selects Python 3.15 locally. Commit `uv.lock` when changing
65
+ dependencies; `pyproject.toml` is the only dependency manifest.
66
+ Runtime dependencies have explicit major-version bounds: Bleak stays on 3.x
67
+ and bleak-retry-connector stays on 4.x. A major upgrade requires changing those
68
+ bounds deliberately; ordinary dependency updates stay within them.
69
+
70
+ `./scripts/test.sh` runs the library/release-tool tests with branch coverage and
71
+ then the example tests. Coverage must be at least 95%; reports are written to
72
+ `logs/`. Both CI tests and linting run on Python 3.11, 3.12, 3.13, 3.14,
73
+ and 3.15. `./scripts/lint.sh` additionally checks shell and YAML files, requiring
74
+ `shellcheck`, `shfmt`, and `yamllint` on PATH. See [testing](docs/TESTING.md) and
75
+ [releases](docs/RELEASING.md) for the full workflow.
76
+
77
+ The repository uses `src/daybetter_ble_client/` for the library, `tests/` for
78
+ offline tests, `tests/examples/` for example tests, `examples/` for runnable
79
+ programs, and `scripts/` for validation/release helpers. Tests block real BLE
80
+ access; they do not need hardware or a PyPI account.
81
+
82
+ For actual device use, enable Bluetooth and grant Bluetooth access to the app
83
+ running Python on macOS. Close the phone app before connecting; a strip accepts
84
+ only one BLE connection at a time.
85
+
86
+ ## Usage
87
+
88
+ The `DaybetterLedStrip` constructor only requires the device address (a UUID on
89
+ macOS, a MAC address on other platforms). Pass the scanned device and its
90
+ advertisement data to `.update_device` for automatic model selection. The
91
+ detected model is available as `.model`. If identifying data is unavailable,
92
+ the compatibility default is P031. If the device disconnects, it will not
93
+ automatically reconnect; call `.update_device` again with a freshly scanned
94
+ device, or `.connect` to retry the stored device. P065 authenticates on every
95
+ connection, and initialization failures raise an exception after cleanup.
96
+
97
+ ```python
98
+ import asyncio
99
+ from bleak import BleakScanner
100
+ from daybetter_ble_client import DaybetterLedStrip
101
+ from daybetter_ble_client.const import SUPPORTED_DISCOVERY_SERVICES
102
+
103
+ # This snippet runs inside an async function. Pick one target explicitly.
104
+ devices = await BleakScanner.discover(timeout=10, return_adv=True)
105
+ matches = [
106
+ (device, data)
107
+ for device, data in devices.values()
108
+ if any(service in data.service_uuids for service in SUPPORTED_DISCOVERY_SERVICES)
109
+ ]
110
+ for device, data in matches:
111
+ print(device.name, device.address)
112
+ if not matches:
113
+ raise RuntimeError("No supported strip found")
114
+ device, data = matches[0] # choose the intended strip if several are found
115
+ led_strip = DaybetterLedStrip(device.address)
116
+ try:
117
+ await led_strip.update_device(device, data)
118
+ if not led_strip.connected:
119
+ raise RuntimeError("Could not connect to strip")
120
+ await led_strip.set_power(True)
121
+ await led_strip.set_color((255, 0, 0))
122
+ await asyncio.sleep(1)
123
+ await led_strip.set_power(False)
124
+ finally:
125
+ await led_strip.disconnect()
126
+ ```
127
+
128
+ A complete example program is provided in `examples/test.py`.
129
+ Run it with `uv run --locked python examples/test.py`. It cycles colors, changes brightness,
130
+ and turns the strip off. It requires exactly one supported strip to be
131
+ discoverable to avoid changing an unintended device.
132
+
133
+ This package was created [for use in Home Assistant](https://github.com/grimsteel/homeassistant-daybetter-led-strip), which may explain some of the API choices.
134
+
135
+ ## Protocol
136
+
137
+ Communication with the LED strip is done over BLE.
138
+
139
+ The light strip exposes two services: the generic access profile w/ the device name, and a custom "LED control service" (`0xe031`).
140
+
141
+ During discovery, the device also advertises itself with a third service (`0xc031`) for identification.
142
+
143
+ P065 uses the equivalent UUIDs `0xc065` (discovery), `0xe065` (control
144
+ service), `0xa065` (write), and `0xf065` (status notifications).
145
+
146
+ ### P065 connection initialization
147
+
148
+ After subscribing to notifications, P065 requires a two-step handshake before
149
+ control commands take effect. Send command `0x00` with `CCHIP`; the response
150
+ contains `CCHIP` and two device-specific bytes. Echo that complete seven-byte
151
+ payload with command `0x01`. A six-byte reply ending in `0x01` indicates success.
152
+ The library validates framing and CRC and waits up to five seconds for each
153
+ response. It disconnects if initialization fails. Do not hard-code the two
154
+ device-specific bytes from another strip.
155
+
156
+ P065 color commands append `0xff` after the RGB bytes, and effect commands
157
+ append `0xff` after the effect ID. P031 payloads retain their existing format.
158
+ An acknowledgement only confirms receipt; it does not prove a visible change.
159
+
160
+ Validation on a physical P065 covered handshake/reconnect, RGB color, brightness,
161
+ power, and `FADE_RED_GREEN`, with settings checked through status reads. Other
162
+ effect IDs have not been individually tested. P031's original command format is
163
+ covered by regression tests; a fresh P031 hardware check could not be completed
164
+ during this change because the device was not consistently discoverable.
165
+
166
+ The LED control service has a read characteristic (`0xf031`) and a write characteristic (`0xa031`). As far as I can tell, the read characteristic cannot actually be read directly. Rather, if notifications are enabled on this service, the device will push updates about its state at various times.
167
+
168
+ ### Write Data Format
169
+
170
+ Commands written to the write characteristic have the following format.
171
+
172
+ 1. The byte `0xA0` (referred to in the app source code as "APP_TO_DEVICE")
173
+ 2. The command ID. (1 byte)
174
+ 3. The length of the entire message, not including the checksum. (1 byte) `= len(payload) + 3`
175
+ 4. The payload for this command
176
+ 5. The little-endian CRC16 MODBUS checksum of the rest of the message.
177
+
178
+ ### Commands
179
+
180
+ | Command | ID | Payload Description |
181
+ |-----------------|--------|-----------------------------------------------------------------------------------------------------|
182
+ | **Get status** | `0x10` | `0x00`<br>Results in status payload below being sent on notify |
183
+ | **Set power** | `0x11` | `0x01` (on) or `0x00` (off) |
184
+ | **Show effect** | `0x12` | One of the effect bytes listed below |
185
+ | **Set brightness** | `0x13` | A byte representing the brightness, from 0 (`0x00`) to 100 (`0x64`). Brightness 0 still has the LEDs slightly on |
186
+ | **Set color** | `0x15` | 3 bytes, in RGB order |
187
+
188
+ Example command (set to red): `A0 15 06 FF 00 00 25 C0`
189
+
190
+ ### Notify/read data format
191
+
192
+ The payloads received over notify have a similar structure as the write commands:
193
+
194
+ 1. The byte `0xA1` ("DEVICE_TO_APP")
195
+ 2. Command ID
196
+ 3. Length of entire message excluding checksum
197
+ 4. Payload
198
+ 5. LE Checksum
199
+
200
+ ### Received "commands"
201
+
202
+ When any of the above write commands are written to the characteristic successfully, the device responds with a message with the same command ID and `0x01` for the payload.
203
+
204
+ For instance, the above color command would result in `A1 15 04 01 F1 1C`
205
+
206
+ When the strip is turned on/off from the IR remote, a message with command ID `0x10` and the payload `00 01 01 64 32 32 ff 31 2d f0 00 00 00 00` (off) or `01 01 01 64 32 32 ff 31 2d f0 00 00 00 00`. The first byte indicates the on/off status, but I don't know what the rest means. It seems unrelated to the color of the light.
207
+
208
+ Running the "get status" command also results in this being sent.
209
+
210
+ ### Effects
211
+
212
+ > Note: the brightness can be changed during the following effects. Lower brightnesses result in blinking happening faster, likely because there is a smaller range of brightnesses to interpolate between.
213
+
214
+ RGB: just red/green/blue
215
+
216
+ All preset colors: red/green/blue/yellow/teal/purple/white
217
+
218
+ * `0x02..0x08`: show preset colors
219
+ * `0x09`: switch RGB abruptly, no transition
220
+ * `0x0A`: switch all preset colors
221
+ * `0x0B`: fade between colors quickly
222
+ * `0x0C`: fade between colors slowly
223
+ * `0x0D`..`0x13`: blink individual preset colors
224
+ * `0x14`: fade red/green
225
+ * `0x15`: fade red/blue
226
+ * `0x16`: fade green/blue
227
+ * `0x17`: flash all preset colors
228
+ * `0x18`..`0x1E`: flash individual preset colors
229
+ * `0x1F`: strobe RGB
230
+ * `0x20`: strobe all colors
@@ -0,0 +1,213 @@
1
+ # daybetter-ble-client
2
+
3
+ ![GitHub License](https://img.shields.io/github/license/jnstockley/daybetter-led-strip)
4
+ ![CI](https://img.shields.io/github/actions/workflow/status/jnstockley/daybetter-led-strip/ci-cd.yml)
5
+
6
+ Async Python control for Daybetter P031 and P065 RGB LED strips over Bluetooth
7
+ Low Energy, using [Bleak](https://bleak.readthedocs.io/en/latest/). Requires
8
+ Python 3.11–3.15. Models are detected from advertised services, with device names
9
+ as a fallback.
10
+
11
+ This project is forked from [grimsteel/daybetter-led-strip](https://github.com/grimsteel/daybetter-led-strip),
12
+ originally created by **Siddhant Kameswar** ([grimsteel](https://github.com/grimsteel)).
13
+ Credit for the original library and documented BLE protocol belongs to the
14
+ original author. The original MIT license and copyright notice are preserved
15
+ in [LICENSE](LICENSE).
16
+
17
+ ## Installation and migration
18
+
19
+ The new distribution name is **`daybetter-ble-client`**, and the import package
20
+ is **`daybetter_ble_client`**. This intentionally changes the old
21
+ `daybetter_led_strip` import path; class and method names remain the same:
22
+
23
+ ```python
24
+ from daybetter_ble_client import DaybetterLedStrip
25
+ from daybetter_ble_client.const import SUPPORTED_DISCOVERY_SERVICES
26
+ ```
27
+
28
+ Until the first release of this fork is published, install this checkout with
29
+ `uv sync --locked`. Once published, install the distribution with
30
+ `uv pip install daybetter-ble-client` (or `pip install daybetter-ble-client`).
31
+ The upstream `daybetter-led-strip` distribution does not include this fork's
32
+ changes.
33
+
34
+ ## Local development
35
+
36
+ Install [uv](https://docs.astral.sh/uv/) and run:
37
+
38
+ ```sh
39
+ uv sync --locked --all-groups
40
+ uv run --locked pytest
41
+ uv run --locked pytest tests/examples
42
+ uv run --locked ruff check
43
+ uv run --locked ruff format --check
44
+ uv build --no-sources
45
+ ```
46
+
47
+ `.python-version` selects Python 3.15 locally. Commit `uv.lock` when changing
48
+ dependencies; `pyproject.toml` is the only dependency manifest.
49
+ Runtime dependencies have explicit major-version bounds: Bleak stays on 3.x
50
+ and bleak-retry-connector stays on 4.x. A major upgrade requires changing those
51
+ bounds deliberately; ordinary dependency updates stay within them.
52
+
53
+ `./scripts/test.sh` runs the library/release-tool tests with branch coverage and
54
+ then the example tests. Coverage must be at least 95%; reports are written to
55
+ `logs/`. Both CI tests and linting run on Python 3.11, 3.12, 3.13, 3.14,
56
+ and 3.15. `./scripts/lint.sh` additionally checks shell and YAML files, requiring
57
+ `shellcheck`, `shfmt`, and `yamllint` on PATH. See [testing](docs/TESTING.md) and
58
+ [releases](docs/RELEASING.md) for the full workflow.
59
+
60
+ The repository uses `src/daybetter_ble_client/` for the library, `tests/` for
61
+ offline tests, `tests/examples/` for example tests, `examples/` for runnable
62
+ programs, and `scripts/` for validation/release helpers. Tests block real BLE
63
+ access; they do not need hardware or a PyPI account.
64
+
65
+ For actual device use, enable Bluetooth and grant Bluetooth access to the app
66
+ running Python on macOS. Close the phone app before connecting; a strip accepts
67
+ only one BLE connection at a time.
68
+
69
+ ## Usage
70
+
71
+ The `DaybetterLedStrip` constructor only requires the device address (a UUID on
72
+ macOS, a MAC address on other platforms). Pass the scanned device and its
73
+ advertisement data to `.update_device` for automatic model selection. The
74
+ detected model is available as `.model`. If identifying data is unavailable,
75
+ the compatibility default is P031. If the device disconnects, it will not
76
+ automatically reconnect; call `.update_device` again with a freshly scanned
77
+ device, or `.connect` to retry the stored device. P065 authenticates on every
78
+ connection, and initialization failures raise an exception after cleanup.
79
+
80
+ ```python
81
+ import asyncio
82
+ from bleak import BleakScanner
83
+ from daybetter_ble_client import DaybetterLedStrip
84
+ from daybetter_ble_client.const import SUPPORTED_DISCOVERY_SERVICES
85
+
86
+ # This snippet runs inside an async function. Pick one target explicitly.
87
+ devices = await BleakScanner.discover(timeout=10, return_adv=True)
88
+ matches = [
89
+ (device, data)
90
+ for device, data in devices.values()
91
+ if any(service in data.service_uuids for service in SUPPORTED_DISCOVERY_SERVICES)
92
+ ]
93
+ for device, data in matches:
94
+ print(device.name, device.address)
95
+ if not matches:
96
+ raise RuntimeError("No supported strip found")
97
+ device, data = matches[0] # choose the intended strip if several are found
98
+ led_strip = DaybetterLedStrip(device.address)
99
+ try:
100
+ await led_strip.update_device(device, data)
101
+ if not led_strip.connected:
102
+ raise RuntimeError("Could not connect to strip")
103
+ await led_strip.set_power(True)
104
+ await led_strip.set_color((255, 0, 0))
105
+ await asyncio.sleep(1)
106
+ await led_strip.set_power(False)
107
+ finally:
108
+ await led_strip.disconnect()
109
+ ```
110
+
111
+ A complete example program is provided in `examples/test.py`.
112
+ Run it with `uv run --locked python examples/test.py`. It cycles colors, changes brightness,
113
+ and turns the strip off. It requires exactly one supported strip to be
114
+ discoverable to avoid changing an unintended device.
115
+
116
+ This package was created [for use in Home Assistant](https://github.com/grimsteel/homeassistant-daybetter-led-strip), which may explain some of the API choices.
117
+
118
+ ## Protocol
119
+
120
+ Communication with the LED strip is done over BLE.
121
+
122
+ The light strip exposes two services: the generic access profile w/ the device name, and a custom "LED control service" (`0xe031`).
123
+
124
+ During discovery, the device also advertises itself with a third service (`0xc031`) for identification.
125
+
126
+ P065 uses the equivalent UUIDs `0xc065` (discovery), `0xe065` (control
127
+ service), `0xa065` (write), and `0xf065` (status notifications).
128
+
129
+ ### P065 connection initialization
130
+
131
+ After subscribing to notifications, P065 requires a two-step handshake before
132
+ control commands take effect. Send command `0x00` with `CCHIP`; the response
133
+ contains `CCHIP` and two device-specific bytes. Echo that complete seven-byte
134
+ payload with command `0x01`. A six-byte reply ending in `0x01` indicates success.
135
+ The library validates framing and CRC and waits up to five seconds for each
136
+ response. It disconnects if initialization fails. Do not hard-code the two
137
+ device-specific bytes from another strip.
138
+
139
+ P065 color commands append `0xff` after the RGB bytes, and effect commands
140
+ append `0xff` after the effect ID. P031 payloads retain their existing format.
141
+ An acknowledgement only confirms receipt; it does not prove a visible change.
142
+
143
+ Validation on a physical P065 covered handshake/reconnect, RGB color, brightness,
144
+ power, and `FADE_RED_GREEN`, with settings checked through status reads. Other
145
+ effect IDs have not been individually tested. P031's original command format is
146
+ covered by regression tests; a fresh P031 hardware check could not be completed
147
+ during this change because the device was not consistently discoverable.
148
+
149
+ The LED control service has a read characteristic (`0xf031`) and a write characteristic (`0xa031`). As far as I can tell, the read characteristic cannot actually be read directly. Rather, if notifications are enabled on this service, the device will push updates about its state at various times.
150
+
151
+ ### Write Data Format
152
+
153
+ Commands written to the write characteristic have the following format.
154
+
155
+ 1. The byte `0xA0` (referred to in the app source code as "APP_TO_DEVICE")
156
+ 2. The command ID. (1 byte)
157
+ 3. The length of the entire message, not including the checksum. (1 byte) `= len(payload) + 3`
158
+ 4. The payload for this command
159
+ 5. The little-endian CRC16 MODBUS checksum of the rest of the message.
160
+
161
+ ### Commands
162
+
163
+ | Command | ID | Payload Description |
164
+ |-----------------|--------|-----------------------------------------------------------------------------------------------------|
165
+ | **Get status** | `0x10` | `0x00`<br>Results in status payload below being sent on notify |
166
+ | **Set power** | `0x11` | `0x01` (on) or `0x00` (off) |
167
+ | **Show effect** | `0x12` | One of the effect bytes listed below |
168
+ | **Set brightness** | `0x13` | A byte representing the brightness, from 0 (`0x00`) to 100 (`0x64`). Brightness 0 still has the LEDs slightly on |
169
+ | **Set color** | `0x15` | 3 bytes, in RGB order |
170
+
171
+ Example command (set to red): `A0 15 06 FF 00 00 25 C0`
172
+
173
+ ### Notify/read data format
174
+
175
+ The payloads received over notify have a similar structure as the write commands:
176
+
177
+ 1. The byte `0xA1` ("DEVICE_TO_APP")
178
+ 2. Command ID
179
+ 3. Length of entire message excluding checksum
180
+ 4. Payload
181
+ 5. LE Checksum
182
+
183
+ ### Received "commands"
184
+
185
+ When any of the above write commands are written to the characteristic successfully, the device responds with a message with the same command ID and `0x01` for the payload.
186
+
187
+ For instance, the above color command would result in `A1 15 04 01 F1 1C`
188
+
189
+ When the strip is turned on/off from the IR remote, a message with command ID `0x10` and the payload `00 01 01 64 32 32 ff 31 2d f0 00 00 00 00` (off) or `01 01 01 64 32 32 ff 31 2d f0 00 00 00 00`. The first byte indicates the on/off status, but I don't know what the rest means. It seems unrelated to the color of the light.
190
+
191
+ Running the "get status" command also results in this being sent.
192
+
193
+ ### Effects
194
+
195
+ > Note: the brightness can be changed during the following effects. Lower brightnesses result in blinking happening faster, likely because there is a smaller range of brightnesses to interpolate between.
196
+
197
+ RGB: just red/green/blue
198
+
199
+ All preset colors: red/green/blue/yellow/teal/purple/white
200
+
201
+ * `0x02..0x08`: show preset colors
202
+ * `0x09`: switch RGB abruptly, no transition
203
+ * `0x0A`: switch all preset colors
204
+ * `0x0B`: fade between colors quickly
205
+ * `0x0C`: fade between colors slowly
206
+ * `0x0D`..`0x13`: blink individual preset colors
207
+ * `0x14`: fade red/green
208
+ * `0x15`: fade red/blue
209
+ * `0x16`: fade green/blue
210
+ * `0x17`: flash all preset colors
211
+ * `0x18`..`0x1E`: flash individual preset colors
212
+ * `0x1F`: strobe RGB
213
+ * `0x20`: strobe all colors
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "daybetter-ble-client"
3
+ version = "1.0.0rc1"
4
+ description = "Async Bluetooth Low Energy control for Daybetter P031 and P065 LED strips"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11,<3.16"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [
10
+ { name = "Siddhant Kameswar", email = "61r7jw39@kameswar.com" },
11
+ { name = "Jack Stockley", email = "jack@jstockley.com" },
12
+ ]
13
+ dependencies = [
14
+ "bleak>=3.1.0,<4",
15
+ "bleak-retry-connector>=4.7.1,<5",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/jnstockley/daybetter-led-strip"
20
+ Repository = "https://github.com/jnstockley/daybetter-led-strip.git"
21
+ Issues = "https://github.com/jnstockley/daybetter-led-strip/issues"
22
+ Upstream = "https://github.com/grimsteel/daybetter-led-strip"
23
+
24
+ [build-system]
25
+ requires = ["setuptools>=77"]
26
+ build-backend = "setuptools.build_meta"
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.pytest.ini_options]
32
+ testpaths = ["tests"]
33
+ norecursedirs = ["examples", ".*"]
34
+ addopts = "-ra"
35
+
36
+ [tool.coverage.run]
37
+ branch = true
38
+ source = ["daybetter_ble_client"]
39
+
40
+ [tool.coverage.report]
41
+ show_missing = true
42
+ fail_under = 95
43
+
44
+ [dependency-groups]
45
+ dev = [
46
+ "packaging>=25.0",
47
+ "pytest>=9.1.1",
48
+ "pytest-cov>=7.1.0",
49
+ "ruff>=0.17.2",
50
+ "twine>=6.0",
51
+ ]
52
+
53
+ [tool.ruff]
54
+ target-version = "py311"
55
+
56
+ [tool.ruff.lint]
57
+ select = ["E4", "E7", "E9", "F", "I"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .led_strip import DaybetterLedStrip
2
+
3
+ __all__ = ["DaybetterLedStrip"]
@@ -0,0 +1,101 @@
1
+ from dataclasses import dataclass
2
+ from enum import IntEnum, unique
3
+
4
+ from bleak.uuids import normalize_uuid_16
5
+
6
+ # includes Device Name characteristic
7
+ SERVICE_GENERIC_ACCESS_PROFILE = normalize_uuid_16(0x1800)
8
+ # read
9
+ CHAR_DEVICE_NAME = normalize_uuid_16(0x2A00)
10
+
11
+ SERVICE_LED_CONTROL = normalize_uuid_16(0xE031)
12
+ # write
13
+ CHAR_LED_CONTROL = normalize_uuid_16(0xA031)
14
+ # "read", notify
15
+ CHAR_LED_STATUS = normalize_uuid_16(0xF031)
16
+
17
+ # Advertised service
18
+ SERVICE_DISCOVERY = normalize_uuid_16(0xC031)
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class DeviceProfile:
23
+ model: str
24
+ discovery_service: str
25
+ control_service: str
26
+ write_characteristic: str
27
+ status_characteristic: str
28
+ requires_handshake: bool = False
29
+ variant: bytes = b""
30
+
31
+
32
+ P031 = DeviceProfile(
33
+ "P031", SERVICE_DISCOVERY, SERVICE_LED_CONTROL, CHAR_LED_CONTROL, CHAR_LED_STATUS
34
+ )
35
+ P065 = DeviceProfile(
36
+ "P065",
37
+ normalize_uuid_16(0xC065),
38
+ normalize_uuid_16(0xE065),
39
+ normalize_uuid_16(0xA065),
40
+ normalize_uuid_16(0xF065),
41
+ True,
42
+ b"\xff",
43
+ )
44
+ DEVICE_PROFILES = (P031, P065)
45
+ SUPPORTED_DISCOVERY_SERVICES = tuple(p.discovery_service for p in DEVICE_PROFILES)
46
+
47
+ # GATT command ID
48
+ COMMAND_BRIGHTNESS = 0x13
49
+ COMMAND_EFFECT = 0x12
50
+ COMMAND_POWER = 0x11
51
+ COMMAND_COLOR = 0x15
52
+
53
+
54
+ @unique
55
+ class Effect(IntEnum):
56
+ # Show preset colors
57
+ SHOW_RED = 0x02
58
+ SHOW_GREEN = 0x03
59
+ SHOW_BLUE = 0x04
60
+ SHOW_YELLOW = 0x05
61
+ SHOW_TEAL = 0x06
62
+ SHOW_PURPLE = 0x07
63
+ SHOW_WHITE = 0x08
64
+
65
+ # Switch colors abruptly
66
+ SWITCH_RGB = 0x09
67
+ SWITCH_ALL_PRESET_COLORS = 0x0A
68
+
69
+ # Fade between colors
70
+ FADE_COLORS_QUICK = 0x0B
71
+ FADE_COLORS_SLOW = 0x0C
72
+
73
+ # Blink individual preset colors
74
+ BLINK_RED = 0x0D
75
+ BLINK_GREEN = 0x0E
76
+ BLINK_BLUE = 0x0F
77
+ BLINK_YELLOW = 0x10
78
+ BLINK_TEAL = 0x11
79
+ BLINK_PURPLE = 0x12
80
+ BLINK_WHITE = 0x13
81
+
82
+ # Fade between two colors
83
+ FADE_RED_GREEN = 0x14
84
+ FADE_RED_BLUE = 0x15
85
+ FADE_GREEN_BLUE = 0x16
86
+
87
+ # Flash all preset colors
88
+ FLASH_ALL_PRESET_COLORS = 0x17
89
+
90
+ # Flash individual preset colors
91
+ FLASH_RED = 0x18
92
+ FLASH_GREEN = 0x19
93
+ FLASH_BLUE = 0x1A
94
+ FLASH_YELLOW = 0x1B
95
+ FLASH_TEAL = 0x1C
96
+ FLASH_PURPLE = 0x1D
97
+ FLASH_WHITE = 0x1E
98
+
99
+ # Strobe effects
100
+ STROBE_RGB = 0x1F
101
+ STROBE_ALL_COLORS = 0x20