Skip to content

feat(holmanctl): add --run and --stop for one-shot tap control - #11

Merged
scottmckenzie merged 1 commit into
masterfrom
feature/holmanctl-run-stop
Oct 15, 2026
Merged

scottmckenzie merged 1 commit into
masterfrom
feature/holmanctl-run-stop

Conversation

@scottmckenzie

Copy link
Copy Markdown
Owner

Summary

Re-implements #3 by @hmoffatt on the current codebase, so holmanctl can start or stop the tap from a script or cron job and exit:

sudo holmanctl --connect AA:BB:CC:DD:EE:FF --run 10   # run for 10 minutes (1-255)
sudo holmanctl --connect AA:BB:CC:DD:EE:FF --stop

It connects, writes the command, disconnects and exits: 0 if the tap timer accepted the command, 1 if connecting or writing failed. Hamish is credited as co-author on the commit.

#3 conflicts with master (it predates BTX1 support and #10's reformatting), so this is a fresh implementation. It also fixes the problems found when reviewing it.

SDK

  • TapTimerListener gains two events, timer_written() and timer_write_failed(error), fired for writes to the manual characteristic (start()/stop()).
  • The base class has no-op defaults. In Add command line arguments to start/stop the tap timer and exit #3 the SDK called listener.timer_written() without adding it to the base class, so every existing listener (including the README example) raised AttributeError after start() or stop().

holmanctl

  • Validation: --run accepts 1-255 minutes, checked by argparse. Previously -5 crashed mid-connection and 600 was silently capped at 255. --run and --stop are mutually exclusive and require --connect.
  • Write failures: a failed write now exits with an error. In Add command line arguments to start/stop the tap timer and exit #3 only a successful write ended the program, so a failed one hung forever.
  • Disconnect: the device is disconnected before exiting, rather than leaving BlueZ holding the connection.
  • Exit status: failures exit 1 instead of 0. main() now returns the exit code (the console-script wrapper passes it to sys.exit) instead of calling sys.exit() inside D-Bus callbacks.
  • No hang on synchronous connect failure: gatt can report a connect failure synchronously inside connect(), before the event loop starts, and DeviceManager.stop() is a no-op until run() has started. main() now skips run() once the listener has finished; otherwise an unknown MAC would block forever.

Behaviour changes for existing commands

  • --connect / --auto connection failures now exit 1 instead of 0.

Tests

  • 47 tests, 91% coverage (was 27 tests, 81%).
  • A new simulated_tap_timer fixture plays connect → resolve → write → disconnect through the real TapTimer and holmanctl code, with switches for connect and write failures.
  • The fake DeviceManager.stop() now matches gatt's no-op-before-run() behaviour.
  • Mutation-checked: removing the base-class methods, the disconnect after writing, or the finished check each makes its test fail.

Test plan

  • uv run pytest (47 passed), ruff check, ruff format --check, pyrefly check
  • CI passes
  • Hardware: --run 1 starts the tap and the process exits 0; --stop stops it; an out-of-range or wrong MAC exits 1 without hanging
  • Hardware: after --run, the phone app can connect straight away (confirms the disconnect)

🤖 Generated with Claude Code

https://claude.ai/code/session_01T53VSjKogcDk3z3avG76Ay

Re-implements #3 on the current codebase so holmanctl can be used from
scripts and cron: `holmanctl --connect <MAC> --run MINUTES` or `--stop`
connects, writes the command, disconnects and exits.

SDK:
- TapTimerListener gains timer_written() and timer_write_failed(error)
  events, fired for writes to the manual characteristic. The base class
  provides no-op defaults so existing listeners keep working.

holmanctl:
- --run takes 1-255 minutes, validated by argparse; --run and --stop are
  mutually exclusive and require --connect
- Disconnects after the write instead of leaving BlueZ holding the
  connection
- Exits 1 if connecting or writing fails, instead of 0. main() now
  returns the exit code rather than calling sys.exit() inside callbacks
- Skips run() when the outcome is already known, since gatt can report a
  connect failure synchronously before the loop starts, and stop() is a
  no-op then; run() would otherwise block forever

Co-authored-by: Hamish Moffatt <hamish@moffatt.id.au>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T53VSjKogcDk3z3avG76Ay
@scottmckenzie scottmckenzie self-assigned this Oct 15, 2026
@scottmckenzie
scottmckenzie merged commit bbf545d into master Oct 15, 2026
4 checks passed
@scottmckenzie
scottmckenzie deleted the feature/holmanctl-run-stop branch October 15, 2026 08:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant