kestrel-pi 0.3.0__tar.gz

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.
Files changed (64) hide show
  1. kestrel_pi-0.3.0/PKG-INFO +25 -0
  2. kestrel_pi-0.3.0/README.md +17 -0
  3. kestrel_pi-0.3.0/kestrel_pi/__init__.py +27 -0
  4. kestrel_pi-0.3.0/kestrel_pi/agent/__init__.py +94 -0
  5. kestrel_pi-0.3.0/kestrel_pi/agent/controller.py +1109 -0
  6. kestrel_pi-0.3.0/kestrel_pi/agent/env.py +245 -0
  7. kestrel_pi-0.3.0/kestrel_pi/agent/features.py +256 -0
  8. kestrel_pi-0.3.0/kestrel_pi/agent/metrics.py +250 -0
  9. kestrel_pi-0.3.0/kestrel_pi/agent/policies.py +283 -0
  10. kestrel_pi-0.3.0/kestrel_pi/agent/readout.py +159 -0
  11. kestrel_pi-0.3.0/kestrel_pi/agent/spec.py +253 -0
  12. kestrel_pi-0.3.0/kestrel_pi/agent/train.py +515 -0
  13. kestrel_pi-0.3.0/kestrel_pi/applyclient.py +40 -0
  14. kestrel_pi-0.3.0/kestrel_pi/capture.py +281 -0
  15. kestrel_pi-0.3.0/kestrel_pi/config.py +66 -0
  16. kestrel_pi-0.3.0/kestrel_pi/config_catalog.py +312 -0
  17. kestrel_pi-0.3.0/kestrel_pi/connectivity.py +220 -0
  18. kestrel_pi-0.3.0/kestrel_pi/context.py +151 -0
  19. kestrel_pi-0.3.0/kestrel_pi/daemon.py +577 -0
  20. kestrel_pi-0.3.0/kestrel_pi/dispatch.py +129 -0
  21. kestrel_pi-0.3.0/kestrel_pi/loader.py +374 -0
  22. kestrel_pi-0.3.0/kestrel_pi/options.py +81 -0
  23. kestrel_pi-0.3.0/kestrel_pi/plugins.py +259 -0
  24. kestrel_pi-0.3.0/kestrel_pi/ui.py +194 -0
  25. kestrel_pi-0.3.0/kestrel_pi/userguide.py +771 -0
  26. kestrel_pi-0.3.0/kestrel_pi/vendor/__init__.py +7 -0
  27. kestrel_pi-0.3.0/kestrel_pi/vendor/waveshare_epd/__init__.py +11 -0
  28. kestrel_pi-0.3.0/kestrel_pi/vendor/waveshare_epd/epd2in7.py +527 -0
  29. kestrel_pi-0.3.0/kestrel_pi/vendor/waveshare_epd/epd2in7_V2.py +519 -0
  30. kestrel_pi-0.3.0/kestrel_pi/vendor/waveshare_epd/epdconfig.py +322 -0
  31. kestrel_pi-0.3.0/kestrel_pi/web.py +3106 -0
  32. kestrel_pi-0.3.0/kestrel_pi.egg-info/PKG-INFO +25 -0
  33. kestrel_pi-0.3.0/kestrel_pi.egg-info/SOURCES.txt +62 -0
  34. kestrel_pi-0.3.0/kestrel_pi.egg-info/dependency_links.txt +1 -0
  35. kestrel_pi-0.3.0/kestrel_pi.egg-info/entry_points.txt +4 -0
  36. kestrel_pi-0.3.0/kestrel_pi.egg-info/top_level.txt +1 -0
  37. kestrel_pi-0.3.0/pyproject.toml +27 -0
  38. kestrel_pi-0.3.0/setup.cfg +4 -0
  39. kestrel_pi-0.3.0/tests/test_agent_capture_gate.py +207 -0
  40. kestrel_pi-0.3.0/tests/test_agent_env.py +215 -0
  41. kestrel_pi-0.3.0/tests/test_agent_features.py +108 -0
  42. kestrel_pi-0.3.0/tests/test_agent_metrics.py +175 -0
  43. kestrel_pi-0.3.0/tests/test_agent_policies.py +189 -0
  44. kestrel_pi-0.3.0/tests/test_agent_readout.py +93 -0
  45. kestrel_pi-0.3.0/tests/test_agent_spec.py +156 -0
  46. kestrel_pi-0.3.0/tests/test_agent_train_reward.py +137 -0
  47. kestrel_pi-0.3.0/tests/test_agent_training.py +496 -0
  48. kestrel_pi-0.3.0/tests/test_capture.py +175 -0
  49. kestrel_pi-0.3.0/tests/test_capture_collect.py +135 -0
  50. kestrel_pi-0.3.0/tests/test_config_catalog.py +65 -0
  51. kestrel_pi-0.3.0/tests/test_config_set.py +195 -0
  52. kestrel_pi-0.3.0/tests/test_connectivity.py +134 -0
  53. kestrel_pi-0.3.0/tests/test_context.py +82 -0
  54. kestrel_pi-0.3.0/tests/test_daemon.py +169 -0
  55. kestrel_pi-0.3.0/tests/test_dispatch.py +103 -0
  56. kestrel_pi-0.3.0/tests/test_eink.py +607 -0
  57. kestrel_pi-0.3.0/tests/test_loader.py +264 -0
  58. kestrel_pi-0.3.0/tests/test_options.py +35 -0
  59. kestrel_pi-0.3.0/tests/test_plugins_apply.py +980 -0
  60. kestrel_pi-0.3.0/tests/test_reference_plugins.py +2262 -0
  61. kestrel_pi-0.3.0/tests/test_ui.py +70 -0
  62. kestrel_pi-0.3.0/tests/test_update_apply.py +360 -0
  63. kestrel_pi-0.3.0/tests/test_updates.py +566 -0
  64. kestrel_pi-0.3.0/tests/test_web.py +1938 -0
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.4
2
+ Name: kestrel-pi
3
+ Version: 0.3.0
4
+ Summary: Kestrel plugin host daemon and plugin framework (Phase 3)
5
+ License: GPL-3.0-or-later
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+
9
+ # kestrel_pi
10
+
11
+ The Phase 3 plugin host for kestrel: a small daemon
12
+ (`kestrel-daemon`) that loads plugins, dispatches the legacy-compatible
13
+ `on_*` hooks off its event loop, sources events from the Phase 1 connectivity
14
+ and the Phase 2 capture store, and serves an embedded Flask status page on the
15
+ management link.
16
+
17
+ This package is installed into the `pi` user's virtualenv (`/home/pi/.venv`) by
18
+ the image build. The reference plugins ship separately under
19
+ `/usr/local/lib/kestrel/plugins/` and operator plugins live in
20
+ `/etc/kestrel/plugins/`.
21
+
22
+ See `docs/design/phase-3-plugins.md` in the repository for the design, and the
23
+ `Plugin` base class in `kestrel_pi/plugins.py` for the API.
24
+
25
+ Run the tests with `pytest` from this directory (pytest is already in the venv).
@@ -0,0 +1,17 @@
1
+ # kestrel_pi
2
+
3
+ The Phase 3 plugin host for kestrel: a small daemon
4
+ (`kestrel-daemon`) that loads plugins, dispatches the legacy-compatible
5
+ `on_*` hooks off its event loop, sources events from the Phase 1 connectivity
6
+ and the Phase 2 capture store, and serves an embedded Flask status page on the
7
+ management link.
8
+
9
+ This package is installed into the `pi` user's virtualenv (`/home/pi/.venv`) by
10
+ the image build. The reference plugins ship separately under
11
+ `/usr/local/lib/kestrel/plugins/` and operator plugins live in
12
+ `/etc/kestrel/plugins/`.
13
+
14
+ See `docs/design/phase-3-plugins.md` in the repository for the design, and the
15
+ `Plugin` base class in `kestrel_pi/plugins.py` for the API.
16
+
17
+ Run the tests with `pytest` from this directory (pytest is already in the venv).
@@ -0,0 +1,27 @@
1
+ """kestrel plugin host daemon and plugin framework (Phase 3).
2
+
3
+ The public plugin API is re-exported here so plugins can write either
4
+
5
+ from kestrel_pi import Plugin
6
+
7
+ or the fully-qualified
8
+
9
+ from kestrel_pi.plugins import Plugin
10
+
11
+ Both are the same class. ``HOOK_API_VERSION`` is the daemon's current hook-API
12
+ version; a plugin declares the version it targets with ``__target_api__`` and
13
+ the loader compares the two (improvement 4).
14
+ """
15
+
16
+ __version__ = "0.3.0"
17
+
18
+ # Bump this on any breaking change to a dispatched hook's signature. Phase 5
19
+ # gives ``on_ui_update`` its real second argument - a read-only device/agent
20
+ # state snapshot (:class:`~kestrel_pi.ui.UIState`) - now that a physical
21
+ # e-ink renderer exists, so this is 2. A plugin written for the new signature
22
+ # declares ``__target_api__ = 2``; one still pinned to 1 loads with a warning.
23
+ HOOK_API_VERSION = 2
24
+
25
+ from .plugins import CAPABILITIES, Plugin # noqa: E402 (re-export after constants)
26
+
27
+ __all__ = ["Plugin", "CAPABILITIES", "HOOK_API_VERSION", "__version__"]
@@ -0,0 +1,94 @@
1
+ """The Phase 4 reinforcement-learning capture agent.
2
+
3
+ This subpackage is the learning agent that drives capture and learns which
4
+ channels, dwells and attack postures yield handshakes, replacing the Phase 2
5
+ scripted sweep with a policy that adapts to the RF environment. It reads its
6
+ observations and issues its control through the Phase 2 capture interface
7
+ (``docs/developer/capture-interface.md``, ``contract_version 1``) and never patches
8
+ AngryOxide, so it only ever runs on the capture dongle when capture is enabled.
9
+
10
+ Layout (see ``docs/design/phase-4-agent.md``):
11
+
12
+ - :mod:`.spec` - the *fixed* env contract: observation shape, the flattened
13
+ ``Discrete`` action vocabulary (posture x dwell x transmit) and its bijective
14
+ decode, the reward, and channel ranking. Pure stdlib + numpy; no torch/gym,
15
+ no hardware, so it is the unit-test seam and the thing a model is bound to.
16
+ - :mod:`.features` - builds the observation vector from the capture status and
17
+ the append-only store delta (the stable Output surface).
18
+ - :mod:`.env` - the Gymnasium environment wrapping the capture interface.
19
+ - :mod:`.policies` - the config-selected SB3 algorithm map and the scripted
20
+ baseline policy (both load against the same env spaces).
21
+ - :mod:`.controller` - the agent process: the decide loop, the status file and
22
+ the episode logs. ``kestrel-agent.service`` runs it.
23
+ - :mod:`.metrics` - the agent-vs-sweep comparison, derived from the episode log.
24
+ - :mod:`.train` - offline training: replay the episode logs through SB3 into an
25
+ updated ``model.zip``.
26
+ - :mod:`.readout` - stdlib-only reader of the agent's ``status.json`` for the
27
+ daemon's dashboard card (never imports torch/gym, so the benign daemon can
28
+ import it with the heavy ML stack absent).
29
+
30
+ Only :mod:`.spec`, :mod:`.features`, :mod:`.metrics` and :mod:`.readout` are
31
+ safe to import without gymnasium/torch installed; :mod:`.env`, :mod:`.policies`
32
+ and :mod:`.controller` lazy-import the heavy stack. ``__init__`` imports none of
33
+ them, so importing :mod:`kestrel_pi.agent` costs nothing.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ # The agent's runtime directory (owned by pi, created by the build). All of the
39
+ # agent's persistent artifacts live here; the boot-partition importer and the
40
+ # backup plugin reference these names.
41
+ AGENT_DIR = "/var/lib/kestrel/agent"
42
+ # Each algorithm keeps its own model file (``model-<slug>.zip`` + a matching
43
+ # ``.meta.json`` sidecar), so switching algorithm and back reloads the saved
44
+ # model instead of retraining from scratch. ``MODEL_NAME``/``MODEL_META_NAME``
45
+ # are the pre-per-algorithm single-model names, kept only as a load-time
46
+ # migration fallback for a device that trained/imported one before this change.
47
+ MODEL_NAME = "model.zip"
48
+ MODEL_META_NAME = "model.meta.json"
49
+ STATUS_NAME = "status.json"
50
+ # Records the last on-device idle-training run (finished-at + duration) so the
51
+ # dashboard can show it and it survives an agent/daemon restart.
52
+ LAST_TRAIN_NAME = "last-train.json"
53
+ # A manual "train now" request from the dashboard: the config tool drops this
54
+ # file and the agent's training thread consumes it to start a run promptly.
55
+ TRAIN_REQUEST_NAME = "train-request"
56
+ # A running count of model adoptions by source over the device's lifetime: how
57
+ # many times a locally trained model vs a promoted remote model became the
58
+ # active model. Persisted so the dashboard can show which side is winning, and
59
+ # so it survives an agent/daemon restart.
60
+ PROMOTION_TALLY_NAME = "promotion-tally.json"
61
+ EPISODES_DIR = "episodes"
62
+ # Where the remote-train plugin drops a pulled remote model for the agent to
63
+ # evaluate: ``<AGENT_DIR>/.remote-staging/<slug>/model-<slug>.{zip,meta.json}``.
64
+ # The plugin only pulls-and-stages here (torch-free); the agent process scores
65
+ # the staged model against the active one and promotes the winner (quality-aware
66
+ # promotion, docs/design/agent-promotion-quality.md).
67
+ REMOTE_STAGING_DIR = ".remote-staging"
68
+
69
+
70
+ def model_slug(algorithm):
71
+ """A filesystem-safe lowercase slug for an algorithm name, used to name its
72
+ per-algorithm model file (e.g. ``RecurrentPPO`` -> ``recurrentppo``,
73
+ ``QR-DQN`` -> ``qr-dqn``). Pure stdlib so the daemon/CLI can derive the
74
+ filename without importing the heavy agent stack."""
75
+ text = (algorithm or "").strip().lower()
76
+ slug = "".join(ch if (ch.isalnum() or ch == "-") else "-" for ch in text)
77
+ slug = slug.strip("-")
78
+ return slug or "default"
79
+
80
+
81
+ def model_name_for(algorithm):
82
+ """The per-algorithm model filename (e.g. ``model-dqn.zip``)."""
83
+ return "model-%s.zip" % model_slug(algorithm)
84
+
85
+
86
+ def model_meta_name_for(algorithm):
87
+ """The per-algorithm model sidecar filename (e.g. ``model-dqn.meta.json``)."""
88
+ return "model-%s.meta.json" % model_slug(algorithm)
89
+
90
+
91
+ __all__ = ["AGENT_DIR", "MODEL_NAME", "MODEL_META_NAME", "STATUS_NAME",
92
+ "LAST_TRAIN_NAME", "TRAIN_REQUEST_NAME", "PROMOTION_TALLY_NAME",
93
+ "EPISODES_DIR", "REMOTE_STAGING_DIR", "model_slug", "model_name_for",
94
+ "model_meta_name_for"]