Add environment-driven digital probe mode
This commit is contained in:
parent
935395797a
commit
0a87fa71f1
14
.env.example
14
.env.example
@ -39,6 +39,20 @@ UART_STOP_BITS=1
|
||||
UART_TX_GPIO=20
|
||||
UART_RX_GPIO=21
|
||||
|
||||
# ---------- Read-only development probe ----------
|
||||
# Keep disabled until a safe, conditioned front-panel signal is connected.
|
||||
PROBE_ENABLED=false
|
||||
# Required when PROBE_ENABLED=true. GPIO3 is a candidate on the C3 Super Mini,
|
||||
# but confirm the board routing and electrical interface before using it.
|
||||
PROBE_GPIO=
|
||||
# state | frequency | pulse | watch
|
||||
PROBE_MODE=frequency
|
||||
PROBE_INTERVAL_SECONDS=3
|
||||
PROBE_FREQUENCY_SECONDS=2
|
||||
PROBE_PULSE_TIMEOUT_US=1000000
|
||||
PROBE_WATCH_SECONDS=5
|
||||
PROBE_WATCH_MAX_EVENTS=200
|
||||
|
||||
# ---------- Self-update ----------
|
||||
UPDATE_ENABLED=true
|
||||
|
||||
|
||||
@ -38,6 +38,25 @@ probe.pulse(timeout_us=1_000_000)
|
||||
probe.watch(seconds=5, max_events=200)
|
||||
```
|
||||
|
||||
## Continuous probe mode
|
||||
|
||||
To collect data without interrupting the application, set the following in the
|
||||
device-local `/.env` and reset the board:
|
||||
|
||||
```env
|
||||
PROBE_ENABLED=true
|
||||
PROBE_GPIO=3
|
||||
PROBE_MODE=frequency
|
||||
PROBE_FREQUENCY_SECONDS=2
|
||||
PROBE_INTERVAL_SECONDS=3
|
||||
```
|
||||
|
||||
`PROBE_MODE` may be `state`, `frequency`, `pulse`, or `watch`. The corresponding
|
||||
`PROBE_*` settings in `.env.example` control the measurement window, timeout,
|
||||
or event cap. The mode continuously writes its results to the serial console;
|
||||
it does not expose any output pin or machine-control capability. Set
|
||||
`PROBE_ENABLED=false` and reset to return to normal application operation.
|
||||
|
||||
`frequency()` counts rising edges in its measurement window. It is useful for
|
||||
signal discovery and approximate rates, but very fast or noisy signals can
|
||||
overflow or exceed MicroPython IRQ handling capacity. `pulse()` reports one
|
||||
|
||||
@ -10,6 +10,63 @@ except ImportError:
|
||||
import asyncio
|
||||
|
||||
|
||||
def as_bool(value, default=False):
|
||||
if value is None:
|
||||
return default
|
||||
return str(value).strip().lower() in ("1", "true", "yes", "on", "enabled")
|
||||
|
||||
|
||||
def as_int(value, default=0, minimum=None):
|
||||
try:
|
||||
result = int(str(value).strip())
|
||||
except Exception:
|
||||
result = default
|
||||
if minimum is not None:
|
||||
result = max(minimum, result)
|
||||
return result
|
||||
|
||||
|
||||
async def run_probe_mode(ctx):
|
||||
"""Continuously collect read-only probe data while the app keeps running."""
|
||||
env = ctx["env"]
|
||||
pin_text = env.get("PROBE_GPIO", "").strip()
|
||||
if not pin_text:
|
||||
raise RuntimeError("PROBE_ENABLED=true requires PROBE_GPIO")
|
||||
try:
|
||||
pin_id = int(pin_text)
|
||||
except Exception:
|
||||
raise RuntimeError("PROBE_GPIO must be an integer GPIO number")
|
||||
|
||||
mode = env.get("PROBE_MODE", "frequency").strip().lower()
|
||||
if mode not in ("state", "frequency", "pulse", "watch"):
|
||||
raise RuntimeError("Unknown PROBE_MODE=" + mode)
|
||||
|
||||
from app.digital_probe import open_probe
|
||||
probe = open_probe(pin_id)
|
||||
frequency_seconds = as_int(env.get("PROBE_FREQUENCY_SECONDS"), 2, 1)
|
||||
watch_seconds = as_int(env.get("PROBE_WATCH_SECONDS"), 5, 1)
|
||||
watch_events = as_int(env.get("PROBE_WATCH_MAX_EVENTS"), 200, 1)
|
||||
pulse_timeout_us = as_int(env.get("PROBE_PULSE_TIMEOUT_US"), 1_000_000, 1)
|
||||
interval_seconds = as_int(env.get("PROBE_INTERVAL_SECONDS"), 3, 1)
|
||||
activity = ctx["activity"]
|
||||
|
||||
print("[probe] mode=%s interval=%ss" % (mode, interval_seconds))
|
||||
while True:
|
||||
# Measurements are data collection, not a machine action, but marking
|
||||
# activity prevents an OTA reboot from interrupting a capture.
|
||||
activity.touch()
|
||||
if mode == "state":
|
||||
probe.state()
|
||||
elif mode == "frequency":
|
||||
probe.frequency(frequency_seconds)
|
||||
elif mode == "pulse":
|
||||
probe.pulse(pulse_timeout_us)
|
||||
else:
|
||||
probe.watch(watch_seconds, watch_events)
|
||||
activity.touch()
|
||||
await asyncio.sleep_ms(interval_seconds * 1000)
|
||||
|
||||
|
||||
async def setup(ctx):
|
||||
# Configure GPIOs, state machines, etc. here.
|
||||
# If this raises immediately after an OTA, boot.py can roll back.
|
||||
@ -17,6 +74,10 @@ async def setup(ctx):
|
||||
|
||||
|
||||
async def main(ctx):
|
||||
if as_bool(ctx["env"].get("PROBE_ENABLED"), False):
|
||||
await run_probe_mode(ctx)
|
||||
return
|
||||
|
||||
activity = ctx["activity"]
|
||||
uart = ctx["uart"]
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"release": "0.1.1-probe.1",
|
||||
"release": "0.1.2-probe.2",
|
||||
"files": [
|
||||
{
|
||||
"source": "firmware/launcher.py",
|
||||
@ -20,7 +20,7 @@
|
||||
{
|
||||
"source": "firmware/app/main.py",
|
||||
"target": "/app/main.py",
|
||||
"sha256": "5b287d984df266007df4014ece8e323339c0da594f4c88bcf4a6da1ff067a16f"
|
||||
"sha256": "7eaa8b4b8f213f44a080c82a1f76c8981b06458fa4b05e239a7d6c8cdc114c18"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user