python-mobius 0.8.0__py3-none-any.whl → 0.8.2__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 CHANGED
@@ -28,6 +28,7 @@ from .constants import (
28
28
  SceneID, OperationState, FsciStatus,
29
29
  PumpMode, PumpOverrideMode, RampType, PumpParam, PUMP_PARAM_SIZE, PUMP_MODE_PARAMS,
30
30
  supported_pump_modes,
31
+ get_pump_reverse,
31
32
  FirmwareType, FIRMWARE_TYPE_LABELS_ETM, HardwareInfo,
32
33
  Color, COLOR_LABELS, RadioType, RADIO_TYPE_LABELS,
33
34
  MotorType, MOTOR_TYPE_LABELS, ProductType, PRODUCT_TYPE_LABELS,
@@ -81,7 +82,7 @@ from .mob import (
81
82
  MAX_PUMP_PRIMITIVE_SIZE,
82
83
  )
83
84
 
84
- __version__ = "0.8.0"
85
+ __version__ = "0.8.2"
85
86
 
86
87
  __all__ = [
87
88
  "__version__",
@@ -103,6 +104,7 @@ __all__ = [
103
104
  "SceneID", "OperationState", "FsciStatus",
104
105
  "PumpMode", "PumpOverrideMode", "RampType", "PumpParam", "PUMP_PARAM_SIZE", "PUMP_MODE_PARAMS",
105
106
  "supported_pump_modes",
107
+ "get_pump_reverse",
106
108
  "FirmwareType", "FIRMWARE_TYPE_LABELS_ETM", "HardwareInfo",
107
109
  "Color", "COLOR_LABELS", "RadioType", "RADIO_TYPE_LABELS",
108
110
  "MotorType", "MOTOR_TYPE_LABELS", "ProductType", "PRODUCT_TYPE_LABELS",
mobius/constants.py CHANGED
@@ -819,6 +819,35 @@ def supported_pump_modes(
819
819
  return _PUMP_SUPPORTED_MODES.get(primitive_type, [])
820
820
 
821
821
 
822
+ def get_pump_reverse(primitive_type: PrimitiveType, params: dict[PumpParam, int]) -> bool | None:
823
+ """Whether this point's own MaxSpeed/MinSpeed value currently
824
+ encodes reverse rotation -- ported from the decompiled app's own
825
+ PumpPrimitive.getReverse(), which checks each of a primitive's own
826
+ params for one whose slider settings say supportsReverse, then
827
+ reports whether that param's raw (unabs'd) value is negative.
828
+
829
+ Confirmed directly from the app's own PumpParameterEntry.sliderSettings():
830
+ MaxSpeed and MinSpeed both only ever set supportsReverse=true when
831
+ primitive_type is AlpacaV1 -- every other primitive type
832
+ (VorTechV1, PumpV1, TurtleV1, CoffeeV1, VectraV1) never supports
833
+ reverse at all, regardless of mode or the value's own sign. This
834
+ always returns None for those, same as the app's own getReverse()
835
+ falling through to null when no param on the primitive supports
836
+ it.
837
+
838
+ parse()/schedule_to_dict() already report MaxSpeed/MinSpeed as
839
+ signed ints as-is (see PumpPrimitiveValue.parse()'s own comment),
840
+ so this just checks their sign -- it doesn't change what those
841
+ functions themselves return.
842
+ """
843
+ if primitive_type != PrimitiveType.AlpacaV1:
844
+ return None
845
+ for param in (PumpParam.MaxSpeed, PumpParam.MinSpeed):
846
+ if param in params:
847
+ return params[param] < 0
848
+ return None
849
+
850
+
822
851
  class RampType(IntEnum):
823
852
  Sinusoidal = 1
824
853
  Logarithmic = 2
mobius/device.py CHANGED
@@ -145,6 +145,24 @@ class LightPollResult:
145
145
  schedule_points: list[SchedulePoint]
146
146
  intensities: LightIntensityResult
147
147
  used_batch: bool = True
148
+ # The same raw 0.0-1.0 schedule-level master dimmer
149
+ # get_schedule_intensity() itself returns -- already fetched and
150
+ # consumed internally by both this method and the individual-reads
151
+ # fallback below (it's one of process_light_intensities()'s own
152
+ # required inputs), but never actually propagated out to the
153
+ # caller until this field existed. NOT the same thing as
154
+ # intensities.diagnostics["scalar"]: that reflects whichever value
155
+ # is CURRENTLY driving the effective per-channel output at this
156
+ # exact moment (which, during a lunar-reduced night segment, is
157
+ # the lunar reduction factor instead) -- this field is always the
158
+ # device's own raw schedule-level intensity setting itself,
159
+ # regardless of whether it's the thing actually in effect right
160
+ # now. A caller wanting "what would the user's own intensity
161
+ # slider currently show" wants this field specifically, not
162
+ # diagnostics["scalar"]. None only if the underlying read itself
163
+ # failed and decode_schedule_intensity() had truly nothing to fall
164
+ # back to.
165
+ schedule_intensity: Optional[float] = None
148
166
 
149
167
 
150
168
  @dataclass
@@ -545,7 +563,9 @@ def decode_light_poll_from_batch(
545
563
  lunar_enabled, lunar_date, lunar_date_source,
546
564
  schedule_intensity, acclimation_info, insolation_active,
547
565
  )
548
- return LightPollResult(schedule_points=points, intensities=intensities, used_batch=True)
566
+ return LightPollResult(
567
+ schedule_points=points, intensities=intensities, used_batch=True, schedule_intensity=schedule_intensity,
568
+ )
549
569
 
550
570
 
551
571
  def decode_pump_flow_range(min_raw: list[bytes], max_raw: list[bytes]) -> Optional[PumpFlowRange]:
@@ -1296,6 +1316,28 @@ class MobiusDevice:
1296
1316
  """
1297
1317
  await self.set_attribute(C2Attribute.Reset, bytes([ResetType.Soft]))
1298
1318
 
1319
+ async def reboot_all(self) -> None:
1320
+ """
1321
+ Soft-reboots every device on this mesh at once -- what the
1322
+ app's own "Restart all Devices" (tank settings screen) does.
1323
+
1324
+ Confirmed from the app's own SettingsFragment.reboot(): this
1325
+ is NOT a loop of individual reboot() calls against every
1326
+ device. It's the exact same single Set as reboot() (Reset=Soft),
1327
+ just with the frame's own reserved-byte "group" field set to 1
1328
+ (see make_reserved()) instead of left at 0 -- one write to
1329
+ this device, propagated to the rest of the mesh the same way
1330
+ set_time_to_now()/start_scene(broadcast=True) already do.
1331
+ group=1 specifically (not the app's own client-side, per-tank
1332
+ group number, which isn't something this library has any way
1333
+ to discover) matches set_time_to_now()'s own confirmed-working
1334
+ value, and the same real-hardware caveats apply here: not
1335
+ guaranteed reliable across every mesh topology or under
1336
+ real-world conditions where a peer is briefly unreachable when
1337
+ the write's own propagation happens.
1338
+ """
1339
+ await self.set_attribute(C2Attribute.Reset, bytes([ResetType.Soft]), reserved=make_reserved(group=1))
1340
+
1299
1341
  # ---- scenes -----------------------------------------------------------
1300
1342
 
1301
1343
  async def start_scene(self, scene_id: int, duration_seconds: int = 0, broadcast: bool = True) -> None:
@@ -3768,7 +3810,11 @@ class MobiusDevice:
3768
3810
  """
3769
3811
  points = await self.get_light_schedule(which)
3770
3812
  intensities = await self.get_current_light_intensities(which, minute_of_day, now)
3771
- return LightPollResult(schedule_points=points, intensities=intensities, used_batch=False)
3813
+ schedule_intensity = await self.get_schedule_intensity(which)
3814
+ return LightPollResult(
3815
+ schedule_points=points, intensities=intensities, used_batch=False,
3816
+ schedule_intensity=schedule_intensity,
3817
+ )
3772
3818
 
3773
3819
  async def get_full_poll_batch(
3774
3820
  self, primitive: PrimitiveType, model: Optional[Model] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-mobius
3
- Version: 0.8.0
3
+ Version: 0.8.2
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
@@ -1,9 +1,9 @@
1
- mobius/__init__.py,sha256=KugOYZA5IgYM5u3TLmkiYT0ygAbKXrjTaG1fYLiPpXc,7574
1
+ mobius/__init__.py,sha256=yON2rPOxmGrhuAQ753NNSXkaUWFuUCc3R5JZTbRkvlY,7620
2
2
  mobius/cli.py,sha256=DJNKcBB7VCROZ90QmFWeCkRzPCRfoTaeTZaCLJ41s-Y,79735
3
3
  mobius/coap.py,sha256=ZRBy8ivgl1o2cVLjIp-df8Jg34c58K0_1Oec1P-gQI4,10464
4
- mobius/constants.py,sha256=xEFVUAQQZohX8RPVkLsKyoj-FIg16wA8lET6izQ_f_4,44896
4
+ mobius/constants.py,sha256=OlbC0GSPT3Lp7rGfIqSMkAqY535oNNRRoOd15TTVt7k,46286
5
5
  mobius/crc.py,sha256=T6PWTp3gAHu-C05ua2LH8z8leD8mSjizM7nuC9G8KhU,2643
6
- mobius/device.py,sha256=dxMEAZA00mY3fwYoNHz0MGaDnH7qE9LHBd0urZUFpKg,198925
6
+ mobius/device.py,sha256=Ti6jBr_gXDXXM2SlqLYV471p1tIhdDk4NTiBTWNXxPY,201501
7
7
  mobius/device_status.py,sha256=tKZwi12fS3PVl7F-Pr5tvTNOTAttaOtAWom7ahl0LZM,33080
8
8
  mobius/discovery.py,sha256=-N_RK8ZK91Ep-XePm9cHJa44yKiBL_Ind9wFHi_TZ-8,11742
9
9
  mobius/dump.py,sha256=2uy-bq8Ja2W3D4dz9VboSO_KqYQ4bE8xmVxYTxWOjXg,16525
@@ -17,8 +17,8 @@ mobius/pump_status.py,sha256=qLprWM0gAoltZj7w8ykdXHPfBDMl-QnBOwrmBlvknIA,1536
17
17
  mobius/relay.py,sha256=BmUc1l0mDOt9isTGs7wCh0NxehbMxfaTzm3cYs7VucA,26445
18
18
  mobius/scenes.py,sha256=Wt0Ys2x1YjF8MNDEd6kxWqYBIYPdBSbfzXD-L5ZKzag,3592
19
19
  mobius/schedule.py,sha256=BDm8Uh8O85AlAZuR7i7pgqKC2GNXcV_HikFoG6_3Xgo,17590
20
- python_mobius-0.8.0.dist-info/METADATA,sha256=9F71G-L4VT_w2M7rkWA95AQUxnSUQoSTvlgnEMeT4xA,9899
21
- python_mobius-0.8.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
- python_mobius-0.8.0.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
23
- python_mobius-0.8.0.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
24
- python_mobius-0.8.0.dist-info/RECORD,,
20
+ python_mobius-0.8.2.dist-info/METADATA,sha256=w2YtMRx_vpnctt1CYM-VEw-uDomBvXawtGpgl_BFC6Q,9899
21
+ python_mobius-0.8.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
22
+ python_mobius-0.8.2.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
23
+ python_mobius-0.8.2.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
24
+ python_mobius-0.8.2.dist-info/RECORD,,