qingping-cgd1 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. qingping_cgd1-0.1.0/.github/workflows/ci.yml +82 -0
  2. qingping_cgd1-0.1.0/.github/workflows/release.yml +43 -0
  3. qingping_cgd1-0.1.0/.gitignore +26 -0
  4. qingping_cgd1-0.1.0/LICENSE +21 -0
  5. qingping_cgd1-0.1.0/PKG-INFO +126 -0
  6. qingping_cgd1-0.1.0/README.md +111 -0
  7. qingping_cgd1-0.1.0/lefthook.yml +21 -0
  8. qingping_cgd1-0.1.0/mise.toml +11 -0
  9. qingping_cgd1-0.1.0/pyproject.toml +102 -0
  10. qingping_cgd1-0.1.0/qingping_cgd1/__init__.py +56 -0
  11. qingping_cgd1-0.1.0/qingping_cgd1/client.py +341 -0
  12. qingping_cgd1-0.1.0/qingping_cgd1/codec.py +265 -0
  13. qingping_cgd1-0.1.0/qingping_cgd1/const.py +44 -0
  14. qingping_cgd1-0.1.0/qingping_cgd1/exceptions.py +23 -0
  15. qingping_cgd1-0.1.0/qingping_cgd1/models.py +93 -0
  16. qingping_cgd1-0.1.0/qingping_cgd1/py.typed +0 -0
  17. qingping_cgd1-0.1.0/tests/__init__.py +0 -0
  18. qingping_cgd1-0.1.0/tests/conftest.py +24 -0
  19. qingping_cgd1-0.1.0/tests/fake_ble.py +133 -0
  20. qingping_cgd1-0.1.0/tests/test_client_actions.py +100 -0
  21. qingping_cgd1-0.1.0/tests/test_client_commands.py +63 -0
  22. qingping_cgd1-0.1.0/tests/test_client_connect.py +90 -0
  23. qingping_cgd1-0.1.0/tests/test_client_idle.py +74 -0
  24. qingping_cgd1-0.1.0/tests/test_client_resilience.py +66 -0
  25. qingping_cgd1-0.1.0/tests/test_codec_advertisement.py +47 -0
  26. qingping_cgd1-0.1.0/tests/test_codec_alarm.py +62 -0
  27. qingping_cgd1-0.1.0/tests/test_codec_next_alarm.py +77 -0
  28. qingping_cgd1-0.1.0/tests/test_codec_sensor.py +46 -0
  29. qingping_cgd1-0.1.0/tests/test_codec_settings.py +100 -0
  30. qingping_cgd1-0.1.0/tests/test_codec_time.py +17 -0
  31. qingping_cgd1-0.1.0/tests/test_const.py +39 -0
  32. qingping_cgd1-0.1.0/tests/test_exceptions.py +31 -0
  33. qingping_cgd1-0.1.0/tests/test_models.py +73 -0
  34. qingping_cgd1-0.1.0/tests/test_public_api.py +44 -0
  35. qingping_cgd1-0.1.0/tests/test_readme.py +21 -0
  36. qingping_cgd1-0.1.0/tests/test_smoke.py +10 -0
  37. qingping_cgd1-0.1.0/uv.lock +650 -0
