• v0.4.0 46f923a1f3

    v0.4.0
    Some checks failed
    Release / release (push) Failing after 2m53s
    Stable

    r3pek released this 2026-08-07 23:29:27 +01:00 | 27 commits to main since this release

    • mobius-scan's default now also connects to ONE device and dumps
      real mesh addresses for its whole network
      , not just the pan_id
      listing. Reuses the exact same verbose discovery --dump-mesh-peers
      already used (_debug_mesh_discovery() -- MLPrefix/
      NetworkedThreadDevices first, falling back to the older Cowboy-hub
      mechanism), so the output is identical in shape whether it happens via
      the default run or that flag explicitly. --scan-only now has real
      meaning again (it had briefly become a no-op after the previous
      change made the pan_id listing itself the whole default): it skips
      this connection step, for a genuinely connection-free run.
      --dump-tanks still exists separately for connecting to EVERY pan_id
      group rather than just one, useful when multiple genuinely separate
      tanks/networks are in range at once. Verified end-to-end with the
      scan/connect layer mocked out: confirmed exactly one MobiusDevice
      connection gets created by the plain default (to the correct,
      first-found serial), and confirmed --scan-only still makes zero.

    • Added Tank and discover_tank(): the connection-based building
      block for full tank discovery
      (device + address + age + shared
      prefix, all in one call) -- the "basic block" a downstream integration
      (e.g. ha-mobius) can call to trigger an on-demand scan and show
      address/age/prefix for its devices. discover_tank(mdevice) takes an
      already-connected MobiusDevice, calls discover_mesh_peers_auto()
      plus get_own_mesh_address()/get_device_info() to include the
      connected device itself in the result (the peer-discovery calls only
      ever report OTHER devices, never themselves) -- so a single-device
      tank correctly comes back with that one device as its sole peer, not
      an empty list that would be indistinguishable from "discovery failed
      entirely." tank.prefix is None (and peers always empty) if the
      connected device didn't report a valid mesh address at all. 3 new
      tests. Also exported mesh_local_prefix_from_mlprefix() from the
      top-level mobius package -- a real gap from when it was first added,
      caught while adding these new exports alongside it.

    • mobius-scan's default behavior changed: now genuinely just scans
      and lists devices grouped by tank, no connections at all
      -- it used
      to print that same pan_id-grouped listing (already existed, already
      connection-free) and then STILL go on to connect to every single
      scanned device for a full summary regardless. That's now opt-in via a
      new --full-summaries flag instead of being the implicit default.
      Added --dump-tanks: connects to just ONE device per pan_id group
      (not every device) and calls the new discover_tank() on it, printing
      the full mesh-address/age for every device sharing that tank --
      richer than the default listing, which only has whatever's in the BLE
      advertisement (model/serial), not address or age. --scan-only kept
      accepted as a no-op (it's redundant now that its old behavior IS the
      default) rather than removed, so no existing script using it
      explicitly breaks. Renamed the printed header from "Pan ID groups" to
      "Tanks" to match. Verified end-to-end with the scan/connect layer
      mocked out, for both the new default (confirmed zero MobiusDevice
      connections created) and --dump-tanks (confirmed it correctly
      prints prefix/address/age for a 2-device tank), not just via
      --help's rendered text.

    • discover_networked_thread_devices() confirmed against real
      hardware
      (a live 4-device tank: 2 VorTech pumps, 2 Radion lights) --
      the byte layout, little-endian field order, and all 4 real peers'
      model/serial/age/derived-address all matched exactly. One real
      correction from this: empty slots on real hardware are encoded as 28
      bytes of raw zero (an all-null-byte serial), not the ASCII
      "00000000000000" sentinel string discover_mesh_peers()'s older
      mechanism uses -- the existing if not serial filter already handled
      this correctly (an all-null-byte serial decodes to an empty string
      after stripping), but the docstring incorrectly claimed this newer
      mechanism used the same ASCII convention as the older one. Docstring
      corrected; added a dedicated test for the actual null-byte case, plus
      a new test replaying the exact real capture end-to-end (all 4 peers,
      interleaved with the 26 real empty slots).

    • Added NetworkedThreadDevices (1001) + MLPrefix (2128) support: a
      second, better Thread mesh peer-discovery mechanism.
      Confirmed via
      reverse engineering the app's own tank-setup logic to be checked via a
      generic support check on the connected device, not gated to a
      Cowboy hub
      the way the existing three-parallel-arrays mechanism
      (discover_mesh_peers()) is -- found while investigating whether the
      app caches device mesh addresses at all (it does, persisting them to
      a local file and syncing them to a cloud backend as part of each
      device's saved state; this attribute is what actually populates that
      cache in the first place). MobiusDevice.discover_networked_thread_devices()
      fetches both attributes and returns every peer the connected device
      knows about in a single round-trip, including a per-peer "age" value
      (present in the confirmed wire format, but its exact meaning isn't
      independently confirmed against real hardware -- see MeshPeer's own
      docstring). MeshPeer gained an age: Optional[int] = None field
      (only ever set by this new method; None from every other source, a
      non-breaking addition verified against all 4 existing MeshPeer(...)
      construction sites, all of which use keyword arguments). Added
      mesh_local_prefix_from_mlprefix() to mobius.mesh_address
      (MLPrefix's own response is 24 bytes; only the first 8 are the real
      prefix, confirmed via reverse engineering).

    • Added MobiusDevice.discover_mesh_peers_auto(): the new
      recommended default entry point for mesh peer discovery
      , replacing
      the previous recommendation to call discover_mesh_peers() or
      discover_mesh_peers_via_direct_connect() directly depending on
      hardware. Tries discover_networked_thread_devices() first, falling
      back to discover_mesh_peers() only if that finds nothing --
      discover_mesh_peers_via_direct_connect() isn't included in this
      chain, since it needs a list of scanned devices as input rather than
      an already-connected MobiusDevice; it remains the right fallback to
      reach for explicitly if both of the above come back empty.

    • On "discovering Tanks": investigated and confirmed not locally
      possible -- the app's own "Tank" grouping is account-level and
      cloud-synced, with user-edited metadata (name, photo) that has no
      local/BLE equivalent to read. The closest local approximation this
      library can offer: every peer discover_networked_thread_devices()
      returns shares the connected device's own MLPrefix, which in
      practice is "every device on the same Thread mesh" -- for a typical
      setup, the same thing as "every device in the same aquarium."
      Documented as an approximation, not presented as equivalent to the
      app's own Tank object.

    • mobius-scan CLI updated to match: --dump-mesh-peers and
      --relay-target now run the new discover_mesh_peers_auto()
      priority order (step 0: MLPrefix/NetworkedThreadDevices, falling
      through to the existing Cowboy-hub-specific steps only if that finds
      nothing) instead of only ever exercising the older mechanism;
      --dump-mesh-peers' output now also prints each peer's age when
      set. --build-peer-map unchanged in behavior, now documented as the
      last-resort fallback when both attribute-based mechanisms come back
      empty. 10 new tests covering the new discovery method, the prefix
      helper, and both branches of discover_mesh_peers_auto()'s fallback
      logic -- plus two direct end-to-end simulations of the updated CLI
      debug path (both the new-mechanism-succeeds case and the
      falls-through-to-the-old-mechanism case) run manually before
      delivery, not just unit-level mocks.

    Downloads