• v0.4.4 f768f4186d

    v0.4.4
    All checks were successful
    Release / release (push) Successful in 2m27s
    Stable

    r3pek released this 2026-08-18 11:36:26 +01:00 | 12 commits to main since this release

    • Filled in the rest of the device model list (EcoTech Marine,
      AquaIllumination, Neptune Systems, NYOS), previously a curated
      subset. Also corrects Model.VersaVX1, mismarked as AquaIllumination;
      it's an EcoTech Marine product.
    • Several Model entries are now named for their real product (e.g.
      Model.Axis40, Model.Blade, Model.Quantum160) instead of the app's
      own internal placeholder codename (Turtle2, Saddle, Coffee1).
    • Fixed the lunar-phase-scaled light intensity sometimes disagreeing
      with the app for hours at a stretch. The lunar calculation now uses
      the gateway device's own reported date instead of the local
      system's, matching what the app itself actually does.
    Downloads
  • v0.4.3 7410a0bd48

    v0.4.3
    All checks were successful
    Release / release (push) Successful in 2m18s
    Stable

    r3pek released this 2026-08-14 09:38:09 +01:00 | 18 commits to main since this release

    • Fixed relayed devices (anything reached through a gateway over the
      Thread mesh, not directly over BLE) occasionally losing a read for
      no apparent reason, even though the official app kept working fine.
      A single relay round-trip crosses both BLE and the Thread mesh, and
      one dropped or delayed packet somewhere in that longer path turned
      out to be common enough that the app always retries once before
      giving up -- this library now does the same.
    Downloads
  • v0.4.2 03b06ba4b9

    v0.4.2
    All checks were successful
    Release / release (push) Successful in 2m46s
    Stable

    r3pek released this 2026-08-13 14:46:18 +01:00 | 20 commits to main since this release

    • Fixed MeshPeer.age being parsed as a signed number instead of
      unsigned -- could have shown as negative for a peer that had been
      unreachable a very long time. Also confirmed what this value
      actually means: milliseconds since that peer was last heard from on
      the mesh, not since it was first found. It resets whenever the mesh
      hears from that device again, so seeing it go down between two
      checks is expected, not a bug.
    • Fixed mobius-scan --dump-schedule crashing for pumps specifically
      ('PumpSchedulePoint' object has no attribute 'has') -- that class
      was missing a method SchedulePoint (the light-schedule equivalent)
      already had, despite sharing the exact same flag bits.
    Downloads
  • v0.4.1 fb6dfc9543

    v0.4.1
    All checks were successful
    Release / release (push) Successful in 2m20s
    Stable

    r3pek released this 2026-08-08 14:40:02 +01:00 | 24 commits to main since this release

    • Fixed discover_tank() sometimes reporting the connected device
      twice in a tank's peer list.
    Downloads
  • 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
  • v0.3.1 8689f914bc

    v0.3.1
    Some checks failed
    Release / release (push) Failing after 3m42s
    Stable

    r3pek released this 2026-08-05 14:48:08 +01:00 | 35 commits to main since this release

    • Fixed a real, high-impact bug: Brightness (the master dimmer
      channel) was never being applied to any other channel at all.

      Confirmed directly from raw smali (LightPrimitive.flatten(), called
      on both bracketing schedule points in getIntensitiesAtTime() before
      interpolating between them -- JADX's decompiled Java for this class
      carried its own "Code duplicated" warning, the same signal that led to
      finding the is_night_segment() bug, so this was re-verified against
      bytecode rather than trusted from the decompile a second time). Every
      channel -- moonlight included -- was being interpolated and returned
      without ever being scaled by Brightness, found via real hardware
      testing: a light showing ~1% in the app's own display came back as
      ~25% from this library at the same moment, with Brightness itself
      reading ~3.3% at that point in its own ramp -- fully consistent with
      the missing multiplication, not a lunar or scheduling bug.
      interpolate_light_schedule() now flattens each bracketing point
      SEPARATELY (by that point's own Brightness value) BEFORE
      interpolating between the two already-flattened points -- confirmed
      via a dedicated test that this is NOT mathematically equivalent to
      interpolating raw values first and multiplying by a single
      interpolated Brightness at the end, whenever Brightness itself
      changes across the segment (which it usually does, e.g. ramping
      during dusk/dawn). Brightness itself is still returned as the plain
      raw interpolated value, not flattened to a constant 100% the way the
      real flatten() does to the primitive it's applied to -- callers
      (e.g. a dedicated Brightness sensor) want the actual current
      master-dimmer level. 4 new tests.
    Downloads