Robomow-BLE 1.0.0__py3-none-any.whl
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/__init__.py +43 -0
- robomow_ble/const.py +205 -0
- robomow_ble/const_rt.py +628 -0
- robomow_ble/exceptions.py +9 -0
- robomow_ble/family_handler_base.py +104 -0
- robomow_ble/family_handler_rt.py +621 -0
- robomow_ble/helpers.py +34 -0
- robomow_ble/mower.py +1212 -0
- robomow_ble/py.typed +1 -0
- robomow_ble-1.0.0.dist-info/METADATA +193 -0
- robomow_ble-1.0.0.dist-info/RECORD +14 -0
- robomow_ble-1.0.0.dist-info/WHEEL +5 -0
- robomow_ble-1.0.0.dist-info/licenses/LICENSE +21 -0
- robomow_ble-1.0.0.dist-info/top_level.txt +1 -0
robomow_ble/__init__.py
ADDED
|
@@ -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
|
+
]
|
robomow_ble/const.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Shared constants and enums for the Robomow BLE package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import logging
|
|
7
|
+
from enum import IntEnum, StrEnum
|
|
8
|
+
|
|
9
|
+
from attr import dataclass, field
|
|
10
|
+
|
|
11
|
+
LOGGER: logging.Logger = logging.getLogger(__package__)
|
|
12
|
+
|
|
13
|
+
MAINBOARD_SERIAL_LENGTH = 14
|
|
14
|
+
AUTH_RESPONSE_LENGTH = 15
|
|
15
|
+
|
|
16
|
+
MINIMUM_MESSAGE_LENGTH = 4
|
|
17
|
+
MESSAGE_START_BYTE = 0xAA
|
|
18
|
+
MESSAGE_SEND_BYTE = 0x1F
|
|
19
|
+
MESSAGE_RECEIVE_BYTE = 0x1E
|
|
20
|
+
|
|
21
|
+
UUID_SERVICE = "ff00a501-d020-913c-1234-56d97200a6a6"
|
|
22
|
+
UUID_CHAR_AUTHENTICATE = "ff00a502-d020-913c-1234-56d97200a6a6"
|
|
23
|
+
UUID_CHAR_DATA_OUT = "ff00a503-d020-913c-1234-56d97200a6a6"
|
|
24
|
+
UUID_CHAR_DATA_IN = "ff00a506-d020-913c-1234-56d97200a6a6"
|
|
25
|
+
|
|
26
|
+
UNKNOWN_FIELD_VALUE = 0xFFFF
|
|
27
|
+
|
|
28
|
+
class MessageType(IntEnum):
|
|
29
|
+
"""Basic message types used in packet payloads."""
|
|
30
|
+
|
|
31
|
+
ACKNOWLEDGE = 0x04
|
|
32
|
+
CLEAR_USER_MESSAGE = 0x0E
|
|
33
|
+
GET_CONFIG = 0x0F
|
|
34
|
+
COMMAND = 0x15
|
|
35
|
+
MISCELLANEOUS = 0x16
|
|
36
|
+
GET_MESSAGE = 0x1B
|
|
37
|
+
UPDATE_DATE_TIME = 0x1D
|
|
38
|
+
WRITE_EEPROM = 0x1F
|
|
39
|
+
READ_EEPROM = 0x20
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class WireSignalType(IntEnum):
|
|
43
|
+
"""Wire signal types used in EEPROM parameters."""
|
|
44
|
+
|
|
45
|
+
TYPE_A = 0x00
|
|
46
|
+
TYPE_B = 0x01
|
|
47
|
+
TYPE_C = 0x02
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Zone(IntEnum):
|
|
51
|
+
"""Robomow mower zones."""
|
|
52
|
+
|
|
53
|
+
MAIN = 0
|
|
54
|
+
STARTING_POINT_A = 1
|
|
55
|
+
STARTING_POINT_B = 2
|
|
56
|
+
SUB_1 = 3
|
|
57
|
+
SUB_2 = 4
|
|
58
|
+
SUB_3 = 5
|
|
59
|
+
SUB_4 = 6
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MowerFamily(IntEnum):
|
|
63
|
+
"""Robomow mower types."""
|
|
64
|
+
|
|
65
|
+
Unknown = -1
|
|
66
|
+
RS = 1
|
|
67
|
+
RC = 2
|
|
68
|
+
RX = 3
|
|
69
|
+
RK = 4
|
|
70
|
+
RT = 5
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MowerModel(IntEnum):
|
|
74
|
+
"""Robomow mower models."""
|
|
75
|
+
|
|
76
|
+
Unknown = -1
|
|
77
|
+
RT300 = 5
|
|
78
|
+
RT500 = 6
|
|
79
|
+
RT700 = 7
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class MowerSchedule:
|
|
84
|
+
"""Data class representing a Robomow mowing schedule.
|
|
85
|
+
|
|
86
|
+
Attributes:
|
|
87
|
+
start_time: Start of the daily mowing window.
|
|
88
|
+
end_time: End of the daily mowing window.
|
|
89
|
+
day: Seven-day schedule entries, ordered Monday to Sunday.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class Day:
|
|
94
|
+
"""Data class representing a day in the mowing schedule.
|
|
95
|
+
|
|
96
|
+
Attributes:
|
|
97
|
+
enabled: Whether mowing is enabled for the day.
|
|
98
|
+
cycles: Number of mowing cycles scheduled for the day.
|
|
99
|
+
zone: Zone to mow during the schedule entry.
|
|
100
|
+
duration: Mowing duration in minutes.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
enabled: bool = True
|
|
104
|
+
cycles: int = 1
|
|
105
|
+
zone: Zone = Zone.MAIN
|
|
106
|
+
duration: int = 30
|
|
107
|
+
|
|
108
|
+
start_time: datetime.time = datetime.time(hour=9, minute=0)
|
|
109
|
+
end_time: datetime.time = datetime.time(hour=21, minute=0)
|
|
110
|
+
day: tuple[Day, ...] = field(
|
|
111
|
+
factory=lambda: tuple(MowerSchedule.Day() for _ in range(7))
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MowerOperatingState(StrEnum):
|
|
116
|
+
"""Human-readable Robomow operating states."""
|
|
117
|
+
|
|
118
|
+
WARMING_UP = "Warming up"
|
|
119
|
+
MOWING = "Mowing"
|
|
120
|
+
EDGE_MOWING = "Edge Mowing"
|
|
121
|
+
RETURNING_HOME_FOLLOWING_EDGE = "Following edge home"
|
|
122
|
+
RETURNING_HOME_WARMING_UP = "Warming up to return home"
|
|
123
|
+
RETURNING_HOME_SEARCHING_EDGE = "Searching edge"
|
|
124
|
+
|
|
125
|
+
GOING_TO_START = "Going to starting point"
|
|
126
|
+
LEARNING_ENTRY_POINT = "Learning entry point"
|
|
127
|
+
IDLE = "Idle"
|
|
128
|
+
CHARGING = "Charging"
|
|
129
|
+
AUTOMATIC = "Automatic"
|
|
130
|
+
REMOTE_CONTROL = "Remote control"
|
|
131
|
+
BIT = "Bit"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class EntityKey(StrEnum):
|
|
135
|
+
"""Entity keys for Robomow entities."""
|
|
136
|
+
|
|
137
|
+
LAWN_MOWER = "lawn_mower"
|
|
138
|
+
BATTERY_LEVEL = "battery_level"
|
|
139
|
+
FAMILY = "family"
|
|
140
|
+
MODEL = "model"
|
|
141
|
+
SOFTWARE_VERSION = "software_version"
|
|
142
|
+
SOFTWARE_RELEASE = "software_release"
|
|
143
|
+
MAINBOARD_VERSION = "mainboard_version"
|
|
144
|
+
STATE = "state"
|
|
145
|
+
MESSAGE = "message"
|
|
146
|
+
SIGNAL_STRENGTH = "signal_strength"
|
|
147
|
+
START_MOWING = "async_start_mowing"
|
|
148
|
+
STOP_MOWING = "async_stop_mowing"
|
|
149
|
+
RETURN_HOME = "return_home"
|
|
150
|
+
EDGE_MOWING = "edge_mowing"
|
|
151
|
+
SCHEDULE_ENABLED = "schedule_enabled"
|
|
152
|
+
SCHEDULE = "schedule"
|
|
153
|
+
SERVICE_INFO = "service_info"
|
|
154
|
+
NEXT_DEPARTURE = "next_departure"
|
|
155
|
+
PREVIOUS_DEPARTURE = "previous_departure"
|
|
156
|
+
EXPECTED_DURATION = "expected_duration"
|
|
157
|
+
NO_DEPART_REASON = "no_depart_reason"
|
|
158
|
+
ANTI_THEFT_ENABLED = "anti_theft_enabled"
|
|
159
|
+
CHILD_LOCK_ENABLED = "child_lock_enabled"
|
|
160
|
+
ANTI_THEFT_ACTIVE = "anti_theft_active"
|
|
161
|
+
MOWER_HOME = "mower_home"
|
|
162
|
+
CHARGING_ACTIVE = "charging_active"
|
|
163
|
+
DISABLING_DEVICE_REMOVED = "disabling_device_removed"
|
|
164
|
+
WIRE_SIGNAL_TYPE = "wire_signal_type"
|
|
165
|
+
STARTING_POINT_A = "starting_point_a"
|
|
166
|
+
STARTING_POINT_B = "starting_point_b"
|
|
167
|
+
LAST_OPERATIONS = "last_operations"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@dataclass
|
|
171
|
+
class MowerOperation:
|
|
172
|
+
"""A single parsed mower operation history entry.
|
|
173
|
+
|
|
174
|
+
Attributes:
|
|
175
|
+
id: Operation record identifier.
|
|
176
|
+
start_time: Start time of the operation.
|
|
177
|
+
duration: Operation duration in minutes.
|
|
178
|
+
zone: Zone used for the operation.
|
|
179
|
+
error: Message describing the operation result.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
id: int
|
|
183
|
+
start_time: datetime.datetime
|
|
184
|
+
duration: int
|
|
185
|
+
zone: Zone
|
|
186
|
+
error: Message
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(frozen=True)
|
|
190
|
+
class Message:
|
|
191
|
+
"""Base class for user-facing messages with optional title and text.
|
|
192
|
+
|
|
193
|
+
Attributes:
|
|
194
|
+
title: Short human-readable message title.
|
|
195
|
+
text: Optional longer explanatory text.
|
|
196
|
+
number: Optional numeric code used by the mower protocol.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
title: str
|
|
200
|
+
text: str | None = None
|
|
201
|
+
number: int | None = None
|
|
202
|
+
|
|
203
|
+
def __str__(self) -> str:
|
|
204
|
+
"""Return a user-friendly string representation of the message."""
|
|
205
|
+
return f"{self.title} - {self.text}" if self.text else self.title
|