Skip to content

Commit 841426c

Browse files
author
NOisi-X
committed
fix: revert Boolean wire format to string, keep __init__ variable name
1 parent d5923ae commit 841426c

3 files changed

Lines changed: 24 additions & 23 deletions

File tree

roborock/devices/traits/a01/__init__.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171

7272
_LOGGER = logging.getLogger(__name__)
7373

74-
__all__ = [
74+
__init__ = [
7575
"DyadApi",
7676
"ZeoApi",
7777
"ZeoFeatureTrait",
@@ -119,16 +119,17 @@ def _decode_expansion_type(val: Any, default: int) -> int:
119119
return int(val) if val is not None else default
120120

121121

122-
def to_dp_bool(val: Any) -> int:
123-
"""Normalise a boolean-like value to the wire-format integer ``1`` or ``0``.
122+
def to_dp_bool(val: Any) -> str:
123+
"""Normalise a boolean-like value to the wire-format string ``"True"`` or ``"False"``.
124124
125-
Used only in the ``set_value`` encoder path because callers (HA switch
126-
entities, external code) may pass Python ``True`` / ``False`` which
127-
``json.dumps`` would serialise as JSON ``true`` / ``false`` — not the
128-
integer ``1`` / ``0`` that the device expects. Cache‑reading paths
129-
do NOT need this: MQTT push already delivers integers.
125+
The official app serialises booleans as the strings ``"True"`` /
126+
``"False"`` (its ``DPBoolean`` enum) on SET commands — not as ``1`` / ``0``
127+
or JSON ``true`` / ``false``. Used only in the ``set_value`` encoder
128+
path because callers (HA switch entities, external code) may pass
129+
Python ``True`` / ``False`` which ``json.dumps`` would serialise as
130+
JSON ``true`` / ``false``.
130131
"""
131-
return 1 if parse_bool(val) else 0
132+
return "True" if parse_bool(val) else "False"
132133

133134

134135
DYAD_PROTOCOL_ENTRIES: dict[RoborockDyadDataProtocol, Callable] = {
@@ -687,13 +688,13 @@ async def start(self) -> dict[RoborockZeoProtocol, Any]:
687688
then call ``start()`` to commit and launch.
688689
689690
For pausing / resuming, use :meth:`resume` which sends only
690-
``START = 1`` without re‑bundling parameters."""
691+
``START = "True"`` without re‑bundling parameters."""
691692
_LOGGER.debug("Start command: discovering features and building payload")
692693
await self._feature_trait.refresh()
693694
features = self._feature_trait.features
694695
p = await self._get_start_params()
695696
dps: dict[RoborockZeoProtocol, Any] = {
696-
RoborockZeoProtocol.START: 1,
697+
RoborockZeoProtocol.START: "True",
697698
RoborockZeoProtocol.MODE: p.mode,
698699
RoborockZeoProtocol.PROGRAM: p.program,
699700
}
@@ -722,7 +723,7 @@ async def start(self) -> dict[RoborockZeoProtocol, Any]:
722723
return await send_decoded_command(self._channel, dps, value_encoder=lambda x: x, qos=MqttQos.AT_LEAST_ONCE)
723724

724725
async def resume(self) -> dict[RoborockZeoProtocol, Any]:
725-
"""Resume a paused cycle — sends only ``START = 1``.
726+
"""Resume a paused cycle — sends only ``START = "True"``.
726727
727728
Matches the official app's ``continue()`` / ``start()``
728729
(simple single-DP version). Unlike :meth:`start`, this does
@@ -731,7 +732,7 @@ async def resume(self) -> dict[RoborockZeoProtocol, Any]:
731732
progress. ``continue`` is a Python keyword so the method is
732733
named ``resume``.
733734
"""
734-
dps: dict[RoborockZeoProtocol, Any] = {RoborockZeoProtocol.START: 1}
735+
dps: dict[RoborockZeoProtocol, Any] = {RoborockZeoProtocol.START: "True"}
735736
return await send_decoded_command(self._channel, dps, value_encoder=lambda x: x, qos=MqttQos.AT_LEAST_ONCE)
736737

737738
# ── Custom programme (DP 222 bitfield) ──────────────────────────
@@ -951,7 +952,7 @@ async def start_with_preset(self, countdown_minutes: int) -> dict[RoborockZeoPro
951952
features = self._feature_trait.features
952953
p = await self._get_start_params()
953954
dps: dict[RoborockZeoProtocol, Any] = {
954-
RoborockZeoProtocol.START: 1,
955+
RoborockZeoProtocol.START: "True",
955956
RoborockZeoProtocol.MODE: p.mode,
956957
RoborockZeoProtocol.PROGRAM: p.program,
957958
}

roborock/roborock_message.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class RoborockDyadDataProtocol(RoborockEnum):
126126

127127
class RoborockZeoProtocol(RoborockEnum):
128128
# ── Control actions ───────────────────────────────────────────
129-
START = 200 # rw [action → start()] set_value(START,1) triggers bundled start()
129+
START = 200 # rw [action → start()] set_value(START,"True") triggers bundled start()
130130
PAUSE = 201 # rw
131131
SHUTDOWN = 202 # rw
132132

tests/devices/test_a01_code_review.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_parse_bool_trap_is_avoided():
7070

7171
@pytest.mark.parametrize(
7272
("val", "expected"),
73-
[(True, 1), (False, 0), (1, 1), (0, 0), ("True", 1), ("False", 0)],
73+
[(True, "True"), (False, "False"), (1, "True"), (0, "False"), ("True", "True"), ("False", "False")],
7474
)
7575
def test_to_dp_bool(val, expected):
7676
assert to_dp_bool(val) == expected, f"to_dp_bool({val!r}) should be {expected}"
@@ -178,10 +178,10 @@ def test_zeo_features_all_bits_clear_decodes_all_false():
178178

179179

180180
@pytest.mark.asyncio
181-
async def test_zeo_set_value_bool_serialises_as_int(mock_channel):
181+
async def test_zeo_set_value_bool_serialises_as_string(mock_channel):
182182
with patch("roborock.devices.traits.a01.send_decoded_command", new_callable=AsyncMock) as mock_send:
183183
api = ZeoApi(mock_channel)
184-
for py_val, wire in ((True, 1), (False, 0), (1, 1), (0, 0)):
184+
for py_val, wire in ((True, "True"), (False, "False"), (1, "True"), (0, "False")):
185185
await api.set_value(RoborockZeoProtocol.UV_LIGHT, py_val)
186186
args, kwargs = mock_send.call_args
187187
params = args[1]
@@ -192,20 +192,20 @@ async def test_zeo_set_value_bool_serialises_as_int(mock_channel):
192192

193193

194194
@pytest.mark.asyncio
195-
async def test_dyad_set_value_bool_serialises_as_int(mock_channel):
195+
async def test_dyad_set_value_bool_serialises_as_string(mock_channel):
196196
with patch("roborock.devices.traits.a01.send_decoded_command", new_callable=AsyncMock) as mock_send:
197197
api = DyadApi(mock_channel)
198198
await api.set_value(RoborockDyadDataProtocol.SILENT_MODE, True)
199199
args, kwargs = mock_send.call_args
200200
params = args[1]
201201
encoder = kwargs.get("value_encoder")
202202
encoded = {k: (encoder(v) if encoder else v) for k, v in params.items()}
203-
assert encoded[RoborockDyadDataProtocol.SILENT_MODE] == 1
203+
assert encoded[RoborockDyadDataProtocol.SILENT_MODE] == "True"
204204

205205

206206
@pytest.mark.asyncio
207-
async def test_zeo_start_sends_int_and_bundles_core_params(mock_channel):
208-
"""start() must send START=1 (int), not a string or JSON boolean."""
207+
async def test_zeo_start_sends_true_and_bundles_core_params(mock_channel):
208+
"""start() must send START="True" (DPBoolean.True), not an integer."""
209209
mock_channel.subscribe = AsyncMock(return_value=Mock())
210210

211211
captured = []
@@ -244,7 +244,7 @@ async def _side_effect(channel, params, **kwargs):
244244
]
245245
assert set_calls, "start() should issue a SET command"
246246
start_params = set_calls[-1]
247-
assert start_params[RoborockZeoProtocol.START] == 1
247+
assert start_params[RoborockZeoProtocol.START] == "True"
248248
# Core numeric params travel as integers, not strings.
249249
assert start_params[RoborockZeoProtocol.MODE] == 1
250250
assert start_params[RoborockZeoProtocol.PROGRAM] == 1

0 commit comments

Comments
 (0)