Commit Graph

4 Commits

Author SHA1 Message Date
Aletheia
7b69479e80 fix: direct commands supersede running patterns; duration auto-stops are tracked
Two races the ported Android engine also has (flagged for backport
there):

- A direct output command (vibrate etc.) now cancels the pattern
  running on the target device. Previously the pattern loop kept
  overwriting the direct command's value every few hundred ms, so the
  command appeared to do nothing.
- Duration auto-stops are now tracked per (device, channel, feature)
  and cancelled when a newer command, pattern, or stop takes over that
  channel. Previously an auto-stop from an earlier command could fire
  minutes later and silently zero output the user believed was running
  — e.g. kill an escalate hold with no ack and no log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:18:08 +02:00
Aletheia
7c61ecb410 fix: pattern lifecycle — indefinite durations, escalate hold contract, monotonic clocks
Ports the Android relay engine's pattern semantics (its 2026-06/2026-07
fix batches) to both Python relay clients:

- duration <= 0 on pulse/wave now runs the pattern until an explicit
  stop, matching how plain commands treat duration=0. Previously the
  loop condition was already false at start, so the pattern silently
  did nothing.
- escalate now honours its documented hold contract: hold_seconds <= 0
  suspends at peak until cancelled, > 0 holds then stops. Both paths
  run through a finally that stops the device, so an error mid-ramp can
  no longer leave hardware running at the last intensity it reached.
  (The naive finally alone would stop the device the moment the ramp
  finished — the same regression the Android app shipped and fixed.)
- Pattern timing uses time.monotonic() instead of time.time(): an NTP
  wall-clock jump could stretch, truncate, or instantly end a pattern.
- relay_client.py now keys pattern tasks by device (one pattern per
  device, as in the Android engine) instead of device:pattern, which
  let a wave and a pulse fight over the same actuator.
- Duplicate same-model devices get suffixed short names (lush, lush_2)
  instead of silently replacing each other.
- Dropped the unsolicited device_list push after auth — the server
  requests a scan on connect and the scan handler answers it; the
  double-send raced session registration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:14:32 +02:00
Aletheia
136920b717 feat: feature_index — target individual motors on multi-actuator devices
Ported from the Android edition and completed for the Python relays
(the Android repo only implemented the phone side in Kotlin):

- models.py / mcp_tools.py: optional feature_index on every output and
  pattern tool, passed through to the phone relay.
- relay_client.py: routes targeted writes through buttplug-py's
  per-feature API (device.features[i].run_output) and validates the
  index up front so a bad one fails the ack with the valid indices
  listed, instead of dying silently inside a pattern task.
- termux_relay_v3.py: ScalarCmd entries filtered to the requested
  actuator index, same fallback semantics as the Android relay engine.

Lets Claude drive e.g. a Dolce's internal and external motors
independently (feature_index 0 / 1) instead of always both together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 20:11:01 +02:00
Aletheia
142e83d512 Add files via upload 2026-03-14 19:40:48 +01:00