midea-dishwasher-api 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.
Files changed (30) hide show
  1. midea_dishwasher_api-1.0.0/.gitignore +9 -0
  2. midea_dishwasher_api-1.0.0/LICENSE +21 -0
  3. midea_dishwasher_api-1.0.0/PKG-INFO +146 -0
  4. midea_dishwasher_api-1.0.0/README.md +98 -0
  5. midea_dishwasher_api-1.0.0/pyproject.toml +52 -0
  6. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/__init__.py +63 -0
  7. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/client.py +41 -0
  8. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/__init__.py +17 -0
  9. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/bright_level.py +13 -0
  10. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/cycle_state.py +28 -0
  11. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/error_code.py +20 -0
  12. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/machine_state.py +12 -0
  13. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/mode.py +52 -0
  14. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/msg_type.py +11 -0
  15. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/enums/wash_stage.py +21 -0
  16. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/protocol/__init__.py +29 -0
  17. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/protocol/codec.py +124 -0
  18. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/protocol/frame_error.py +5 -0
  19. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/security/__init__.py +44 -0
  20. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/security/crypto.py +67 -0
  21. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/security/security.py +163 -0
  22. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/security/v2.py +69 -0
  23. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/security/v3_error.py +5 -0
  24. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/state/__init__.py +4 -0
  25. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/state/decoder.py +64 -0
  26. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/state/dishwasher_status.py +20 -0
  27. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/transport/__init__.py +3 -0
  28. midea_dishwasher_api-1.0.0/src/midea_dishwasher_api/transport/v3_transport.py +140 -0
  29. midea_dishwasher_api-1.0.0/tests/test_protocol.py +140 -0
  30. midea_dishwasher_api-1.0.0/tests/test_security.py +162 -0
