pygrouw 0.1.1__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.
- pygrouw-0.1.1/LICENSE +21 -0
- pygrouw-0.1.1/MANIFEST.in +1 -0
- pygrouw-0.1.1/PKG-INFO +156 -0
- pygrouw-0.1.1/README.md +130 -0
- pygrouw-0.1.1/pyproject.toml +46 -0
- pygrouw-0.1.1/reverse_engineered/app_identity.md +80 -0
- pygrouw-0.1.1/reverse_engineered/ble_write_flow.md +156 -0
- pygrouw-0.1.1/reverse_engineered/bluekey_commands.md +165 -0
- pygrouw-0.1.1/reverse_engineered/dart_blutter_analysis.md +205 -0
- pygrouw-0.1.1/reverse_engineered/dym_protocol.md +170 -0
- pygrouw-0.1.1/reverse_engineered/gatt_table.md +63 -0
- pygrouw-0.1.1/reverse_engineered/index.md +105 -0
- pygrouw-0.1.1/reverse_engineered/java_kotlin_findings.md +87 -0
- pygrouw-0.1.1/reverse_engineered/manual_findings.md +84 -0
- pygrouw-0.1.1/reverse_engineered/native_crypto.md +138 -0
- pygrouw-0.1.1/reverse_engineered/response_parsing.md +220 -0
- pygrouw-0.1.1/reverse_engineered/sources.md +89 -0
- pygrouw-0.1.1/setup.cfg +4 -0
- pygrouw-0.1.1/src/pygrouw/__init__.py +62 -0
- pygrouw-0.1.1/src/pygrouw/client.py +476 -0
- pygrouw-0.1.1/src/pygrouw/const.py +29 -0
- pygrouw-0.1.1/src/pygrouw/discovery.py +148 -0
- pygrouw-0.1.1/src/pygrouw/exceptions.py +26 -0
- pygrouw-0.1.1/src/pygrouw/mower.py +83 -0
- pygrouw-0.1.1/src/pygrouw/protocol.py +441 -0
- pygrouw-0.1.1/src/pygrouw/py.typed +1 -0
- pygrouw-0.1.1/src/pygrouw.egg-info/PKG-INFO +156 -0
- pygrouw-0.1.1/src/pygrouw.egg-info/SOURCES.txt +34 -0
- pygrouw-0.1.1/src/pygrouw.egg-info/dependency_links.txt +1 -0
- pygrouw-0.1.1/src/pygrouw.egg-info/requires.txt +5 -0
- pygrouw-0.1.1/src/pygrouw.egg-info/top_level.txt +1 -0
- pygrouw-0.1.1/tests/test_client.py +458 -0
- pygrouw-0.1.1/tests/test_discovery.py +135 -0
- pygrouw-0.1.1/tests/test_home_assistant_compatibility.py +64 -0
- pygrouw-0.1.1/tests/test_mower.py +108 -0
- pygrouw-0.1.1/tests/test_protocol.py +217 -0
pygrouw-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jesper B
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
recursive-include reverse_engineered *.md
|
pygrouw-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pygrouw
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python BLE library for Grouw robotic mowers using the Daye Power protocol
|
|
5
|
+
Author: Jesper B
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Bjorkan/pyGrouw
|
|
8
|
+
Project-URL: Issues, https://github.com/Bjorkan/pyGrouw/issues
|
|
9
|
+
Keywords: bluetooth,ble,grouw,mower,home-assistant
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Home Automation
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: bleak>=0.21.1
|
|
22
|
+
Requires-Dist: bleak-retry-connector>=3.6.0
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# pyGrouw
|
|
28
|
+
|
|
29
|
+
Python library for local Bluetooth Low Energy communication with Grouw robotic
|
|
30
|
+
mowers that use the Daye Power app protocol (`com.dayepower.dayeappleaf`).
|
|
31
|
+
|
|
32
|
+
This package contains the device/protocol code intended to be used by Home
|
|
33
|
+
Assistant integrations and other Python applications. It has no Home Assistant
|
|
34
|
+
runtime dependency.
|
|
35
|
+
|
|
36
|
+
## Status
|
|
37
|
+
|
|
38
|
+
The library currently targets the DYM-era Grouw mower generation observed in
|
|
39
|
+
the Daye Power APK and redacted real-hardware captures.
|
|
40
|
+
|
|
41
|
+
Durable protocol and reverse-engineering notes live in
|
|
42
|
+
[reverse_engineered/index.md](reverse_engineered/index.md).
|
|
43
|
+
|
|
44
|
+
Supported protocol helpers:
|
|
45
|
+
|
|
46
|
+
- DYM status, start/resume, pause/stop, dock, session start, and auth query
|
|
47
|
+
payload encoding.
|
|
48
|
+
- DYM status and auth/PIN notification parsing.
|
|
49
|
+
- APK-shaped BlueKey debug payload encoding and parsing helpers for protocol
|
|
50
|
+
research.
|
|
51
|
+
- Optional BLE discovery helpers that match the Home Assistant integration's
|
|
52
|
+
supported name/service UUID filters.
|
|
53
|
+
- Serialized BLE request flow using `bleak` and `bleak-retry-connector`.
|
|
54
|
+
|
|
55
|
+
Not yet supported:
|
|
56
|
+
|
|
57
|
+
- Cloud or Wi-Fi control.
|
|
58
|
+
- Settings writes for rain, schedules, multi-area, PIN change, or firmware
|
|
59
|
+
update.
|
|
60
|
+
|
|
61
|
+
## Installation For Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python3 -m venv .venv
|
|
65
|
+
. .venv/bin/activate
|
|
66
|
+
python3 -m pip install -e ".[test]"
|
|
67
|
+
pytest -q
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
Applications should resolve a connectable BLE device themselves, then pass it
|
|
73
|
+
to the client. Home Assistant integrations should use Home Assistant's
|
|
74
|
+
Bluetooth manager and inject the resolved device.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from pygrouw import GrouwBleMower, GrouwBleMowerClient
|
|
78
|
+
|
|
79
|
+
client = GrouwBleMowerClient(
|
|
80
|
+
address="AA:BB:CC:DD:EE:FF",
|
|
81
|
+
name="Robot Mower_DYM",
|
|
82
|
+
pin="1234",
|
|
83
|
+
device_provider=lambda: ble_device,
|
|
84
|
+
)
|
|
85
|
+
mower = GrouwMower(client)
|
|
86
|
+
|
|
87
|
+
state = await mower.async_update()
|
|
88
|
+
await mower.async_start()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`device_provider` may be synchronous or asynchronous. It must return a
|
|
92
|
+
connectable `bleak.backends.device.BLEDevice` or `None`.
|
|
93
|
+
|
|
94
|
+
For standalone scripts, the library also exposes optional discovery helpers:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pygrouw import GrouwBleMower, GrouwBleMowerClient, discover_devices
|
|
98
|
+
|
|
99
|
+
devices = await discover_devices(timeout=5)
|
|
100
|
+
client = await GrouwBleMowerClient.from_discovery(
|
|
101
|
+
address=devices[0].address,
|
|
102
|
+
pin="1234",
|
|
103
|
+
)
|
|
104
|
+
mower = GrouwMower(client)
|
|
105
|
+
|
|
106
|
+
state = await mower.async_update()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Home Assistant integrations should still prefer Home Assistant's Bluetooth
|
|
110
|
+
manager over calling these scanning helpers from inside Home Assistant.
|
|
111
|
+
|
|
112
|
+
## Home Assistant Development
|
|
113
|
+
|
|
114
|
+
When testing an unpublished editable library inside Home Assistant:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip3 install -e ../pyGrouw
|
|
118
|
+
hass --skip-pip-packages pygrouw
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
That follows Home Assistant's guidance for standalone API/protocol libraries:
|
|
122
|
+
protocol-specific code lives outside the integration and package releases are
|
|
123
|
+
eventually published to PyPI from tagged source releases.
|
|
124
|
+
|
|
125
|
+
## Release Publishing
|
|
126
|
+
|
|
127
|
+
GitHub Actions runs tests for pull requests and pushes to `main`. Published
|
|
128
|
+
GitHub releases also run the full test/build/check flow before uploading to
|
|
129
|
+
PyPI.
|
|
130
|
+
|
|
131
|
+
PyPI publishing is configured for Trusted Publishing. On PyPI, create a
|
|
132
|
+
Trusted Publisher for this project with:
|
|
133
|
+
|
|
134
|
+
- Repository owner: `Bjorkan`
|
|
135
|
+
- Repository name: `pyGrouw`
|
|
136
|
+
- Workflow name: `publish.yml`
|
|
137
|
+
- Environment name: `pypi`
|
|
138
|
+
|
|
139
|
+
If `pygrouw` does not exist on PyPI yet, create a pending publisher from your
|
|
140
|
+
PyPI account sidebar under **Publishing**. If the project already exists, open
|
|
141
|
+
the project on PyPI, go to **Manage project** -> **Publishing**, and add the
|
|
142
|
+
GitHub Actions publisher there.
|
|
143
|
+
|
|
144
|
+
Before publishing a release, update `version` in `pyproject.toml`, merge to
|
|
145
|
+
`main`, create a matching tag, and publish a GitHub Release from that tag. The
|
|
146
|
+
publish workflow requires the release tag to match the package version, with or
|
|
147
|
+
without a `v` prefix. For version `0.1.0`, both `0.1.0` and `v0.1.0` are valid.
|
|
148
|
+
|
|
149
|
+
## Dependency Updates
|
|
150
|
+
|
|
151
|
+
Renovate is configured in `renovate.json`. The GitHub workflow runs Renovate
|
|
152
|
+
at `00:00` UTC, and can also be started manually from the Actions tab.
|
|
153
|
+
|
|
154
|
+
Add a repository secret named `RENOVATE_TOKEN` with permission to create
|
|
155
|
+
branches and pull requests. Do not use the default `GITHUB_TOKEN` for Renovate:
|
|
156
|
+
pull requests created with it do not reliably trigger the normal PR checks.
|
pygrouw-0.1.1/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# pyGrouw
|
|
2
|
+
|
|
3
|
+
Python library for local Bluetooth Low Energy communication with Grouw robotic
|
|
4
|
+
mowers that use the Daye Power app protocol (`com.dayepower.dayeappleaf`).
|
|
5
|
+
|
|
6
|
+
This package contains the device/protocol code intended to be used by Home
|
|
7
|
+
Assistant integrations and other Python applications. It has no Home Assistant
|
|
8
|
+
runtime dependency.
|
|
9
|
+
|
|
10
|
+
## Status
|
|
11
|
+
|
|
12
|
+
The library currently targets the DYM-era Grouw mower generation observed in
|
|
13
|
+
the Daye Power APK and redacted real-hardware captures.
|
|
14
|
+
|
|
15
|
+
Durable protocol and reverse-engineering notes live in
|
|
16
|
+
[reverse_engineered/index.md](reverse_engineered/index.md).
|
|
17
|
+
|
|
18
|
+
Supported protocol helpers:
|
|
19
|
+
|
|
20
|
+
- DYM status, start/resume, pause/stop, dock, session start, and auth query
|
|
21
|
+
payload encoding.
|
|
22
|
+
- DYM status and auth/PIN notification parsing.
|
|
23
|
+
- APK-shaped BlueKey debug payload encoding and parsing helpers for protocol
|
|
24
|
+
research.
|
|
25
|
+
- Optional BLE discovery helpers that match the Home Assistant integration's
|
|
26
|
+
supported name/service UUID filters.
|
|
27
|
+
- Serialized BLE request flow using `bleak` and `bleak-retry-connector`.
|
|
28
|
+
|
|
29
|
+
Not yet supported:
|
|
30
|
+
|
|
31
|
+
- Cloud or Wi-Fi control.
|
|
32
|
+
- Settings writes for rain, schedules, multi-area, PIN change, or firmware
|
|
33
|
+
update.
|
|
34
|
+
|
|
35
|
+
## Installation For Development
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python3 -m venv .venv
|
|
39
|
+
. .venv/bin/activate
|
|
40
|
+
python3 -m pip install -e ".[test]"
|
|
41
|
+
pytest -q
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Applications should resolve a connectable BLE device themselves, then pass it
|
|
47
|
+
to the client. Home Assistant integrations should use Home Assistant's
|
|
48
|
+
Bluetooth manager and inject the resolved device.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from pygrouw import GrouwBleMower, GrouwBleMowerClient
|
|
52
|
+
|
|
53
|
+
client = GrouwBleMowerClient(
|
|
54
|
+
address="AA:BB:CC:DD:EE:FF",
|
|
55
|
+
name="Robot Mower_DYM",
|
|
56
|
+
pin="1234",
|
|
57
|
+
device_provider=lambda: ble_device,
|
|
58
|
+
)
|
|
59
|
+
mower = GrouwMower(client)
|
|
60
|
+
|
|
61
|
+
state = await mower.async_update()
|
|
62
|
+
await mower.async_start()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`device_provider` may be synchronous or asynchronous. It must return a
|
|
66
|
+
connectable `bleak.backends.device.BLEDevice` or `None`.
|
|
67
|
+
|
|
68
|
+
For standalone scripts, the library also exposes optional discovery helpers:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from pygrouw import GrouwBleMower, GrouwBleMowerClient, discover_devices
|
|
72
|
+
|
|
73
|
+
devices = await discover_devices(timeout=5)
|
|
74
|
+
client = await GrouwBleMowerClient.from_discovery(
|
|
75
|
+
address=devices[0].address,
|
|
76
|
+
pin="1234",
|
|
77
|
+
)
|
|
78
|
+
mower = GrouwMower(client)
|
|
79
|
+
|
|
80
|
+
state = await mower.async_update()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Home Assistant integrations should still prefer Home Assistant's Bluetooth
|
|
84
|
+
manager over calling these scanning helpers from inside Home Assistant.
|
|
85
|
+
|
|
86
|
+
## Home Assistant Development
|
|
87
|
+
|
|
88
|
+
When testing an unpublished editable library inside Home Assistant:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip3 install -e ../pyGrouw
|
|
92
|
+
hass --skip-pip-packages pygrouw
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
That follows Home Assistant's guidance for standalone API/protocol libraries:
|
|
96
|
+
protocol-specific code lives outside the integration and package releases are
|
|
97
|
+
eventually published to PyPI from tagged source releases.
|
|
98
|
+
|
|
99
|
+
## Release Publishing
|
|
100
|
+
|
|
101
|
+
GitHub Actions runs tests for pull requests and pushes to `main`. Published
|
|
102
|
+
GitHub releases also run the full test/build/check flow before uploading to
|
|
103
|
+
PyPI.
|
|
104
|
+
|
|
105
|
+
PyPI publishing is configured for Trusted Publishing. On PyPI, create a
|
|
106
|
+
Trusted Publisher for this project with:
|
|
107
|
+
|
|
108
|
+
- Repository owner: `Bjorkan`
|
|
109
|
+
- Repository name: `pyGrouw`
|
|
110
|
+
- Workflow name: `publish.yml`
|
|
111
|
+
- Environment name: `pypi`
|
|
112
|
+
|
|
113
|
+
If `pygrouw` does not exist on PyPI yet, create a pending publisher from your
|
|
114
|
+
PyPI account sidebar under **Publishing**. If the project already exists, open
|
|
115
|
+
the project on PyPI, go to **Manage project** -> **Publishing**, and add the
|
|
116
|
+
GitHub Actions publisher there.
|
|
117
|
+
|
|
118
|
+
Before publishing a release, update `version` in `pyproject.toml`, merge to
|
|
119
|
+
`main`, create a matching tag, and publish a GitHub Release from that tag. The
|
|
120
|
+
publish workflow requires the release tag to match the package version, with or
|
|
121
|
+
without a `v` prefix. For version `0.1.0`, both `0.1.0` and `v0.1.0` are valid.
|
|
122
|
+
|
|
123
|
+
## Dependency Updates
|
|
124
|
+
|
|
125
|
+
Renovate is configured in `renovate.json`. The GitHub workflow runs Renovate
|
|
126
|
+
at `00:00` UTC, and can also be started manually from the Actions tab.
|
|
127
|
+
|
|
128
|
+
Add a repository secret named `RENOVATE_TOKEN` with permission to create
|
|
129
|
+
branches and pull requests. Do not use the default `GITHUB_TOKEN` for Renovate:
|
|
130
|
+
pull requests created with it do not reliably trigger the normal PR checks.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pygrouw"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Python BLE library for Grouw robotic mowers using the Daye Power protocol"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "Jesper B" }]
|
|
13
|
+
keywords = ["bluetooth", "ble", "grouw", "mower", "home-assistant"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Programming Language :: Python :: 3.14",
|
|
22
|
+
"Topic :: Home Automation",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"bleak>=0.21.1",
|
|
26
|
+
"bleak-retry-connector>=3.6.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
test = [
|
|
31
|
+
"pytest>=8.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/Bjorkan/pyGrouw"
|
|
36
|
+
Issues = "https://github.com/Bjorkan/pyGrouw/issues"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-data]
|
|
42
|
+
pygrouw = ["py.typed"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# App Identity And BLE Discovery
|
|
2
|
+
|
|
3
|
+
Last updated: 2026-06-26.
|
|
4
|
+
|
|
5
|
+
## Daye APK Identity
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Package: com.dayepower.dayeappleaf
|
|
9
|
+
Version: 2.0.1
|
|
10
|
+
Version code: 117
|
|
11
|
+
Flutter app: romow_bluetooth
|
|
12
|
+
BLE library: flutter_blue_plus
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
BLE names found in the APK:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
Robot Mower_DYM
|
|
19
|
+
RobotMower_DYM
|
|
20
|
+
Robot_Mower-
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The app contains UI/routes for Bluetooth connection, mower control, mower
|
|
24
|
+
status, mower settings, firmware update, working-time settings, multi-area
|
|
25
|
+
mowing, rain mowing, rain delay, ultrasound, back-to-station, and go-to-work.
|
|
26
|
+
|
|
27
|
+
## UUIDs Found In The App
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
49535343-1E4D-4BD9-BA61-23C647249616
|
|
31
|
+
49535343-fe7d-4ae5-8fa9-9fafd205e455
|
|
32
|
+
00002902-0000-1000-8000-00805f9b34fb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`00002902-0000-1000-8000-00805f9b34fb` is the standard Client
|
|
36
|
+
Characteristic Configuration descriptor.
|
|
37
|
+
|
|
38
|
+
## FlutterBluePlus Signals
|
|
39
|
+
|
|
40
|
+
Strings found in the APK include:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
service_uuid
|
|
44
|
+
characteristic_uuid
|
|
45
|
+
writeAndNotify
|
|
46
|
+
writeAll
|
|
47
|
+
writeFinalChunk
|
|
48
|
+
allow_long_write
|
|
49
|
+
blueWriteAndNotification
|
|
50
|
+
BmWriteCharacteristicRequest
|
|
51
|
+
BmSetNotifyValueRequest
|
|
52
|
+
OnDiscoveredServices
|
|
53
|
+
OnCharacteristicReceived
|
|
54
|
+
OnCharacteristicWritten
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Home Assistant Discovery Impact
|
|
58
|
+
|
|
59
|
+
The integration should match:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
Service UUID: 49535343-fe7d-4ae5-8fa9-9fafd205e455
|
|
63
|
+
Local names: Robot Mower_DYM*, RobotMower_DYM*, Robot_Mower*
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The integration uses characteristic
|
|
67
|
+
`49535343-1E4D-4BD9-BA61-23C647249616` for write and notify.
|
|
68
|
+
|
|
69
|
+
## Manual Corroboration
|
|
70
|
+
|
|
71
|
+
The Grouw 17941/17947 manual says users should select `RobotMower_DYM` from the
|
|
72
|
+
Bluetooth device list in the app and enter the mower PIN. This supports the
|
|
73
|
+
current discovery aliases.
|
|
74
|
+
|
|
75
|
+
The Grouw 18739/18740 CLEVR manual describes a different IoT generation:
|
|
76
|
+
`robotic-mower connect`, 2.4 GHz Wi-Fi, Bluetooth 4.0, manual pairing as
|
|
77
|
+
`Mower_XXXXXX`, and factory PIN `0000`.
|
|
78
|
+
|
|
79
|
+
Do not add `Mower_XXXXXX` discovery to this DYM integration without separate
|
|
80
|
+
hardware captures for that generation.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# BLE Write And Notification Flow
|
|
2
|
+
|
|
3
|
+
Last updated: 2026-06-26.
|
|
4
|
+
|
|
5
|
+
Findings from `MainLogic::writeAndNotify` (`0x461eb4`),
|
|
6
|
+
`MainLogic::blueWriteAndNotification` (`0x461fa4`), and related Daye app
|
|
7
|
+
classes.
|
|
8
|
+
|
|
9
|
+
## Connection Sequence
|
|
10
|
+
|
|
11
|
+
When the Daye app sees `BluetoothDevice.connectionState == connected`,
|
|
12
|
+
`MainLogic` awaits `BluetoothDevice::requestMtu` before showing the success
|
|
13
|
+
toast and calling `discoverServices`.
|
|
14
|
+
|
|
15
|
+
The bundled FlutterBluePlus implementation requests MTU 512 with a 15-second
|
|
16
|
+
timeout.
|
|
17
|
+
|
|
18
|
+
## Write/Notify Sequence
|
|
19
|
+
|
|
20
|
+
1. `Helper::writeAndNotify` resolves `MainLogic` through GetIt and forwards
|
|
21
|
+
the payload, callback, and optional flags.
|
|
22
|
+
2. `MainLogic::writeAndNotify` checks the connection-type sentinel.
|
|
23
|
+
3. `MainLogic::blueWriteAndNotification` cancels the existing `resultListen`
|
|
24
|
+
subscription.
|
|
25
|
+
4. It subscribes to `onValueReceived` on the write characteristic.
|
|
26
|
+
5. It writes the command bytes with `BluetoothCharacteristic::write`.
|
|
27
|
+
6. On notification, it calls `Helper::parseBlueResult`.
|
|
28
|
+
7. The parsed one-based byte map is passed to the callback closure.
|
|
29
|
+
|
|
30
|
+
`Helper::writeAndNotify` signature:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
writeAndNotify(payload, callback, {
|
|
34
|
+
canBack,
|
|
35
|
+
errorTip,
|
|
36
|
+
noLimitNotify,
|
|
37
|
+
notifyType,
|
|
38
|
+
showTip,
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## `notifyType`
|
|
43
|
+
|
|
44
|
+
When `notifyType` is supplied, `blueWriteAndNotification` formats received byte
|
|
45
|
+
index 3, the fourth byte, as a padded hex string and compares it to
|
|
46
|
+
`notifyType` before invoking the callback.
|
|
47
|
+
|
|
48
|
+
This byte index is the same position as the DYM response command byte.
|
|
49
|
+
|
|
50
|
+
Observed `BlueKey::queryInfo` call sites:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
MowerStatusLogic::changeWorkType
|
|
54
|
+
Helper.writeAndNotify(BlueKey.queryInfo, callback,
|
|
55
|
+
canBack: true, showTip: false)
|
|
56
|
+
No notifyType filter. Callback reads byte13 for work-mode display.
|
|
57
|
+
|
|
58
|
+
DeviceLogic::initDeviceInfo
|
|
59
|
+
Helper.writeAndNotify(BlueKey.queryInfo, callback,
|
|
60
|
+
notifyType: "0x80", errorTip: "Get info error")
|
|
61
|
+
Callback reads byte5 battery and byte9-byte15 device/version fields.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This supports that app-side query/info handling can wait for response command
|
|
65
|
+
`0x80`, but does not prove that all DYM and BlueKey response fields share the
|
|
66
|
+
same semantics.
|
|
67
|
+
|
|
68
|
+
## State Classes
|
|
69
|
+
|
|
70
|
+
### MainState
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
0x08 connectType
|
|
74
|
+
0x0c area
|
|
75
|
+
0x10 robotPin
|
|
76
|
+
0x14 type
|
|
77
|
+
0x18 deviceName
|
|
78
|
+
0x1c deviceAddress
|
|
79
|
+
0x20 loading
|
|
80
|
+
0x24 startTime
|
|
81
|
+
0x28 isOpenAuto
|
|
82
|
+
0x2c haveBlueControll
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### MowerStatusState
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
0x08 disConnect
|
|
89
|
+
0x0c workType
|
|
90
|
+
0x10 mowerControl
|
|
91
|
+
0x14 timer
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### DeviceState
|
|
95
|
+
|
|
96
|
+
```text
|
|
97
|
+
0x08 connectType
|
|
98
|
+
0x0c currentIndex
|
|
99
|
+
0x10 pageController
|
|
100
|
+
0x14 deviceInfo
|
|
101
|
+
0x18 batteryImage
|
|
102
|
+
0x1c timer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### ChangePinState
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
0x08 oldPin
|
|
109
|
+
0x0c newPin
|
|
110
|
+
0x10 reNewPin
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### MowerSettingState
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
0x08 hour
|
|
117
|
+
0x0c min
|
|
118
|
+
0x10 mowInTheRain
|
|
119
|
+
0x14 boundaryCut
|
|
120
|
+
0x18 ultrasound
|
|
121
|
+
0x1c helixSet
|
|
122
|
+
0x20 led
|
|
123
|
+
0x24 timer
|
|
124
|
+
0x28 requestTimer
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### MultiAreaMowingState
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
0x08 area2Per
|
|
131
|
+
0x0c area2Dis
|
|
132
|
+
0x10 area3Per
|
|
133
|
+
0x14 area3Dis
|
|
134
|
+
0x18 timer
|
|
135
|
+
0x1c requestTimer
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### WorkingTimeSettingState
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
0x08 data
|
|
142
|
+
0x0c type
|
|
143
|
+
0x10 day
|
|
144
|
+
0x14 startHour
|
|
145
|
+
0x18 startMinute
|
|
146
|
+
0x1c workHour
|
|
147
|
+
0x20 workMinute
|
|
148
|
+
0x24 workMinuteList
|
|
149
|
+
0x28 startHourController
|
|
150
|
+
0x2c startMinuteController
|
|
151
|
+
0x30 workHourController
|
|
152
|
+
0x34 workMinuteController
|
|
153
|
+
0x38 dayController
|
|
154
|
+
0x3c timer
|
|
155
|
+
0x40 requestTimer
|
|
156
|
+
```
|