python-mobius 0.4.0__py3-none-any.whl → 0.4.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
@@ -65,7 +65,7 @@ from .discovery import (
65
65
  discover_tank,
66
66
  )
67
67
 
68
- __version__ = "0.4.0"
68
+ __version__ = "0.4.2"
69
69
 
70
70
  __all__ = [
71
71
  "__version__",
mobius/device.py CHANGED
@@ -709,12 +709,22 @@ class MobiusDevice:
709
709
  ShortAddressArray/SerialNumberArray/DeviceModelArray
710
710
  (3700/3701/3702, the same "get all elements" pattern used
711
711
  elsewhere in this library) from THIS connected device, acting as
712
- a Thread mesh gateway, and returns every OTHER device it knows
713
- about over the mesh -- not devices we necessarily have (or even
714
- can have) a direct BLE connection to. See documentation/
712
+ a Thread mesh gateway, and returns what it reports about devices
713
+ over the mesh -- not devices we necessarily have (or even can
714
+ have) a direct BLE connection to. See documentation/
715
715
  09-thread-coap-relay.md for the full protocol trace this is
716
716
  based on.
717
717
 
718
+ Whether this includes the connected device's own entry
719
+ alongside others isn't confirmed either way -- unlike
720
+ discover_networked_thread_devices() (whose sibling docstring
721
+ this once matched, until a real capture disproved it there),
722
+ real hardware evidence specific to THIS older mechanism was
723
+ never gathered. Callers that also separately add the connected
724
+ device's own entry (e.g. mobius.discovery.discover_tank()) treat
725
+ this as a real possibility and deduplicate by serial regardless,
726
+ rather than assuming either way.
727
+
718
728
  **Confirmed via real hardware testing to be Cowboy-hub-specific.**
719
729
  These three arrays were originally found being fetched as part of
720
730
  the app's own network-setup logic for a dedicated Cowboy hub device,
@@ -789,9 +799,19 @@ class MobiusDevice:
789
799
  """
790
800
  Fetches MLPrefix (2128) plus NetworkedThreadDevices (1001, the
791
801
  same "get all elements" pattern used elsewhere in this library)
792
- from THIS connected device, and returns every OTHER device it
793
- knows about over the Thread mesh -- not devices we necessarily
794
- have (or even can have) a direct BLE connection to.
802
+ from THIS connected device, and returns every device it knows
803
+ about over the Thread mesh -- not devices we necessarily have
804
+ (or even can have) a direct BLE connection to.
805
+
806
+ **Includes the connected device's own entry, not just others.**
807
+ An earlier version of this docstring claimed "every OTHER
808
+ device," an untested assumption rather than a confirmed one --
809
+ disproven by a real 4-device tank capture where a single
810
+ connected device's own NetworkedThreadDevices read returned all
811
+ 4 real devices, necessarily including itself. Callers building
812
+ a peer list that also separately adds the connected device's own
813
+ entry (e.g. mobius.discovery.discover_tank()) need to
814
+ deduplicate by serial, or that device ends up listed twice.
795
815
 
796
816
  A second, better mesh-peer-discovery mechanism than
797
817
  discover_mesh_peers() -- confirmed via reverse engineering the
@@ -823,9 +843,11 @@ class MobiusDevice:
823
843
  build_rloc_address(), which is for the different case of only
824
844
  having a 2-byte short address to encode into the prefix's last 2
825
845
  bytes -- here the full 8-byte suffix is already present in the
826
- entry, so it's used as-is); bytes 8-11 are a 4-byte "age" value
827
- (see MeshPeer's own docstring for the important caveat that its
828
- exact meaning isn't confirmed); bytes 12-13 are the 2-byte model
846
+ entry, so it's used as-is); bytes 8-11 are a 4-byte unsigned
847
+ "age" value in milliseconds -- confirmed to mean time since this
848
+ peer was last heard from over the mesh, a resetting value, not
849
+ time since first discovered (see MeshPeer's own docstring for
850
+ the full detail); bytes 12-13 are the 2-byte model
829
851
  ID; bytes 14-27 are the 14-byte ASCII serial number, matching the
830
852
  SerialNumberArray format used elsewhere in this library. All
831
853
  multi-byte numeric fields are read little-endian -- confirmed
@@ -868,7 +890,13 @@ class MobiusDevice:
868
890
  if len(entry) != 28:
869
891
  continue
870
892
  address = prefix + entry[0:8]
871
- age = struct.unpack("<i", entry[8:12])[0]
893
+ # Unsigned -- confirmed via reverse engineering the app's own
894
+ # network-troubleshooting screen, which explicitly treats this
895
+ # same 4-byte field as an unsigned value before ever using it
896
+ # for anything else. See MeshPeer's own docstring for the full,
897
+ # now-confirmed meaning (milliseconds since this peer was last
898
+ # heard from over the mesh).
899
+ age = struct.unpack("<I", entry[8:12])[0]
872
900
  model_raw = struct.unpack("<h", entry[12:14])[0]
873
901
  try:
874
902
  serial = entry[14:28].decode("ascii").rstrip("\x00")
mobius/device_status.py CHANGED
@@ -130,18 +130,27 @@ class MeshPeer:
130
130
  `age` is only ever set by discover_networked_thread_devices() --
131
131
  None from every other source of MeshPeer, including
132
132
  discover_mesh_peers() and discover_mesh_peers_via_direct_connect().
133
- Confirmed present as a raw 4-byte field in the wire format, but its
134
- exact unit/meaning (freshness of the gateway's own knowledge of this
135
- peer, most likely, given the name -- but not confirmed against real
136
- hardware) is not confirmed; treat it as an opaque, larger-is-probably-
137
- less-fresh value rather than a specific unit of time unless verified.
133
+ Confirmed via reverse engineering the app's own network-
134
+ troubleshooting screen: milliseconds since this specific peer was
135
+ last heard from over the Thread mesh, as far as the connected
136
+ gateway currently knows -- NOT time since it was first discovered
137
+ or added to the tank. A RESETTING value, not a monotonic one: it
138
+ legitimately drops back toward zero whenever the mesh genuinely
139
+ receives fresh activity from that peer, which happens on its own
140
+ schedule, independent of whenever this is read. Two checks in
141
+ quick succession can validly show a SMALLER age the second time --
142
+ that's expected, not a parsing error. The app's own troubleshooting
143
+ screen treats anything over roughly 5 minutes (300000) as long
144
+ enough to flag that peer as currently unreachable/failed, which is
145
+ a reasonable rule of thumb for interpreting this value generally,
146
+ though not enforced by this library itself.
138
147
  """
139
148
  serial: str
140
149
  model_raw: int
141
150
  model: Optional[Model] # None if model_raw doesn't match a known Model value
142
151
  short_address: int
143
152
  address: bytes # 16-byte Thread mesh-local IPv6, ready to pass to encode_coap_request()
144
- age: Optional[int] = None # see docstring -- only set by discover_networked_thread_devices()
153
+ age: Optional[int] = None # milliseconds since last heard from on the mesh -- see docstring above
145
154
 
146
155
 
147
156
  @dataclass
mobius/discovery.py CHANGED
@@ -193,12 +193,28 @@ async def discover_tank(mdevice) -> "Tank":
193
193
  (not just serial/model, the way advertisement-only grouping like
194
194
  group_by_pan_id() can offer).
195
195
 
196
- Calls mdevice.discover_mesh_peers_auto() (which only ever reports
197
- OTHER devices) plus get_own_mesh_address()/get_device_info() (to
198
- include the connected device itself in the result -- otherwise a
199
- single-device tank would come back with zero peers, which would look
200
- identical to "this device supports none of the discovery mechanisms"
201
- rather than "this device IS the whole tank").
196
+ Calls mdevice.discover_mesh_peers_auto() plus
197
+ get_own_mesh_address()/get_device_info() to build the connected
198
+ device's own entry directly. These are deliberately DEDUPLICATED by
199
+ serial before returning, keeping the directly-built own entry over
200
+ whatever discover_mesh_peers_auto() reported for the same serial --
201
+ confirmed via a real 4-device tank capture that
202
+ discover_networked_thread_devices() (the mechanism
203
+ discover_mesh_peers_auto() tries first) is self-INCLUSIVE, not
204
+ self-exclusive as an earlier version of this docstring claimed
205
+ (based on an untested assumption, not a confirmed one): all 4 real
206
+ devices came back from a single connected device's own
207
+ NetworkedThreadDevices read, necessarily including whichever one was
208
+ actually connected to. Without deduplication, that connected
209
+ device's own peer would appear twice in the result -- once from this
210
+ function's own direct build, once from discover_mesh_peers_auto()'s
211
+ own report of it as "just another peer" on its own network. Not
212
+ confirmed whether the OLDER discover_mesh_peers() mechanism
213
+ (discover_mesh_peers_auto()'s own fallback) shares this same
214
+ self-inclusive behavior or not -- the deduplication here is
215
+ unconditional specifically so it's correct either way, rather than
216
+ depending on getting that confirmation for every code path that
217
+ could produce peers.
202
218
 
203
219
  tank.prefix is None if this device didn't report a valid mesh
204
220
  address at all (unsupported, or not currently part of an active
@@ -240,5 +256,13 @@ async def discover_tank(mdevice) -> "Tank":
240
256
  other_peers = await mdevice.discover_mesh_peers_auto()
241
257
  prefix = mesh_local_prefix_from_own_address(own_address)
242
258
 
243
- return Tank(prefix=prefix, peers=[own_peer] + other_peers)
259
+ # own_peer always wins over whatever discover_mesh_peers_auto()
260
+ # separately reported for the same serial -- see this function's
261
+ # own docstring for why that duplicate is a real, confirmed
262
+ # possibility, not just a defensive guard against a hypothetical.
263
+ deduplicated = [own_peer] + [
264
+ peer for peer in other_peers if peer.serial != own_peer.serial
265
+ ]
266
+
267
+ return Tank(prefix=prefix, peers=deduplicated)
244
268
 
mobius/schedule.py CHANGED
@@ -235,6 +235,16 @@ class PumpSchedulePoint:
235
235
  return None
236
236
  return cls(time_minutes, flags, PumpPrimitiveValue.parse(data[3:]))
237
237
 
238
+ def has(self, flag: int) -> bool:
239
+ # Same flags field, same bit meanings as SchedulePoint's own
240
+ # has() (see this class's own docstring: identical wire format,
241
+ # just different primitiveData) -- an earlier version of this
242
+ # class omitted this entirely, which crashed mobius.cli's own
243
+ # --dump-schedule for pumps specifically (its own _format_flags()
244
+ # helper is shared between the light and pump branches, calling
245
+ # point.has() either way).
246
+ return (self.flags & flag) == flag
247
+
238
248
 
239
249
  def get_active_pump_block(points: list[PumpSchedulePoint], minute_of_day: int) -> Optional[PumpSchedulePoint]:
240
250
  """
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-mobius
3
- Version: 0.4.0
3
+ Version: 0.4.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,11 +1,11 @@
1
- mobius/__init__.py,sha256=xZW-4m1biRKqDKuxJEWXcuhVAEfZnXsEJ_r5XIW_Kgk,5722
1
+ mobius/__init__.py,sha256=BpkSMm-tv7BC2F_vWHd-jym24Hsmwfd_OSMeJa2FEfQ,5722
2
2
  mobius/cli.py,sha256=8XH-pVTX4KObzd0IOGXicT7KrcNEjm5xYZS8JOWfHDA,30726
3
3
  mobius/coap.py,sha256=eXf_-9heQD1UjLyqZU9KA-c0jB50NGzAd8D7d6kFMb0,10750
4
4
  mobius/constants.py,sha256=z_KrFev5vGD9-9lKn4J0nGVtzTmelGQ13bYlVcCdjm0,25217
5
5
  mobius/crc.py,sha256=kLtAYWZvLbo5_p9ep08ZlLg5Yah8Q2qq0IwTvqbBGW0,2700
6
- mobius/device.py,sha256=4-7siTYYZ6iNjGFfa9X0NsLBz99B_bJfYx6nKKdfpq8,77337
7
- mobius/device_status.py,sha256=4mHpwr3v4TvMtNXsLCoz5uOVsmsERC-u6RIha8ss9v4,7797
8
- mobius/discovery.py,sha256=khPcvl_mDamgh1EY6R6xeR2CPFDtNiy_pMOe0qJIZD8,10240
6
+ mobius/device.py,sha256=H5JGFtV90xu69jEOydMPqpj53e-5ws6TUVej0xd7WSg,79078
7
+ mobius/device_status.py,sha256=p1l6RpCcg_G4p9nudhSo8MzBSgqkH8m5_0qrnjsFQ2k,8374
8
+ mobius/discovery.py,sha256=LqHOEA_j1ZROuounn3PPmy8IEbpL6-N1TOW1MpoKg6A,11619
9
9
  mobius/frame.py,sha256=UNLTHuzVue5b6L4nv1hWJTSbn5k8lETWdsiL_52yI0Q,6141
10
10
  mobius/manufacturer.py,sha256=8-vBzckN8xmsPSMj5EWtOCXenbyQueoY5pnZHePgb_k,1937
11
11
  mobius/mesh_address.py,sha256=PKQ-mm7Z4bY11ZnVmeWZO9EmOiAHp8hNtXceMGMs2tk,5940
@@ -13,9 +13,9 @@ mobius/modifiers.py,sha256=zWJCsjaJ4TtwS1QgQoRFH-ve1mykuPWn5KX4YQpNqco,7215
13
13
  mobius/power.py,sha256=5l9ehjNCDrSsJjQkaU4M6pYnSMqw2FVF8Ogg6KIE5vQ,3844
14
14
  mobius/pump_status.py,sha256=RWTl8ZViCFDbiFU2mX-WAIH_cWhZOZ0M2B0oYXnPx3Y,1598
15
15
  mobius/relay.py,sha256=tMpV_67CYt9A1L_DPERzbqOgy3BOdG4NZpAG_IlefhY,16587
16
- mobius/schedule.py,sha256=3p7uZ8iYu_g7GUUy3S8X85qZHMNOMpwULhFw8SCnuls,10430
17
- python_mobius-0.4.0.dist-info/METADATA,sha256=mAIIbuwjwTMkL9ljtgS5DnO4eOXys_reFlcuhT4mXDQ,5952
18
- python_mobius-0.4.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
19
- python_mobius-0.4.0.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
20
- python_mobius-0.4.0.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
21
- python_mobius-0.4.0.dist-info/RECORD,,
16
+ mobius/schedule.py,sha256=d5iAHvKuFDwfbrSptPx6viiA_lj2qGfWhsZwM_Bpr1k,10974
17
+ python_mobius-0.4.2.dist-info/METADATA,sha256=PBqNDTyqaF-wG4Q8YUZsMqsQ2TkBTFfOoUNKe0yYZG4,5952
18
+ python_mobius-0.4.2.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
19
+ python_mobius-0.4.2.dist-info/entry_points.txt,sha256=Q2hlcek-bWm70zvd12v32essflPgC2su-dKDHAqbPoE,48
20
+ python_mobius-0.4.2.dist-info/licenses/LICENSE,sha256=7a72Msu2Q-TnoiFxemxEGkwafJGObk1W3rw9hzmyM_Y,17984
21
+ python_mobius-0.4.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.31.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any