@@ -0,0 +1,9 @@
1
+ /resources/
2
+ .env
3
+ .venv/
4
+ __pycache__/
5
+ *.pyc
6
+ .pytest_cache/
7
+ *.egg-info/
8
+ build/
9
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rodrigo Roque
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,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: midea-dishwasher-api
3
+ Version: 1.0.0
4
+ Summary: Cliente Python para lava-louças Midea (device_type 0xE1, plugin v5) — codec do frame AA + transporte LAN V3.
5
+ Project-URL: Homepage, https://github.com/roquerodrigo/midea-dishwasher-api
6
+ Project-URL: Repository, https://github.com/roquerodrigo/midea-dishwasher-api
7
+ Project-URL: Issues, https://github.com/roquerodrigo/midea-dishwasher-api/issues
8
+ Author-email: Rodrigo Roque <rodrigogoncalvesroque@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Rodrigo Roque
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: dishwasher,iot,lan,midea,smart-home
32
+ Classifier: Development Status :: 5 - Production/Stable
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: OS Independent
36
+ Classifier: Programming Language :: Python
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.14
39
+ Classifier: Topic :: Home Automation
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Classifier: Typing :: Typed
42
+ Requires-Python: >=3.14
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8; extra == 'dev'
45
+ Provides-Extra: lan
46
+ Requires-Dist: cryptography>=42; extra == 'lan'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # midea-dishwasher-api
50
+
51
+ Cliente Python para lava-louças Midea (`device_type 0xE1`, plugin v5).
52
+
53
+ Implementa o protocolo de aplicação `AA … E1` e a camada de transporte LAN V3
54
+ (handshake 8370 + AES-128-CBC + SHA-256, com framing V2 5A5A interno).
55
+
56
+ ## Instalação
57
+
58
+ ```bash
59
+ pip install midea-dishwasher-api[lan]
60
+ ```
61
+
62
+ O extra `lan` instala `cryptography`, necessário para falar diretamente com a
63
+ máquina via LAN. Sem o extra, o pacote ainda expõe o codec do frame e o
64
+ parser de status para uso com transporte próprio (cloud, mock, etc.).
65
+
66
+ ## Uso rápido
67
+
68
+ ```python
69
+ from midea_dishwasher_api import BrightLevel, Client, Mode, V3Transport
70
+
71
+ with V3Transport(
72
+ host="192.168.5.100",
73
+ device_id=151732606394621,
74
+ token=bytes.fromhex("..."), # 64 bytes
75
+ key=bytes.fromhex("..."), # 32 bytes
76
+ ) as transport:
77
+ client = Client(send=transport)
78
+
79
+ status = client.query_status()
80
+ print(status.machine_state) # MachineState.POWER_ON / POWER_OFF
81
+ print(status.cycle_state) # CycleState.IDLE / WORK / ORDER / ...
82
+ print(status.left_time) # minutos restantes (apenas em WORK)
83
+ print(status.door_closed)
84
+ print(status.bright_lack) # secante acabou?
85
+
86
+ client.power_on()
87
+ client.start_to_work(mode=Mode.ECO, extra_drying=True)
88
+ client.set_bright(BrightLevel.L3)
89
+ client.cancel_work()
90
+ client.power_off()
91
+ ```
92
+
93
+ Os métodos de controle não retornam estado (a máquina demora alguns segundos
94
+ para refletir a mudança). Chame `query_status()` quando quiser estado fresco.
95
+
96
+ ## API
97
+
98
+ ### Client
99
+
100
+ | Método | Efeito |
101
+ |---|---|
102
+ | `query_status() -> DishwasherStatus` | Lê o estado atual |
103
+ | `power_on()` | Liga a máquina |
104
+ | `power_off()` | Desliga |
105
+ | `cancel_work()` | Cancela ciclo / volta ao idle |
106
+ | `start_to_work(mode, extra_drying=False)` | Inicia ciclo |
107
+ | `set_bright(level: BrightLevel)` | Ajusta o nível do abrilhantador (1–5) |
108
+
109
+ ### DishwasherStatus
110
+
111
+ Campos decodificados da resposta:
112
+
113
+ - `machine_state: MachineState | None` — `POWER_ON` / `POWER_OFF`
114
+ - `cycle_state: CycleState | None` — `idle`, `order`, `work`, `error`, ...
115
+ - `wash_stage: WashStage | int | None` — `IDLE`, `PRE_WASH`, `MAIN_WASH`, `RINSE`, `DRY`, `FINISH`
116
+ - `error_code: ErrorCode | int` — `NONE`, `WATER_SUPPLY`, `HEATING`, `OVERFLOW`, `WATER_VALVE`
117
+ - `left_time: int | None` — minutos restantes (preenchido apenas quando `cycle_state == WORK`)
118
+ - `door_closed: bool`
119
+ - `bright_lack: bool` — secante (rinse aid) acabou
120
+
121
+ ### Modos disponíveis (`Mode`)
122
+
123
+ `AUTO`, `INTENSIVE`, `NORMAL`, `ECO`, `GLASS`, `NINETY_MIN`, `ONE_HOUR`,
124
+ `RAPID`, `SOAK`, `THREE_IN_ONE`, `HYGIENE`, `QUIET`, `PARTY`, `FRUIT`.
125
+
126
+ ## Transporte customizado
127
+
128
+ `Client` aceita qualquer `Callable[[bytes], bytes]` como `send`. Útil para
129
+ testes com transporte mock, integração com cloud, ou pipeline próprio:
130
+
131
+ ```python
132
+ def fake_send(frame: bytes) -> bytes:
133
+ return assemble_frame(b"...", 0x02)
134
+
135
+ client = Client(send=fake_send)
136
+ ```
137
+
138
+ ## Como obter `token` e `key`
139
+
140
+ São credenciais por dispositivo emitidas pela cloud da Midea. Use ferramentas
141
+ existentes (`midea-msmart`, `midea-beautiful-air`, `midea-discover`) para
142
+ extrair a partir da sua conta no app.
143
+
144
+ ## Licença
145
+
146
+ MIT — ver `LICENSE`.
@@ -0,0 +1,98 @@
1
+ # midea-dishwasher-api
2
+
3
+ Cliente Python para lava-louças Midea (`device_type 0xE1`, plugin v5).
4
+
5
+ Implementa o protocolo de aplicação `AA … E1` e a camada de transporte LAN V3
6
+ (handshake 8370 + AES-128-CBC + SHA-256, com framing V2 5A5A interno).
7
+
8
+ ## Instalação
9
+
10
+ ```bash
11
+ pip install midea-dishwasher-api[lan]
12
+ ```
13
+
14
+ O extra `lan` instala `cryptography`, necessário para falar diretamente com a
15
+ máquina via LAN. Sem o extra, o pacote ainda expõe o codec do frame e o
16
+ parser de status para uso com transporte próprio (cloud, mock, etc.).
17
+
18
+ ## Uso rápido
19
+
20
+ ```python
21
+ from midea_dishwasher_api import BrightLevel, Client, Mode, V3Transport
22
+
23
+ with V3Transport(
24
+ host="192.168.5.100",
25
+ device_id=151732606394621,
26
+ token=bytes.fromhex("..."), # 64 bytes
27
+ key=bytes.fromhex("..."), # 32 bytes
28
+ ) as transport:
29
+ client = Client(send=transport)
30
+
31
+ status = client.query_status()
32
+ print(status.machine_state) # MachineState.POWER_ON / POWER_OFF
33
+ print(status.cycle_state) # CycleState.IDLE / WORK / ORDER / ...
34
+ print(status.left_time) # minutos restantes (apenas em WORK)
35
+ print(status.door_closed)
36
+ print(status.bright_lack) # secante acabou?
37
+
38
+ client.power_on()
39
+ client.start_to_work(mode=Mode.ECO, extra_drying=True)
40
+ client.set_bright(BrightLevel.L3)
41
+ client.cancel_work()
42
+ client.power_off()
43
+ ```
44
+
45
+ Os métodos de controle não retornam estado (a máquina demora alguns segundos
46
+ para refletir a mudança). Chame `query_status()` quando quiser estado fresco.
47
+
48
+ ## API
49
+
50
+ ### Client
51
+
52
+ | Método | Efeito |
53
+ |---|---|
54
+ | `query_status() -> DishwasherStatus` | Lê o estado atual |
55
+ | `power_on()` | Liga a máquina |
56
+ | `power_off()` | Desliga |
57
+ | `cancel_work()` | Cancela ciclo / volta ao idle |
58
+ | `start_to_work(mode, extra_drying=False)` | Inicia ciclo |
59
+ | `set_bright(level: BrightLevel)` | Ajusta o nível do abrilhantador (1–5) |
60
+
61
+ ### DishwasherStatus
62
+
63
+ Campos decodificados da resposta:
64
+
65
+ - `machine_state: MachineState | None` — `POWER_ON` / `POWER_OFF`
66
+ - `cycle_state: CycleState | None` — `idle`, `order`, `work`, `error`, ...
67
+ - `wash_stage: WashStage | int | None` — `IDLE`, `PRE_WASH`, `MAIN_WASH`, `RINSE`, `DRY`, `FINISH`
68
+ - `error_code: ErrorCode | int` — `NONE`, `WATER_SUPPLY`, `HEATING`, `OVERFLOW`, `WATER_VALVE`
69
+ - `left_time: int | None` — minutos restantes (preenchido apenas quando `cycle_state == WORK`)
70
+ - `door_closed: bool`
71
+ - `bright_lack: bool` — secante (rinse aid) acabou
72
+
73
+ ### Modos disponíveis (`Mode`)
74
+
75
+ `AUTO`, `INTENSIVE`, `NORMAL`, `ECO`, `GLASS`, `NINETY_MIN`, `ONE_HOUR`,
76
+ `RAPID`, `SOAK`, `THREE_IN_ONE`, `HYGIENE`, `QUIET`, `PARTY`, `FRUIT`.
77
+
78
+ ## Transporte customizado
79
+
80
+ `Client` aceita qualquer `Callable[[bytes], bytes]` como `send`. Útil para
81
+ testes com transporte mock, integração com cloud, ou pipeline próprio:
82
+
83
+ ```python
84
+ def fake_send(frame: bytes) -> bytes:
85
+ return assemble_frame(b"...", 0x02)
86
+
87
+ client = Client(send=fake_send)
88
+ ```
89
+
90
+ ## Como obter `token` e `key`
91
+
92
+ São credenciais por dispositivo emitidas pela cloud da Midea. Use ferramentas
93
+ existentes (`midea-msmart`, `midea-beautiful-air`, `midea-discover`) para
94
+ extrair a partir da sua conta no app.
95
+
96
+ ## Licença
97
+
98
+ MIT — ver `LICENSE`.
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "midea-dishwasher-api"
3
+ version = "1.0.0"
4
+ description = "Cliente Python para lava-louças Midea (device_type 0xE1, plugin v5) — codec do frame AA + transporte LAN V3."
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ requires-python = ">=3.14"
8
+ authors = [
9
+ { name = "Rodrigo Roque", email = "rodrigogoncalvesroque@gmail.com" },
10
+ ]
11
+ keywords = ["midea", "dishwasher", "iot", "smart-home", "lan"]
12
+ classifiers = [
13
+ "Development Status :: 5 - Production/Stable",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Topic :: Home Automation",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = []
25
+
26
+ [project.optional-dependencies]
27
+ lan = ["cryptography>=42"]
28
+ dev = ["pytest>=8"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/roquerodrigo/midea-dishwasher-api"
32
+ Repository = "https://github.com/roquerodrigo/midea-dishwasher-api"
33
+ Issues = "https://github.com/roquerodrigo/midea-dishwasher-api/issues"
34
+
35
+ [build-system]
36
+ requires = ["hatchling"]
37
+ build-backend = "hatchling.build"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/midea_dishwasher_api"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = [
44
+ "src/midea_dishwasher_api",
45
+ "tests",
46
+ "README.md",
47
+ "LICENSE",
48
+ "pyproject.toml",
49
+ ]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
@@ -0,0 +1,63 @@
1
+ """Cliente Python para a lava-louças Midea (device type `0xE1`)."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from .client import Client
7
+ from .enums import (
8
+ BrightLevel,
9
+ CycleState,
10
+ ErrorCode,
11
+ MachineState,
12
+ Mode,
13
+ MsgType,
14
+ WashStage,
15
+ )
16
+ from .protocol import (
17
+ ControlPayload,
18
+ FrameError,
19
+ assemble_frame,
20
+ build_control,
21
+ build_query,
22
+ parse_frame,
23
+ )
24
+ from .state import DishwasherStatus, decode_response
25
+
26
+ if TYPE_CHECKING:
27
+ from .transport import OnWireCallback, V3Transport
28
+
29
+ try:
30
+ __version__ = version("midea-dishwasher-api")
31
+ except PackageNotFoundError:
32
+ __version__ = "0.0.0+local"
33
+
34
+ __all__ = [
35
+ "BrightLevel",
36
+ "Client",
37
+ "ControlPayload",
38
+ "CycleState",
39
+ "DishwasherStatus",
40
+ "ErrorCode",
41
+ "FrameError",
42
+ "MachineState",
43
+ "Mode",
44
+ "MsgType",
45
+ "OnWireCallback",
46
+ "V3Transport",
47
+ "WashStage",
48
+ "__version__",
49
+ "assemble_frame",
50
+ "build_control",
51
+ "build_query",
52
+ "decode_response",
53
+ "parse_frame",
54
+ ]
55
+
56
+ _LAZY_TRANSPORT_EXPORTS = {"V3Transport", "OnWireCallback"}
57
+
58
+
59
+ def __getattr__(name: str) -> Any:
60
+ if name in _LAZY_TRANSPORT_EXPORTS:
61
+ from . import transport
62
+ return getattr(transport, name)
63
+ raise AttributeError(f"module 'midea_dishwasher_api' has no attribute {name!r}")
@@ -0,0 +1,41 @@
1
+ """Cliente de alto nível: cada método replica uma operação `$api(...)` do app."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable
6
+
7
+ from .enums import BrightLevel, Mode
8
+ from .protocol import ControlPayload, build_control, build_query
9
+ from .state import DishwasherStatus, decode_response
10
+
11
+ Send = Callable[[bytes], bytes]
12
+
13
+
14
+ class Client:
15
+ def __init__(self, send: Send) -> None:
16
+ self._send: Send = send
17
+
18
+ def query_status(self) -> DishwasherStatus:
19
+ return decode_response(self._send(build_query()))
20
+
21
+ def power_on(self) -> None:
22
+ self._control({"machine_state": "power_on"})
23
+
24
+ def power_off(self) -> None:
25
+ self._control({"machine_state": "power_off"})
26
+
27
+ def cancel_work(self) -> None:
28
+ self._control({"machine_state": "cancel"})
29
+
30
+ def start_to_work(self, mode: Mode, extra_drying: bool = False) -> None:
31
+ self._control({
32
+ "mode": str(mode),
33
+ "machine_state": "work",
34
+ "additional": 1 if extra_drying else 0,
35
+ })
36
+
37
+ def set_bright(self, level: BrightLevel) -> None:
38
+ self._control({"bright": int(BrightLevel(level))})
39
+
40
+ def _control(self, payload: ControlPayload) -> None:
41
+ self._send(build_control(payload))
@@ -0,0 +1,17 @@
1
+ from .bright_level import BrightLevel
2
+ from .cycle_state import CycleState
3
+ from .error_code import ErrorCode
4
+ from .machine_state import MachineState
5
+ from .mode import Mode
6
+ from .msg_type import MsgType
7
+ from .wash_stage import WashStage
8
+
9
+ __all__ = [
10
+ "BrightLevel",
11
+ "CycleState",
12
+ "ErrorCode",
13
+ "MachineState",
14
+ "Mode",
15
+ "MsgType",
16
+ "WashStage",
17
+ ]
@@ -0,0 +1,13 @@
1
+ """Nível do abrilhantador (rinse aid), 1 a 5."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import IntEnum
6
+
7
+
8
+ class BrightLevel(IntEnum):
9
+ L1 = 1
10
+ L2 = 2
11
+ L3 = 3
12
+ L4 = 4
13
+ L5 = 5
@@ -0,0 +1,28 @@
1
+ """Estado atual do ciclo (byte 1 da resposta de status)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+
8
+ class CycleState(StrEnum):
9
+ POWER_OFF = "power_off"
10
+ IDLE = "idle"
11
+ ORDER = "order"
12
+ WORK = "work"
13
+ ERROR = "error"
14
+ SOFT_GEAR = "soft_gear"
15
+
16
+ @classmethod
17
+ def from_byte(cls, byte: int) -> "CycleState | None":
18
+ return _BYTE_TO_CYCLE_STATE.get(byte)
19
+
20
+
21
+ _BYTE_TO_CYCLE_STATE: dict[int, CycleState] = {
22
+ 0x00: CycleState.POWER_OFF,
23
+ 0x01: CycleState.IDLE,
24
+ 0x02: CycleState.ORDER,
25
+ 0x03: CycleState.WORK,
26
+ 0x04: CycleState.ERROR,
27
+ 0x05: CycleState.SOFT_GEAR,
28
+ }
@@ -0,0 +1,20 @@
1
+ """Código de falha reportado pela máquina (byte 10 da resposta)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import IntEnum
6
+
7
+
8
+ class ErrorCode(IntEnum):
9
+ NONE = 0
10
+ WATER_SUPPLY = 1
11
+ HEATING = 2
12
+ OVERFLOW = 3
13
+ WATER_VALVE = 4
14
+
15
+ @classmethod
16
+ def from_byte(cls, byte: int) -> "ErrorCode | int":
17
+ try:
18
+ return cls(byte)
19
+ except ValueError:
20
+ return byte
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+
5
+
6
+ class MachineState(StrEnum):
7
+ POWER_ON = "power_on"
8
+ POWER_OFF = "power_off"
9
+
10
+ @classmethod
11
+ def from_byte(cls, byte: int) -> "MachineState":
12
+ return cls.POWER_OFF if byte == 0x00 else cls.POWER_ON
@@ -0,0 +1,52 @@
1
+ """Modos de lavagem suportados pela máquina (byte 2 do controle 0x08)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+
7
+
8
+ class Mode(StrEnum):
9
+ AUTO = "auto"
10
+ INTENSIVE = "intensive"
11
+ NORMAL = "normal"
12
+ ECO = "eco"
13
+ GLASS = "glass"
14
+ NINETY_MIN = "90min"
15
+ ONE_HOUR = "1hour"
16
+ RAPID = "rapid"
17
+ SOAK = "soak"
18
+ THREE_IN_ONE = "3in1"
19
+ HYGIENE = "hygiene"
20
+ QUIET = "quiet"
21
+ PARTY = "party"
22
+ FRUIT = "fruit"
23
+
24
+ def to_byte(self) -> int:
25
+ return _MODE_TO_BYTE[self]
26
+
27
+ @classmethod
28
+ def byte_for(cls, mode: str | None) -> int:
29
+ if mode is None:
30
+ return 0x00
31
+ try:
32
+ return _MODE_TO_BYTE[cls(mode)]
33
+ except ValueError:
34
+ return 0x00
35
+
36
+
37
+ _MODE_TO_BYTE: dict[Mode, int] = {
38
+ Mode.AUTO: 0x01,
39
+ Mode.INTENSIVE: 0x02,
40
+ Mode.NORMAL: 0x03,
41
+ Mode.ECO: 0x04,
42
+ Mode.GLASS: 0x05,
43
+ Mode.NINETY_MIN: 0x06,
44
+ Mode.RAPID: 0x07,
45
+ Mode.SOAK: 0x08,
46
+ Mode.ONE_HOUR: 0x09,
47
+ Mode.THREE_IN_ONE: 0x0A,
48
+ Mode.PARTY: 0x0C,
49
+ Mode.QUIET: 0x0D,
50
+ Mode.HYGIENE: 0x0F,
51
+ Mode.FRUIT: 0x13,
52
+ }
@@ -0,0 +1,11 @@
1
+ """Tipo de mensagem do frame (byte 9)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import IntEnum
6
+
7
+
8
+ class MsgType(IntEnum):
9
+ CONTROL = 0x02
10
+ QUERY = 0x03
11
+ PUSH = 0x04
@@ -0,0 +1,21 @@
1
+ """Etapa atual do ciclo de lavagem (byte 9 da resposta)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import IntEnum
6
+
7
+
8
+ class WashStage(IntEnum):
9
+ IDLE = 0
10
+ PRE_WASH = 1
11
+ MAIN_WASH = 2
12
+ RINSE = 3
13
+ DRY = 4
14
+ FINISH = 5
15
+
16
+ @classmethod
17
+ def from_byte(cls, byte: int) -> "WashStage | int":
18
+ try:
19
+ return cls(byte)
20
+ except ValueError:
21
+ return byte
@@ -0,0 +1,29 @@
1
+ from .codec import (
2
+ CONTROL_BODY_LEN,
3
+ DEVICE_TYPE,
4
+ HEADER_LEN,
5
+ QUERY_BODY,
6
+ SYNC,
7
+ ControlPayload,
8
+ assemble_frame,
9
+ build_control,
10
+ build_query,
11
+ make_sum,
12
+ parse_frame,
13
+ )
14
+ from .frame_error import FrameError
15
+
16
+ __all__ = [
17
+ "CONTROL_BODY_LEN",
18
+ "ControlPayload",
19
+ "DEVICE_TYPE",
20
+ "FrameError",
21
+ "HEADER_LEN",
22
+ "QUERY_BODY",
23
+ "SYNC",
24
+ "assemble_frame",
25
+ "build_control",
26
+ "build_query",
27
+ "make_sum",
28
+ "parse_frame",
29
+ ]