@@ -0,0 +1,82 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ lint:
18
+ name: Ruff
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
22
+ with:
23
+ persist-credentials: false
24
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
25
+ with:
26
+ python-version: "3.14"
27
+ enable-cache: true
28
+ - run: uv sync --group dev
29
+ - run: uv run ruff check --output-format=github .
30
+ - run: uv run ruff format --check .
31
+
32
+ typecheck:
33
+ name: mypy --strict
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
37
+ with:
38
+ persist-credentials: false
39
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
40
+ with:
41
+ python-version: "3.14"
42
+ enable-cache: true
43
+ - run: uv sync --group dev --group test
44
+ - run: uv run mypy qingping_cgd1
45
+
46
+ test:
47
+ name: pytest (${{ matrix.os }})
48
+ runs-on: ${{ matrix.os }}
49
+ strategy:
50
+ fail-fast: false
51
+ matrix:
52
+ os: [ubuntu-latest, macos-latest]
53
+ steps:
54
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
55
+ with:
56
+ persist-credentials: false
57
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
58
+ with:
59
+ python-version: "3.14"
60
+ enable-cache: true
61
+ - run: uv sync --group test
62
+ - run: >-
63
+ uv run --no-sync pytest
64
+ --cov=qingping_cgd1
65
+ --cov-branch
66
+ --cov-report=term-missing
67
+ --cov-report=xml
68
+ - run: >-
69
+ uv run --no-sync coverage report
70
+ --include="*/codec.py,*/models.py,*/const.py" --fail-under=100
71
+
72
+ zizmor:
73
+ name: zizmor
74
+ runs-on: ubuntu-latest
75
+ permissions:
76
+ contents: read
77
+ steps:
78
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
79
+ with:
80
+ persist-credentials: false
81
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
82
+ - run: uvx zizmor@1.25.2 --persona=pedantic .github/workflows
@@ -0,0 +1,43 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ build:
16
+ name: Build distributions
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
20
+ with:
21
+ persist-credentials: false
22
+ - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
23
+ with:
24
+ enable-cache: false
25
+ - run: uv build
26
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ name: Publish to PyPI
33
+ needs: build
34
+ runs-on: ubuntu-latest
35
+ environment: pypi
36
+ permissions:
37
+ id-token: write # OIDC token for trusted publishing; no stored secret
38
+ steps:
39
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
40
+ with:
41
+ name: dist
42
+ path: dist/
43
+ - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
@@ -0,0 +1,26 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Virtual environments
10
+ .venv/
11
+
12
+ # Tooling caches
13
+ .mypy_cache/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ coverage.xml
18
+ htmlcov/
19
+
20
+ # Editor / OS
21
+ .DS_Store
22
+ .idea/
23
+ .vscode/
24
+
25
+ # Scratch
26
+ .superpowers/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robert Coleman
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,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: qingping-cgd1
3
+ Version: 0.1.0
4
+ Summary: Pure-Python BLE library for the Qingping CGD1 alarm clock
5
+ Project-URL: Homepage, https://github.com/rjocoleman/qingping-cgd1
6
+ Project-URL: Source, https://github.com/rjocoleman/qingping-cgd1
7
+ Author: Robert Coleman
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: alarm-clock,ble,bluetooth,cgd1,qingping
11
+ Requires-Python: >=3.14.2
12
+ Requires-Dist: bleak-retry-connector>=3.5
13
+ Requires-Dist: bleak>=0.22
14
+ Description-Content-Type: text/markdown
15
+
16
+ # qingping-cgd1
17
+
18
+ Local BLE control for the Qingping CGD1 "Dove" alarm clock. Pure Python, no
19
+ cloud. Not affiliated with Qingping.
20
+
21
+ ## Install
22
+
23
+ ```
24
+ pip install qingping-cgd1
25
+ ```
26
+
27
+ Requires Python 3.14.2+. Depends only on `bleak` and `bleak-retry-connector`.
28
+
29
+ ## Passive sensor reading
30
+
31
+ Temperature, humidity, and battery ride in the BLE advertisement, so you can
32
+ read them without connecting to the device:
33
+
34
+ ```python
35
+ from qingping_cgd1 import parse_advertisement
36
+
37
+ reading = parse_advertisement(service_data) # the 0xfdcd service-data bytes
38
+ if reading is not None:
39
+ print(reading.temperature, reading.humidity, reading.battery)
40
+ ```
41
+
42
+ ## Active control
43
+
44
+ Everything else - settings, alarms, time sync - needs a connection,
45
+ authenticated with a `bleak` `BLEDevice`:
46
+
47
+ ```python
48
+ from qingping_cgd1 import Alarm, QingpingCGD1Client, Weekday
49
+
50
+ async with QingpingCGD1Client(ble_device) as client:
51
+ settings = await client.read_settings()
52
+ await client.write_settings(settings)
53
+
54
+ alarms = await client.read_alarms()
55
+ await client.write_alarm(0, Alarm(
56
+ enabled=True,
57
+ hour=7,
58
+ minute=30,
59
+ days=frozenset({Weekday.MONDAY, Weekday.TUESDAY}),
60
+ snooze=True,
61
+ ))
62
+ await client.delete_alarm(1)
63
+
64
+ await client.sync_time()
65
+ info = await client.read_firmware()
66
+ ```
67
+
68
+ `QingpingCGD1Client` also has explicit `connect()`/`disconnect()` methods if
69
+ you'd rather not use it as a context manager. It authenticates with a
70
+ 16-byte token (overridable via the `token` argument, default
71
+ `DEFAULT_AUTH_TOKEN`), retries a timed-out command once after a reconnect,
72
+ and disconnects automatically after `disconnect_delay` seconds of
73
+ inactivity (default 120s).
74
+
75
+ ### Working out the next alarm
76
+
77
+ `next_alarm` takes the list from `read_alarms()` and a reference time, and
78
+ returns the soonest upcoming fire time (or `None` if nothing is enabled):
79
+
80
+ ```python
81
+ from datetime import datetime
82
+
83
+ from qingping_cgd1 import next_alarm
84
+
85
+ upcoming = next_alarm(alarms, datetime.now())
86
+ ```
87
+
88
+ ## Protocol notes
89
+
90
+ This library is reverse-engineered, not an official SDK. The protocol was
91
+ mapped mainly by [MrBoombastic/clOwOck](https://github.com/MrBoombastic/clOwOck),
92
+ an Android app for the CGD1; this is a Python port of that work. A few things
93
+ worth knowing before you rely on it:
94
+
95
+ - There are no checksums on the wire. Frames are fixed-layout and unvalidated
96
+ by the device beyond their length.
97
+ - The 16-byte auth token is a per-device pairing secret, not a universal
98
+ key. A clock that's already paired (for example, one that's been used
99
+ with the official Qingping+ app) will reject the default token: the
100
+ device answers auth step 2 with `04 ff 02 00 01` (fail). You need to
101
+ unbind/reset the clock first - long-press its button until the
102
+ Bluetooth icon flashes, or remove it in the Qingping+ app - after which
103
+ it binds to the first token it's presented with. This library presents
104
+ its default token, and from then on that token authenticates every
105
+ time. The token is injectable via the `token` argument if you'd rather
106
+ manage pairing secrets yourself.
107
+ - The `0xfdcd` advertisement layout (`model | mac | temperature | humidity |
108
+ battery`) is confirmed from a real CGD1 capture (firmware 1.0.1_0130):
109
+ decoding it gave 20.0 C / 51.7% / 80%, matching the device's own
110
+ display. There are still two unknown/reserved byte pairs in the frame
111
+ whose meaning isn't established.
112
+
113
+ ## Scope
114
+
115
+ Covers passive sensor reading from advertisements, settings read/write,
116
+ all 16 alarm slots, and time sync. Ringtone upload is not implemented.
117
+
118
+ ## How this was built
119
+
120
+ Built largely with AI assistance (Claude) and tested against a real CGD1
121
+ (firmware 1.0.1_0130). Tested and working, but a spare-time project - no
122
+ warranty, no support promises.
123
+
124
+ ## Licence
125
+
126
+ MIT.
@@ -0,0 +1,111 @@
1
+ # qingping-cgd1
2
+
3
+ Local BLE control for the Qingping CGD1 "Dove" alarm clock. Pure Python, no
4
+ cloud. Not affiliated with Qingping.
5
+
6
+ ## Install
7
+
8
+ ```
9
+ pip install qingping-cgd1
10
+ ```
11
+
12
+ Requires Python 3.14.2+. Depends only on `bleak` and `bleak-retry-connector`.
13
+
14
+ ## Passive sensor reading
15
+
16
+ Temperature, humidity, and battery ride in the BLE advertisement, so you can
17
+ read them without connecting to the device:
18
+
19
+ ```python
20
+ from qingping_cgd1 import parse_advertisement
21
+
22
+ reading = parse_advertisement(service_data) # the 0xfdcd service-data bytes
23
+ if reading is not None:
24
+ print(reading.temperature, reading.humidity, reading.battery)
25
+ ```
26
+
27
+ ## Active control
28
+
29
+ Everything else - settings, alarms, time sync - needs a connection,
30
+ authenticated with a `bleak` `BLEDevice`:
31
+
32
+ ```python
33
+ from qingping_cgd1 import Alarm, QingpingCGD1Client, Weekday
34
+
35
+ async with QingpingCGD1Client(ble_device) as client:
36
+ settings = await client.read_settings()
37
+ await client.write_settings(settings)
38
+
39
+ alarms = await client.read_alarms()
40
+ await client.write_alarm(0, Alarm(
41
+ enabled=True,
42
+ hour=7,
43
+ minute=30,
44
+ days=frozenset({Weekday.MONDAY, Weekday.TUESDAY}),
45
+ snooze=True,
46
+ ))
47
+ await client.delete_alarm(1)
48
+
49
+ await client.sync_time()
50
+ info = await client.read_firmware()
51
+ ```
52
+
53
+ `QingpingCGD1Client` also has explicit `connect()`/`disconnect()` methods if
54
+ you'd rather not use it as a context manager. It authenticates with a
55
+ 16-byte token (overridable via the `token` argument, default
56
+ `DEFAULT_AUTH_TOKEN`), retries a timed-out command once after a reconnect,
57
+ and disconnects automatically after `disconnect_delay` seconds of
58
+ inactivity (default 120s).
59
+
60
+ ### Working out the next alarm
61
+
62
+ `next_alarm` takes the list from `read_alarms()` and a reference time, and
63
+ returns the soonest upcoming fire time (or `None` if nothing is enabled):
64
+
65
+ ```python
66
+ from datetime import datetime
67
+
68
+ from qingping_cgd1 import next_alarm
69
+
70
+ upcoming = next_alarm(alarms, datetime.now())
71
+ ```
72
+
73
+ ## Protocol notes
74
+
75
+ This library is reverse-engineered, not an official SDK. The protocol was
76
+ mapped mainly by [MrBoombastic/clOwOck](https://github.com/MrBoombastic/clOwOck),
77
+ an Android app for the CGD1; this is a Python port of that work. A few things
78
+ worth knowing before you rely on it:
79
+
80
+ - There are no checksums on the wire. Frames are fixed-layout and unvalidated
81
+ by the device beyond their length.
82
+ - The 16-byte auth token is a per-device pairing secret, not a universal
83
+ key. A clock that's already paired (for example, one that's been used
84
+ with the official Qingping+ app) will reject the default token: the
85
+ device answers auth step 2 with `04 ff 02 00 01` (fail). You need to
86
+ unbind/reset the clock first - long-press its button until the
87
+ Bluetooth icon flashes, or remove it in the Qingping+ app - after which
88
+ it binds to the first token it's presented with. This library presents
89
+ its default token, and from then on that token authenticates every
90
+ time. The token is injectable via the `token` argument if you'd rather
91
+ manage pairing secrets yourself.
92
+ - The `0xfdcd` advertisement layout (`model | mac | temperature | humidity |
93
+ battery`) is confirmed from a real CGD1 capture (firmware 1.0.1_0130):
94
+ decoding it gave 20.0 C / 51.7% / 80%, matching the device's own
95
+ display. There are still two unknown/reserved byte pairs in the frame
96
+ whose meaning isn't established.
97
+
98
+ ## Scope
99
+
100
+ Covers passive sensor reading from advertisements, settings read/write,
101
+ all 16 alarm slots, and time sync. Ringtone upload is not implemented.
102
+
103
+ ## How this was built
104
+
105
+ Built largely with AI assistance (Claude) and tested against a real CGD1
106
+ (firmware 1.0.1_0130). Tested and working, but a spare-time project - no
107
+ warranty, no support promises.
108
+
109
+ ## Licence
110
+
111
+ MIT.
@@ -0,0 +1,21 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ ruff-check:
5
+ glob: "*.py"
6
+ run: uv run ruff check --force-exclude {staged_files}
7
+ stage_fixed: true
8
+ ruff-format:
9
+ glob: "*.py"
10
+ run: uv run ruff format --check --force-exclude {staged_files}
11
+ mypy:
12
+ glob: "*.py"
13
+ run: uv run mypy qingping_cgd1
14
+ zizmor:
15
+ glob: ".github/workflows/*.{yml,yaml}"
16
+ run: uvx zizmor@1.25.2 {staged_files}
17
+
18
+ pre-push:
19
+ commands:
20
+ pytest:
21
+ run: uv run pytest
@@ -0,0 +1,11 @@
1
+ [tools]
2
+ python = "3.14"
3
+ uv = "latest"
4
+ lefthook = "2.1.9"
5
+ zizmor = "1.25.2"
6
+
7
+ [settings]
8
+ python.uv_venv_auto = "create|source"
9
+
10
+ [env]
11
+ UV_PYTHON = { value = "{{ tools.python.path }}", tools = true }
@@ -0,0 +1,102 @@
1
+ [project]
2
+ name = "qingping-cgd1"
3
+ version = "0.1.0"
4
+ description = "Pure-Python BLE library for the Qingping CGD1 alarm clock"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14.2"
7
+ license = "MIT"
8
+ authors = [{ name = "Robert Coleman" }]
9
+ keywords = ["bluetooth", "ble", "qingping", "cgd1", "alarm-clock"]
10
+ dependencies = [
11
+ "bleak>=0.22",
12
+ "bleak-retry-connector>=3.5",
13
+ ]
14
+
15
+ [project.urls]
16
+ Homepage = "https://github.com/rjocoleman/qingping-cgd1"
17
+ Source = "https://github.com/rjocoleman/qingping-cgd1"
18
+
19
+ [dependency-groups]
20
+ dev = [
21
+ "ruff>=0.15",
22
+ "mypy>=2.1",
23
+ ]
24
+ test = [
25
+ "pytest>=8.3",
26
+ "pytest-asyncio>=0.25",
27
+ "pytest-cov>=6.0",
28
+ "freezegun>=1.5",
29
+ ]
30
+
31
+ [build-system]
32
+ requires = ["hatchling"]
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["qingping_cgd1"]
37
+
38
+ [tool.ruff]
39
+ required-version = ">=0.14"
40
+ target-version = "py314"
41
+ line-length = 88
42
+ src = ["qingping_cgd1", "tests"]
43
+
44
+ [tool.ruff.lint]
45
+ select = [
46
+ "A", "ASYNC", "B", "BLE", "C4", "C90", "D", "DTZ", "E", "EM", "EXE", "F",
47
+ "FA", "FLY", "FURB", "G", "I", "ICN", "INP", "ISC", "LOG", "N", "PERF",
48
+ "PGH", "PIE", "PL", "PT", "PTH", "RET", "RSE", "RUF", "S", "SIM", "SLF",
49
+ "SLOT", "T20", "TC", "TID", "TRY", "UP", "W",
50
+ ]
51
+ ignore = [
52
+ "D203", # incompatible with D211
53
+ "D213", # incompatible with D212
54
+ "E501", # the formatter owns line width
55
+ "ISC001", # conflicts with the formatter
56
+ ]
57
+
58
+ [tool.ruff.lint.per-file-ignores]
59
+ "tests/**/*.py" = [
60
+ "D", # tests document themselves through their names
61
+ "S101", # asserts are the point of tests
62
+ "PLR2004", # magic values are fine in tests
63
+ "SLF001", # tests reach into internals
64
+ ]
65
+
66
+ [tool.ruff.lint.isort]
67
+ force-sort-within-sections = true
68
+ combine-as-imports = true
69
+ known-first-party = ["qingping_cgd1"]
70
+
71
+ [tool.ruff.lint.mccabe]
72
+ max-complexity = 12
73
+
74
+ [tool.ruff.lint.pydocstyle]
75
+ convention = "pep257"
76
+
77
+ [tool.mypy]
78
+ python_version = "3.14"
79
+ strict = true
80
+ warn_unreachable = true
81
+ enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
82
+ follow_imports = "normal"
83
+ local_partial_types = true
84
+ no_implicit_reexport = true
85
+
86
+ [tool.pytest.ini_options]
87
+ testpaths = ["tests"]
88
+ pythonpath = ["."]
89
+ asyncio_mode = "auto"
90
+ addopts = "-ra --strict-markers --strict-config"
91
+
92
+ [tool.coverage.run]
93
+ branch = true
94
+ source = ["qingping_cgd1"]
95
+
96
+ [tool.coverage.report]
97
+ show_missing = true
98
+ exclude_also = [
99
+ "if TYPE_CHECKING:",
100
+ "raise NotImplementedError",
101
+ "@overload",
102
+ ]
@@ -0,0 +1,56 @@
1
+ """Pure-Python BLE library for the Qingping CGD1 alarm clock."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .client import QingpingCGD1Client
6
+ from .codec import (
7
+ decode_alarm,
8
+ decode_settings,
9
+ encode_alarm,
10
+ encode_settings,
11
+ encode_time,
12
+ next_alarm,
13
+ parse_advertisement,
14
+ parse_connected_sensor,
15
+ )
16
+ from .const import ALARM_SLOT_COUNT, DEFAULT_AUTH_TOKEN
17
+ from .exceptions import (
18
+ AuthError,
19
+ CommandError,
20
+ ConnectionError, # noqa: A004 - deliberate re-export, see exceptions.py
21
+ QingpingError,
22
+ )
23
+ from .models import (
24
+ Alarm,
25
+ DeviceInfo,
26
+ DeviceSettings,
27
+ Language,
28
+ SensorData,
29
+ Weekday,
30
+ )
31
+
32
+ __version__ = "0.1.0"
33
+
34
+ __all__ = [
35
+ "ALARM_SLOT_COUNT",
36
+ "DEFAULT_AUTH_TOKEN",
37
+ "Alarm",
38
+ "AuthError",
39
+ "CommandError",
40
+ "ConnectionError",
41
+ "DeviceInfo",
42
+ "DeviceSettings",
43
+ "Language",
44
+ "QingpingCGD1Client",
45
+ "QingpingError",
46
+ "SensorData",
47
+ "Weekday",
48
+ "decode_alarm",
49
+ "decode_settings",
50
+ "encode_alarm",
51
+ "encode_settings",
52
+ "encode_time",
53
+ "next_alarm",
54
+ "parse_advertisement",
55
+ "parse_connected_sensor",
56
+ ]