Robomow-BLE 1.0.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.
- robomow_ble-1.0.0/LICENSE +21 -0
- robomow_ble-1.0.0/PKG-INFO +193 -0
- robomow_ble-1.0.0/README.md +161 -0
- robomow_ble-1.0.0/pyproject.toml +55 -0
- robomow_ble-1.0.0/setup.cfg +4 -0
- robomow_ble-1.0.0/src/Robomow_BLE.egg-info/PKG-INFO +193 -0
- robomow_ble-1.0.0/src/Robomow_BLE.egg-info/SOURCES.txt +18 -0
- robomow_ble-1.0.0/src/Robomow_BLE.egg-info/dependency_links.txt +1 -0
- robomow_ble-1.0.0/src/Robomow_BLE.egg-info/requires.txt +9 -0
- robomow_ble-1.0.0/src/Robomow_BLE.egg-info/top_level.txt +1 -0
- robomow_ble-1.0.0/src/robomow_ble/__init__.py +43 -0
- robomow_ble-1.0.0/src/robomow_ble/const.py +205 -0
- robomow_ble-1.0.0/src/robomow_ble/const_rt.py +628 -0
- robomow_ble-1.0.0/src/robomow_ble/exceptions.py +9 -0
- robomow_ble-1.0.0/src/robomow_ble/family_handler_base.py +104 -0
- robomow_ble-1.0.0/src/robomow_ble/family_handler_rt.py +621 -0
- robomow_ble-1.0.0/src/robomow_ble/helpers.py +34 -0
- robomow_ble-1.0.0/src/robomow_ble/mower.py +1212 -0
- robomow_ble-1.0.0/src/robomow_ble/py.typed +1 -0
- robomow_ble-1.0.0/tests/test_smoke.py +38 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arjan Mels
|
|
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,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Robomow-BLE
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Robomow BLE protocol library
|
|
5
|
+
Author: Robomow-HA contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/arjanmels/robomow_ble
|
|
8
|
+
Project-URL: Changelog, https://github.com/arjanmels/robomow_ble/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Documentation, https://arjanmels.github.io/robomow_ble/
|
|
10
|
+
Project-URL: Issues, https://github.com/arjanmels/robomow_ble/issues
|
|
11
|
+
Keywords: ble,bluetooth,robomow,lawn-mower,protocol
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: attrs>=23.2.0
|
|
25
|
+
Requires-Dist: bleak>=0.22.0
|
|
26
|
+
Requires-Dist: bleak-retry-connector>=3.10.0
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
29
|
+
Provides-Extra: docs
|
|
30
|
+
Requires-Dist: pdoc>=15.0; extra == "docs"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Robomow-BLE
|
|
34
|
+
|
|
35
|
+
Robomow-BLE is a standalone Python library for talking to Robomow mowers over
|
|
36
|
+
Bluetooth Low Energy (BLE).
|
|
37
|
+
|
|
38
|
+
It provides a reusable BLE protocol layer for Robomow mowers and can be used
|
|
39
|
+
directly in Python applications.
|
|
40
|
+
|
|
41
|
+
Currently it only supports RT models.
|
|
42
|
+
Support for other models may be added in the future if there is demand and access to devices for testing.
|
|
43
|
+
|
|
44
|
+
## API Overview
|
|
45
|
+
|
|
46
|
+
Please refer to the [API documentation](https://arjanmels.github.io/robomow_ble/) for details.
|
|
47
|
+
|
|
48
|
+
## How to use
|
|
49
|
+
|
|
50
|
+
### Install
|
|
51
|
+
|
|
52
|
+
Install from PyPI:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m pip install Robomow-BLE
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Create a device client
|
|
59
|
+
|
|
60
|
+
Create a `RobomowDevice` with:
|
|
61
|
+
|
|
62
|
+
- `mainboard_serial`: mower mainboard serial used during authentication.
|
|
63
|
+
- `update_callback` (optional): called for each update as `RobomowUpdate(key, value)`.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from robomow_ble import RobomowDevice
|
|
68
|
+
|
|
69
|
+
mower = RobomowDevice(
|
|
70
|
+
mainboard_serial="12345678901234",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### How to find `mainboard_serial`:
|
|
75
|
+
|
|
76
|
+
1. Log in to myrobomow.robomow.com.
|
|
77
|
+
2. Open your browser's Developer Tools (F12).
|
|
78
|
+
3. Go to the Network tab.
|
|
79
|
+
4. Reload the page.
|
|
80
|
+
5. Look for a request ending in `/api/customer/products`.
|
|
81
|
+
6. Click the request and open the Response tab.
|
|
82
|
+
7. In the JSON response, find the field named `MainboardSerial`. This value is the actual mainboard serial number.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
### Connect and authenticate
|
|
86
|
+
|
|
87
|
+
Discover a BLE device with Bleak, then connect:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from bleak import BleakScanner
|
|
91
|
+
|
|
92
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
93
|
+
if ble_device is None:
|
|
94
|
+
raise RuntimeError("Mower not found")
|
|
95
|
+
|
|
96
|
+
await mower.async_connect(ble_device)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Receive updates
|
|
100
|
+
|
|
101
|
+
Pass a callback to receive state changes:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from robomow_ble import RobomowUpdate, EntityKey
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
108
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
109
|
+
print(f"Battery: {update.value}%")
|
|
110
|
+
else:
|
|
111
|
+
print(f"{update.key}: {update.value}")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Send commands
|
|
115
|
+
|
|
116
|
+
Common control methods:
|
|
117
|
+
|
|
118
|
+
- `await mower.async_start_mowing(duration_minutes=30)`
|
|
119
|
+
- `await mower.async_start_mowing_edge()`
|
|
120
|
+
- `await mower.async_stop_mowing()`
|
|
121
|
+
- `await mower.async_return_to_home()`
|
|
122
|
+
|
|
123
|
+
Common settings methods:
|
|
124
|
+
|
|
125
|
+
- `await mower.async_enable_schedule()` / `await mower.async_disable_schedule()`
|
|
126
|
+
- `await mower.async_set_schedule(schedule)`
|
|
127
|
+
- `await mower.async_enable_anti_theft()` / `await mower.async_disable_anti_theft()`
|
|
128
|
+
- `await mower.async_enable_child_lock()` / `await mower.async_disable_child_lock()`
|
|
129
|
+
- `await mower.async_set_wire_signal_type(wire_signal_type)`
|
|
130
|
+
|
|
131
|
+
### Read state
|
|
132
|
+
|
|
133
|
+
Example state properties:
|
|
134
|
+
|
|
135
|
+
- Identity/version: `family`, `model`, `mainboard_version`, `software_version`
|
|
136
|
+
- Live values: `operating_state`, `message`, `battery_level`, `rssi`
|
|
137
|
+
- Status: `schedule_enabled`, `next_departure`, `expected_duration`
|
|
138
|
+
|
|
139
|
+
### Disconnect cleanly
|
|
140
|
+
|
|
141
|
+
Always disconnect in a `finally` block:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
try:
|
|
145
|
+
...
|
|
146
|
+
finally:
|
|
147
|
+
await mower.async_disconnect()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Minimal example
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import asyncio
|
|
154
|
+
|
|
155
|
+
from bleak import BleakScanner
|
|
156
|
+
from robomow_ble import RobomowDevice, RobomowUpdate, EntityKey
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
160
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
161
|
+
print(f"Battery: {update.value}%")
|
|
162
|
+
else:
|
|
163
|
+
print(f"{update.key}: {update.value}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def main() -> None:
|
|
167
|
+
# Replace these with your mower details
|
|
168
|
+
address = "AA:BB:CC:DD:EE:FF"
|
|
169
|
+
mainboard_serial = "12345678901234"
|
|
170
|
+
|
|
171
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
172
|
+
if ble_device is None:
|
|
173
|
+
raise RuntimeError("Mower not found")
|
|
174
|
+
|
|
175
|
+
mower = RobomowDevice(
|
|
176
|
+
mainboard_serial=mainboard_serial,
|
|
177
|
+
update_callback=on_update,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
await mower.async_connect(ble_device)
|
|
181
|
+
try:
|
|
182
|
+
await mower.async_start_mowing(duration_minutes=30)
|
|
183
|
+
await asyncio.sleep(5)
|
|
184
|
+
print("State:", mower.operating_state)
|
|
185
|
+
finally:
|
|
186
|
+
await mower.async_disconnect()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
asyncio.run(main())
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Robomow-BLE
|
|
2
|
+
|
|
3
|
+
Robomow-BLE is a standalone Python library for talking to Robomow mowers over
|
|
4
|
+
Bluetooth Low Energy (BLE).
|
|
5
|
+
|
|
6
|
+
It provides a reusable BLE protocol layer for Robomow mowers and can be used
|
|
7
|
+
directly in Python applications.
|
|
8
|
+
|
|
9
|
+
Currently it only supports RT models.
|
|
10
|
+
Support for other models may be added in the future if there is demand and access to devices for testing.
|
|
11
|
+
|
|
12
|
+
## API Overview
|
|
13
|
+
|
|
14
|
+
Please refer to the [API documentation](https://arjanmels.github.io/robomow_ble/) for details.
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
|
|
18
|
+
### Install
|
|
19
|
+
|
|
20
|
+
Install from PyPI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python -m pip install Robomow-BLE
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Create a device client
|
|
27
|
+
|
|
28
|
+
Create a `RobomowDevice` with:
|
|
29
|
+
|
|
30
|
+
- `mainboard_serial`: mower mainboard serial used during authentication.
|
|
31
|
+
- `update_callback` (optional): called for each update as `RobomowUpdate(key, value)`.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from robomow_ble import RobomowDevice
|
|
36
|
+
|
|
37
|
+
mower = RobomowDevice(
|
|
38
|
+
mainboard_serial="12345678901234",
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### How to find `mainboard_serial`:
|
|
43
|
+
|
|
44
|
+
1. Log in to myrobomow.robomow.com.
|
|
45
|
+
2. Open your browser's Developer Tools (F12).
|
|
46
|
+
3. Go to the Network tab.
|
|
47
|
+
4. Reload the page.
|
|
48
|
+
5. Look for a request ending in `/api/customer/products`.
|
|
49
|
+
6. Click the request and open the Response tab.
|
|
50
|
+
7. In the JSON response, find the field named `MainboardSerial`. This value is the actual mainboard serial number.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Connect and authenticate
|
|
54
|
+
|
|
55
|
+
Discover a BLE device with Bleak, then connect:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from bleak import BleakScanner
|
|
59
|
+
|
|
60
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
61
|
+
if ble_device is None:
|
|
62
|
+
raise RuntimeError("Mower not found")
|
|
63
|
+
|
|
64
|
+
await mower.async_connect(ble_device)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Receive updates
|
|
68
|
+
|
|
69
|
+
Pass a callback to receive state changes:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from robomow_ble import RobomowUpdate, EntityKey
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
76
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
77
|
+
print(f"Battery: {update.value}%")
|
|
78
|
+
else:
|
|
79
|
+
print(f"{update.key}: {update.value}")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Send commands
|
|
83
|
+
|
|
84
|
+
Common control methods:
|
|
85
|
+
|
|
86
|
+
- `await mower.async_start_mowing(duration_minutes=30)`
|
|
87
|
+
- `await mower.async_start_mowing_edge()`
|
|
88
|
+
- `await mower.async_stop_mowing()`
|
|
89
|
+
- `await mower.async_return_to_home()`
|
|
90
|
+
|
|
91
|
+
Common settings methods:
|
|
92
|
+
|
|
93
|
+
- `await mower.async_enable_schedule()` / `await mower.async_disable_schedule()`
|
|
94
|
+
- `await mower.async_set_schedule(schedule)`
|
|
95
|
+
- `await mower.async_enable_anti_theft()` / `await mower.async_disable_anti_theft()`
|
|
96
|
+
- `await mower.async_enable_child_lock()` / `await mower.async_disable_child_lock()`
|
|
97
|
+
- `await mower.async_set_wire_signal_type(wire_signal_type)`
|
|
98
|
+
|
|
99
|
+
### Read state
|
|
100
|
+
|
|
101
|
+
Example state properties:
|
|
102
|
+
|
|
103
|
+
- Identity/version: `family`, `model`, `mainboard_version`, `software_version`
|
|
104
|
+
- Live values: `operating_state`, `message`, `battery_level`, `rssi`
|
|
105
|
+
- Status: `schedule_enabled`, `next_departure`, `expected_duration`
|
|
106
|
+
|
|
107
|
+
### Disconnect cleanly
|
|
108
|
+
|
|
109
|
+
Always disconnect in a `finally` block:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
try:
|
|
113
|
+
...
|
|
114
|
+
finally:
|
|
115
|
+
await mower.async_disconnect()
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Minimal example
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import asyncio
|
|
122
|
+
|
|
123
|
+
from bleak import BleakScanner
|
|
124
|
+
from robomow_ble import RobomowDevice, RobomowUpdate, EntityKey
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
128
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
129
|
+
print(f"Battery: {update.value}%")
|
|
130
|
+
else:
|
|
131
|
+
print(f"{update.key}: {update.value}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def main() -> None:
|
|
135
|
+
# Replace these with your mower details
|
|
136
|
+
address = "AA:BB:CC:DD:EE:FF"
|
|
137
|
+
mainboard_serial = "12345678901234"
|
|
138
|
+
|
|
139
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
140
|
+
if ble_device is None:
|
|
141
|
+
raise RuntimeError("Mower not found")
|
|
142
|
+
|
|
143
|
+
mower = RobomowDevice(
|
|
144
|
+
mainboard_serial=mainboard_serial,
|
|
145
|
+
update_callback=on_update,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
await mower.async_connect(ble_device)
|
|
149
|
+
try:
|
|
150
|
+
await mower.async_start_mowing(duration_minutes=30)
|
|
151
|
+
await asyncio.sleep(5)
|
|
152
|
+
print("State:", mower.operating_state)
|
|
153
|
+
finally:
|
|
154
|
+
await mower.async_disconnect()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
asyncio.run(main())
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "Robomow-BLE"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Robomow BLE protocol library"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Robomow-HA contributors" }
|
|
15
|
+
]
|
|
16
|
+
keywords = ["ble", "bluetooth", "robomow", "lawn-mower", "protocol"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Natural Language :: English",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
"Topic :: System :: Hardware :: Hardware Drivers",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"attrs>=23.2.0",
|
|
30
|
+
"bleak>=0.22.0",
|
|
31
|
+
"bleak-retry-connector>=3.10.0"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
test = [
|
|
36
|
+
"pytest>=8.0"
|
|
37
|
+
]
|
|
38
|
+
docs = [
|
|
39
|
+
"pdoc>=15.0"
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/arjanmels/robomow_ble"
|
|
44
|
+
Changelog = "https://github.com/arjanmels/robomow_ble/blob/main/CHANGELOG.md"
|
|
45
|
+
Documentation = "https://arjanmels.github.io/robomow_ble/"
|
|
46
|
+
Issues = "https://github.com/arjanmels/robomow_ble/issues"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools]
|
|
49
|
+
package-dir = { "" = "src" }
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.packages.find]
|
|
52
|
+
where = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
robomow_ble = ["py.typed"]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: Robomow-BLE
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Robomow BLE protocol library
|
|
5
|
+
Author: Robomow-HA contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/arjanmels/robomow_ble
|
|
8
|
+
Project-URL: Changelog, https://github.com/arjanmels/robomow_ble/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Documentation, https://arjanmels.github.io/robomow_ble/
|
|
10
|
+
Project-URL: Issues, https://github.com/arjanmels/robomow_ble/issues
|
|
11
|
+
Keywords: ble,bluetooth,robomow,lawn-mower,protocol
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: attrs>=23.2.0
|
|
25
|
+
Requires-Dist: bleak>=0.22.0
|
|
26
|
+
Requires-Dist: bleak-retry-connector>=3.10.0
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
29
|
+
Provides-Extra: docs
|
|
30
|
+
Requires-Dist: pdoc>=15.0; extra == "docs"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Robomow-BLE
|
|
34
|
+
|
|
35
|
+
Robomow-BLE is a standalone Python library for talking to Robomow mowers over
|
|
36
|
+
Bluetooth Low Energy (BLE).
|
|
37
|
+
|
|
38
|
+
It provides a reusable BLE protocol layer for Robomow mowers and can be used
|
|
39
|
+
directly in Python applications.
|
|
40
|
+
|
|
41
|
+
Currently it only supports RT models.
|
|
42
|
+
Support for other models may be added in the future if there is demand and access to devices for testing.
|
|
43
|
+
|
|
44
|
+
## API Overview
|
|
45
|
+
|
|
46
|
+
Please refer to the [API documentation](https://arjanmels.github.io/robomow_ble/) for details.
|
|
47
|
+
|
|
48
|
+
## How to use
|
|
49
|
+
|
|
50
|
+
### Install
|
|
51
|
+
|
|
52
|
+
Install from PyPI:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m pip install Robomow-BLE
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Create a device client
|
|
59
|
+
|
|
60
|
+
Create a `RobomowDevice` with:
|
|
61
|
+
|
|
62
|
+
- `mainboard_serial`: mower mainboard serial used during authentication.
|
|
63
|
+
- `update_callback` (optional): called for each update as `RobomowUpdate(key, value)`.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from robomow_ble import RobomowDevice
|
|
68
|
+
|
|
69
|
+
mower = RobomowDevice(
|
|
70
|
+
mainboard_serial="12345678901234",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### How to find `mainboard_serial`:
|
|
75
|
+
|
|
76
|
+
1. Log in to myrobomow.robomow.com.
|
|
77
|
+
2. Open your browser's Developer Tools (F12).
|
|
78
|
+
3. Go to the Network tab.
|
|
79
|
+
4. Reload the page.
|
|
80
|
+
5. Look for a request ending in `/api/customer/products`.
|
|
81
|
+
6. Click the request and open the Response tab.
|
|
82
|
+
7. In the JSON response, find the field named `MainboardSerial`. This value is the actual mainboard serial number.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
### Connect and authenticate
|
|
86
|
+
|
|
87
|
+
Discover a BLE device with Bleak, then connect:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from bleak import BleakScanner
|
|
91
|
+
|
|
92
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
93
|
+
if ble_device is None:
|
|
94
|
+
raise RuntimeError("Mower not found")
|
|
95
|
+
|
|
96
|
+
await mower.async_connect(ble_device)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Receive updates
|
|
100
|
+
|
|
101
|
+
Pass a callback to receive state changes:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from robomow_ble import RobomowUpdate, EntityKey
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
108
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
109
|
+
print(f"Battery: {update.value}%")
|
|
110
|
+
else:
|
|
111
|
+
print(f"{update.key}: {update.value}")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Send commands
|
|
115
|
+
|
|
116
|
+
Common control methods:
|
|
117
|
+
|
|
118
|
+
- `await mower.async_start_mowing(duration_minutes=30)`
|
|
119
|
+
- `await mower.async_start_mowing_edge()`
|
|
120
|
+
- `await mower.async_stop_mowing()`
|
|
121
|
+
- `await mower.async_return_to_home()`
|
|
122
|
+
|
|
123
|
+
Common settings methods:
|
|
124
|
+
|
|
125
|
+
- `await mower.async_enable_schedule()` / `await mower.async_disable_schedule()`
|
|
126
|
+
- `await mower.async_set_schedule(schedule)`
|
|
127
|
+
- `await mower.async_enable_anti_theft()` / `await mower.async_disable_anti_theft()`
|
|
128
|
+
- `await mower.async_enable_child_lock()` / `await mower.async_disable_child_lock()`
|
|
129
|
+
- `await mower.async_set_wire_signal_type(wire_signal_type)`
|
|
130
|
+
|
|
131
|
+
### Read state
|
|
132
|
+
|
|
133
|
+
Example state properties:
|
|
134
|
+
|
|
135
|
+
- Identity/version: `family`, `model`, `mainboard_version`, `software_version`
|
|
136
|
+
- Live values: `operating_state`, `message`, `battery_level`, `rssi`
|
|
137
|
+
- Status: `schedule_enabled`, `next_departure`, `expected_duration`
|
|
138
|
+
|
|
139
|
+
### Disconnect cleanly
|
|
140
|
+
|
|
141
|
+
Always disconnect in a `finally` block:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
try:
|
|
145
|
+
...
|
|
146
|
+
finally:
|
|
147
|
+
await mower.async_disconnect()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Minimal example
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import asyncio
|
|
154
|
+
|
|
155
|
+
from bleak import BleakScanner
|
|
156
|
+
from robomow_ble import RobomowDevice, RobomowUpdate, EntityKey
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def on_update(update: RobomowUpdate) -> None:
|
|
160
|
+
if update.key == EntityKey.BATTERY_LEVEL:
|
|
161
|
+
print(f"Battery: {update.value}%")
|
|
162
|
+
else:
|
|
163
|
+
print(f"{update.key}: {update.value}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def main() -> None:
|
|
167
|
+
# Replace these with your mower details
|
|
168
|
+
address = "AA:BB:CC:DD:EE:FF"
|
|
169
|
+
mainboard_serial = "12345678901234"
|
|
170
|
+
|
|
171
|
+
ble_device = await BleakScanner.find_device_by_address(address, timeout=15.0)
|
|
172
|
+
if ble_device is None:
|
|
173
|
+
raise RuntimeError("Mower not found")
|
|
174
|
+
|
|
175
|
+
mower = RobomowDevice(
|
|
176
|
+
mainboard_serial=mainboard_serial,
|
|
177
|
+
update_callback=on_update,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
await mower.async_connect(ble_device)
|
|
181
|
+
try:
|
|
182
|
+
await mower.async_start_mowing(duration_minutes=30)
|
|
183
|
+
await asyncio.sleep(5)
|
|
184
|
+
print("State:", mower.operating_state)
|
|
185
|
+
finally:
|
|
186
|
+
await mower.async_disconnect()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
asyncio.run(main())
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/Robomow_BLE.egg-info/PKG-INFO
|
|
5
|
+
src/Robomow_BLE.egg-info/SOURCES.txt
|
|
6
|
+
src/Robomow_BLE.egg-info/dependency_links.txt
|
|
7
|
+
src/Robomow_BLE.egg-info/requires.txt
|
|
8
|
+
src/Robomow_BLE.egg-info/top_level.txt
|
|
9
|
+
src/robomow_ble/__init__.py
|
|
10
|
+
src/robomow_ble/const.py
|
|
11
|
+
src/robomow_ble/const_rt.py
|
|
12
|
+
src/robomow_ble/exceptions.py
|
|
13
|
+
src/robomow_ble/family_handler_base.py
|
|
14
|
+
src/robomow_ble/family_handler_rt.py
|
|
15
|
+
src/robomow_ble/helpers.py
|
|
16
|
+
src/robomow_ble/mower.py
|
|
17
|
+
src/robomow_ble/py.typed
|
|
18
|
+
tests/test_smoke.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
robomow_ble
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Robomow-BLE provides a reusable Bluetooth Low Energy protocol layer for
|
|
2
|
+
communicating with Robomow mowers.
|
|
3
|
+
|
|
4
|
+
The package exposes a small top-level API intended for direct use:
|
|
5
|
+
|
|
6
|
+
- ``RobomowDevice``: main client used to connect, read state, and send commands.
|
|
7
|
+
- ``RobomowUpdate``: callback payload for state changes.
|
|
8
|
+
- ``RobomowAuthenticationError`` and ``RobomowProtocolError``: protocol errors.
|
|
9
|
+
- Enums: ``MowerFamily``, ``MowerModel``, ``MowerOperatingState``,
|
|
10
|
+
``EntityKey``, ``WireSignalType``, ``Zone``.
|
|
11
|
+
- Datatypes: ``MowerSchedule``, ``MowerSchedule.Day``, ``MowerOperation``,
|
|
12
|
+
``Message``.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .const import (
|
|
16
|
+
EntityKey,
|
|
17
|
+
Message,
|
|
18
|
+
MowerFamily,
|
|
19
|
+
MowerModel,
|
|
20
|
+
MowerOperation,
|
|
21
|
+
MowerOperatingState,
|
|
22
|
+
MowerSchedule,
|
|
23
|
+
WireSignalType,
|
|
24
|
+
Zone,
|
|
25
|
+
)
|
|
26
|
+
from .exceptions import RobomowAuthenticationError, RobomowProtocolError
|
|
27
|
+
from .mower import RobomowDevice, RobomowUpdate
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"RobomowDevice",
|
|
31
|
+
"RobomowUpdate",
|
|
32
|
+
"RobomowProtocolError",
|
|
33
|
+
"RobomowAuthenticationError",
|
|
34
|
+
"Message",
|
|
35
|
+
"MowerOperation",
|
|
36
|
+
"MowerFamily",
|
|
37
|
+
"MowerModel",
|
|
38
|
+
"MowerSchedule",
|
|
39
|
+
"MowerOperatingState",
|
|
40
|
+
"EntityKey",
|
|
41
|
+
"WireSignalType",
|
|
42
|
+
"Zone",
|
|
43
|
+
]
|