python-mobius 0.1.0__py3-none-any.whl → 0.1.1__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.
- mobius/__init__.py +5 -3
- mobius/constants.py +60 -0
- mobius/device.py +22 -2
- {python_mobius-0.1.0.dist-info → python_mobius-0.1.1.dist-info}/METADATA +1 -1
- python_mobius-0.1.1.dist-info/RECORD +14 -0
- python_mobius-0.1.0.dist-info/RECORD +0 -14
- {python_mobius-0.1.0.dist-info → python_mobius-0.1.1.dist-info}/WHEEL +0 -0
- {python_mobius-0.1.0.dist-info → python_mobius-0.1.1.dist-info}/entry_points.txt +0 -0
- {python_mobius-0.1.0.dist-info → python_mobius-0.1.1.dist-info}/licenses/LICENSE +0 -0
mobius/__init__.py
CHANGED
|
@@ -23,7 +23,8 @@ from .frame import (
|
|
|
23
23
|
from .constants import (
|
|
24
24
|
PrimitiveType, PRIMITIVE_SIZE, LIGHT_PRIMITIVES,
|
|
25
25
|
PUMP_PRIMITIVES_VERIFIED, PUMP_PRIMITIVES_EXPERIMENTAL,
|
|
26
|
-
Model,
|
|
26
|
+
Model, MODEL_MANUFACTURER, manufacturer_for_model,
|
|
27
|
+
ErrorState, C2Attribute, PhysicalValueID, VisualID,
|
|
27
28
|
SceneID, OperationState, FsciStatus,
|
|
28
29
|
PumpMode, RampType, PumpParam, PUMP_PARAM_SIZE, PUMP_MODE_PARAMS,
|
|
29
30
|
)
|
|
@@ -40,7 +41,7 @@ from .discovery import (
|
|
|
40
41
|
scan_for_mobius_devices, scan_for_mobius_devices_with_info, group_by_pan_id,
|
|
41
42
|
)
|
|
42
43
|
|
|
43
|
-
__version__ = "0.1.
|
|
44
|
+
__version__ = "0.1.1"
|
|
44
45
|
|
|
45
46
|
__all__ = [
|
|
46
47
|
"__version__",
|
|
@@ -57,7 +58,8 @@ __all__ = [
|
|
|
57
58
|
# constants
|
|
58
59
|
"PrimitiveType", "PRIMITIVE_SIZE", "LIGHT_PRIMITIVES",
|
|
59
60
|
"PUMP_PRIMITIVES_VERIFIED", "PUMP_PRIMITIVES_EXPERIMENTAL",
|
|
60
|
-
"Model", "
|
|
61
|
+
"Model", "MODEL_MANUFACTURER", "manufacturer_for_model",
|
|
62
|
+
"ErrorState", "C2Attribute", "PhysicalValueID", "VisualID",
|
|
61
63
|
"SceneID", "OperationState", "FsciStatus",
|
|
62
64
|
"PumpMode", "RampType", "PumpParam", "PUMP_PARAM_SIZE", "PUMP_MODE_PARAMS",
|
|
63
65
|
# schedule
|
mobius/constants.py
CHANGED
|
@@ -11,6 +11,7 @@ See documentation/ for the full derivation of each table.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
from enum import IntEnum
|
|
14
|
+
from typing import Optional
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
# --------------------------------------------------------------------------
|
|
@@ -107,6 +108,65 @@ class Model(IntEnum):
|
|
|
107
108
|
Coffee2 = 397 # "Quantum 220/300" (NYOS)
|
|
108
109
|
|
|
109
110
|
|
|
111
|
+
# Ported from M.Model.getManufacturer(). The decompiled version branches on
|
|
112
|
+
# enum *ordinal* against a hardcoded position list (the full ~100-entry
|
|
113
|
+
# Model enum's index 1-37 range, plus a few extras, all map to "EcoTech
|
|
114
|
+
# Marine"); since this library only implements a curated subset of Model,
|
|
115
|
+
# this is re-derived by name pattern against that same logic instead of by
|
|
116
|
+
# ordinal position -- functionally equivalent for every Model value defined
|
|
117
|
+
# above, but if you add more Model entries, add their manufacturer here too.
|
|
118
|
+
MODEL_MANUFACTURER: dict[Model, str] = {
|
|
119
|
+
Model.Unknown: "Unknown",
|
|
120
|
+
Model.VorTechMP10wES: "EcoTech Marine",
|
|
121
|
+
Model.VorTechMP10wQD: "EcoTech Marine",
|
|
122
|
+
Model.VorTechMP40wES: "EcoTech Marine",
|
|
123
|
+
Model.VorTechMP40wQD: "EcoTech Marine",
|
|
124
|
+
Model.VorTechMP40wG3QD: "EcoTech Marine",
|
|
125
|
+
Model.VorTechMP60wES: "EcoTech Marine",
|
|
126
|
+
Model.VorTechMP60wQD: "EcoTech Marine",
|
|
127
|
+
Model.RadionXR30w: "EcoTech Marine",
|
|
128
|
+
Model.RadionXR30wG2: "EcoTech Marine",
|
|
129
|
+
Model.RadionXR30wPro: "EcoTech Marine",
|
|
130
|
+
Model.RadionXR30wG3: "EcoTech Marine",
|
|
131
|
+
Model.RadionXR30wG3Pro: "EcoTech Marine",
|
|
132
|
+
Model.RadionXR30wG4: "EcoTech Marine",
|
|
133
|
+
Model.RadionXR30wG4Pro: "EcoTech Marine",
|
|
134
|
+
Model.RadionXR30wFWPro: "EcoTech Marine",
|
|
135
|
+
Model.RadionXR30wPrototype: "EcoTech Marine",
|
|
136
|
+
Model.RadionXR15wG3Pro: "EcoTech Marine",
|
|
137
|
+
Model.RadionXR15wFW: "EcoTech Marine",
|
|
138
|
+
Model.RadionXR15wG4Pro: "EcoTech Marine",
|
|
139
|
+
Model.RadionXR15wFWPro: "EcoTech Marine",
|
|
140
|
+
Model.RadionXR15wPrototype: "EcoTech Marine",
|
|
141
|
+
Model.RadionXR15G5Pro: "EcoTech Marine",
|
|
142
|
+
Model.RadionXR15wG5Blue: "EcoTech Marine",
|
|
143
|
+
Model.RadionXR15wG5FW: "EcoTech Marine",
|
|
144
|
+
Model.RadionXR15wG6Pro: "EcoTech Marine",
|
|
145
|
+
Model.RadionXR15wG6Blue: "EcoTech Marine",
|
|
146
|
+
Model.RadionXR30wG5Pro: "EcoTech Marine",
|
|
147
|
+
Model.RadionXR30wG5Blue: "EcoTech Marine",
|
|
148
|
+
Model.VectraM1: "EcoTech Marine",
|
|
149
|
+
Model.VectraL1: "EcoTech Marine",
|
|
150
|
+
Model.VectraS1: "EcoTech Marine",
|
|
151
|
+
Model.VectraS2: "EcoTech Marine",
|
|
152
|
+
Model.VectraM2: "EcoTech Marine",
|
|
153
|
+
Model.VectraL2: "EcoTech Marine",
|
|
154
|
+
Model.Nero5: "AquaIllumination",
|
|
155
|
+
Model.Nero3: "AquaIllumination",
|
|
156
|
+
Model.DosingPump: "AquaIllumination",
|
|
157
|
+
Model.Muffler: "Neptune Systems", # confirmed explicitly in the decompile's manufacturer list
|
|
158
|
+
Model.Coffee1: "NYOS", # confirmed explicitly (CoffeeV1 branch)
|
|
159
|
+
Model.Coffee2: "NYOS",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def manufacturer_for_model(model: Optional[Model]) -> Optional[str]:
|
|
164
|
+
"""Returns the manufacturer name for a Model, or None if unknown/unmapped."""
|
|
165
|
+
if model is None:
|
|
166
|
+
return None
|
|
167
|
+
return MODEL_MANUFACTURER.get(model)
|
|
168
|
+
|
|
169
|
+
|
|
110
170
|
class ErrorState(IntEnum):
|
|
111
171
|
"""Confirmed literal values from M.ErrorState (subset covering common
|
|
112
172
|
pump/light fault codes -- the full enum also has dosing-pump-specific codes)."""
|
mobius/device.py
CHANGED
|
@@ -29,7 +29,7 @@ from .frame import (
|
|
|
29
29
|
)
|
|
30
30
|
from .constants import (
|
|
31
31
|
C2Attribute, PrimitiveType, Model, ErrorState, SceneID, OperationState,
|
|
32
|
-
FsciStatus, PhysicalValueID, VisualID,
|
|
32
|
+
FsciStatus, PhysicalValueID, VisualID, manufacturer_for_model,
|
|
33
33
|
PRIMITIVE_SIZE, LIGHT_PRIMITIVES, PUMP_PRIMITIVES_VERIFIED, PUMP_PRIMITIVES_EXPERIMENTAL,
|
|
34
34
|
)
|
|
35
35
|
from .schedule import (
|
|
@@ -48,6 +48,25 @@ CHAR_TX_DATA = "01ff0103-ba5e-f4ee-5ca1-eb1e5e4b1ce0" # write: app -> device,
|
|
|
48
48
|
CHAR_TX_FINAL = "01ff0104-ba5e-f4ee-5ca1-eb1e5e4b1ce0" # write: app -> device, last fragment
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def _decode_serial(raw: bytes) -> str:
|
|
52
|
+
"""
|
|
53
|
+
Serial numbers are printable ASCII on every device tested (confirmed by
|
|
54
|
+
cross-validating the GATT-read serial against the same serial parsed
|
|
55
|
+
from BLE advertisement manufacturer data -- see manufacturer.py and
|
|
56
|
+
documentation/08-manufacturer-data.md), so decode as text rather than
|
|
57
|
+
hex-encoding raw bytes. Falls back to hex for any non-printable-ASCII
|
|
58
|
+
edge case not yet observed, rather than raising.
|
|
59
|
+
"""
|
|
60
|
+
text = raw.rstrip(b"\x00")
|
|
61
|
+
try:
|
|
62
|
+
decoded = text.decode("ascii")
|
|
63
|
+
except UnicodeDecodeError:
|
|
64
|
+
return raw.hex()
|
|
65
|
+
if decoded and all(32 <= ord(c) < 127 for c in decoded):
|
|
66
|
+
return decoded
|
|
67
|
+
return raw.hex()
|
|
68
|
+
|
|
69
|
+
|
|
51
70
|
class MobiusDevice:
|
|
52
71
|
"""
|
|
53
72
|
High-level async client for a single Mobius-protocol BLE device.
|
|
@@ -252,8 +271,9 @@ class MobiusDevice:
|
|
|
252
271
|
return {
|
|
253
272
|
"model_raw": model_val,
|
|
254
273
|
"model": model.name if model is not None else (f"unknown({model_val})" if model_val is not None else None),
|
|
274
|
+
"manufacturer": manufacturer_for_model(model),
|
|
255
275
|
"name": name_raw.decode("utf-8", errors="replace").rstrip("\x00") if name_raw else None,
|
|
256
|
-
"serial": serial_raw
|
|
276
|
+
"serial": _decode_serial(serial_raw) if serial_raw else None,
|
|
257
277
|
"primitive_type": PrimitiveType(primitive_raw[0]).name if primitive_raw else None,
|
|
258
278
|
"error_state": error.name if error is not None else (f"unknown({error_val})" if error_val is not None else None),
|
|
259
279
|
"mac_address": ":".join(f"{b:02X}" for b in mac_raw) if mac_raw else None,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-mobius
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Reverse-engineered Python client for the Mobius BLE protocol (EcoTech Marine VorTech/Radion, AquaIllumination, Neptune Systems, NYOS)
|
|
5
5
|
Project-URL: Homepage, https://code.r3pek.org/r3pek/python-mobius
|
|
6
6
|
Project-URL: Documentation, https://code.r3pek.org/r3pek/python-mobius/src/branch/main/documentation
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
mobius/__init__.py,sha256=JK6iAhOfAJ1LBf2tKUi3OQY7NMNwOudyyExjYKbehNU,3381
|
|
2
|
+
mobius/cli.py,sha256=uhGZIDrhpTp8LkTGOl4BZVQWgJYHbaIVH2HzX3RFCCs,2642
|
|
3
|
+
mobius/constants.py,sha256=wKrWU-QnqMuWuvczHwt3ha3sPw6D9NNK-kIhxJcTbHA,14702
|
|
4
|
+
mobius/crc.py,sha256=WwIfUXGN_UKdsv-_Q303-L1-L1Rc1fvJnB5176ftzqU,2669
|
|
5
|
+
mobius/device.py,sha256=VeBBT2z0XyqwdnUxw56GrG_ZlVe5Oq05tF-m4z-rMDY,20805
|
|
6
|
+
mobius/discovery.py,sha256=4MjGwpd0bUK-koiZ-a2Cvq-gCTImAKAQ4lZ-9vOge3Q,2290
|
|
7
|
+
mobius/frame.py,sha256=zG2vh8PceJHD8R43MwZiJnm0Ge1dr8nFcKsJR9_c9LM,6135
|
|
8
|
+
mobius/manufacturer.py,sha256=8-vBzckN8xmsPSMj5EWtOCXenbyQueoY5pnZHePgb_k,1937
|
|
9
|
+
mobius/schedule.py,sha256=kHr684TIKPZ2BplIySoeD-Yn1Xa_2bE6rfGUs-waWMQ,7483
|
|
10
|
+
python_mobius-0.1.1.dist-info/METADATA,sha256=m9dnMFZU2-Qdq_kLSpxPqoYE05Ac3E7NhtSjSmwjXNo,5907
|
|
11
|
+
python_mobius-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
12
|
+
python_mobius-0.1.1.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
|
|
13
|
+
python_mobius-0.1.1.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
|
|
14
|
+
python_mobius-0.1.1.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
mobius/__init__.py,sha256=wtCy-JKP5KhFr3dq_C0V4458R-msyZt2EP96vH16Tr8,3281
|
|
2
|
-
mobius/cli.py,sha256=uhGZIDrhpTp8LkTGOl4BZVQWgJYHbaIVH2HzX3RFCCs,2642
|
|
3
|
-
mobius/constants.py,sha256=9OtNEOU4FXXhdgayGrz5e_T8s6M20UE31B6GQ-mywnY,12022
|
|
4
|
-
mobius/crc.py,sha256=WwIfUXGN_UKdsv-_Q303-L1-L1Rc1fvJnB5176ftzqU,2669
|
|
5
|
-
mobius/device.py,sha256=CbCQQjmLQmg-zFufRufcRPkA3PlO_18AkeCP_eDE0EA,19993
|
|
6
|
-
mobius/discovery.py,sha256=4MjGwpd0bUK-koiZ-a2Cvq-gCTImAKAQ4lZ-9vOge3Q,2290
|
|
7
|
-
mobius/frame.py,sha256=zG2vh8PceJHD8R43MwZiJnm0Ge1dr8nFcKsJR9_c9LM,6135
|
|
8
|
-
mobius/manufacturer.py,sha256=8-vBzckN8xmsPSMj5EWtOCXenbyQueoY5pnZHePgb_k,1937
|
|
9
|
-
mobius/schedule.py,sha256=kHr684TIKPZ2BplIySoeD-Yn1Xa_2bE6rfGUs-waWMQ,7483
|
|
10
|
-
python_mobius-0.1.0.dist-info/METADATA,sha256=pwoXT7fGt7RyDQKMvRzjvNJZaaFtzENzOUWI_OwmELs,5907
|
|
11
|
-
python_mobius-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
12
|
-
python_mobius-0.1.0.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
|
|
13
|
-
python_mobius-0.1.0.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
|
|
14
|
-
python_mobius-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|