Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/base-station.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ on:
push:
branches: [main]
paths:
- "tools/dev_gui/**"
- "packages/dev_gui/**"
- "packages/sfm-analysis/**"
- ".github/workflows/base-station.yml"
pull_request:
paths:
- "tools/dev_gui/**"
- "packages/dev_gui/**"
- "packages/sfm-analysis/**"
- ".github/workflows/base-station.yml"
workflow_dispatch:
Expand All @@ -26,7 +26,7 @@ jobs:
# everything else is importable and testable on a plain Linux runner.
# The remaining ~26 tests (test_app.py, test_app_pellet_counting.py,
# test_schedule.py, test_hat.py) are Pi-only and run manually — see
# tools/dev_gui/README.md.
# packages/dev_gui/README.md.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -35,14 +35,14 @@ jobs:
python-version: "3.11"
- run: pip install -e packages/sfm-analysis pytest python-can
- name: syntax-check everything, including the GUI modules we cannot import
run: python -m compileall -q tools/dev_gui/base_station tools/dev_gui/*.py
run: python -m compileall -q packages/dev_gui/base_station packages/dev_gui/*.py
- name: run the hardware-free base-station tests
working-directory: tools/dev_gui
working-directory: packages/dev_gui
run: |
pytest -q \
tests/test_protocol.py tests/test_log_manager.py \
tests/test_behavioral_log_names.py tests/test_node_registry.py \
tests/test_pellet_ledger.py tests/test_mac_id_registry.py \
tests/test_discovery_manager.py tests/test_experiment.py \
tests/test_experiment_schema.py tests/test_script.py \
tests/test_two_armed_bandit.py tests/test_run.py
tests/test_two_armed_bandit.py
52 changes: 31 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
# VFM

Spatial Foraging Platform: node firmware, base-station developer GUI, and
session analysis. Each application lives in its own directory.

## Launch the GUI

```
cd Project/VFM/tools/dev_gui
cd Project/VFM/packages/dev_gui
python run.py
```

## Generate a behavior report

```
cd Project/VFM/tools/dev_gui
cd Project/VFM/packages/dev_gui
python run_report.py
```

With no arguments this opens an interactive session picker. For list/combine, date filters, designs, and other use cases, see [Behavior reports](tools/dev_gui/README.md#behavior-reports-session-csv--printable-html) in the GUI README.
With no arguments this opens an interactive session picker. For list/combine, date filters, designs, and other use cases, see [Behavior reports](packages/dev_gui/README.md#behavior-reports-session-csv--printable-html) in the GUI README.

Report generation itself is cross-platform — it doesn't need a Raspberry Pi or any base-station hardware library. It ships as its own package, [`sfm-analysis`](packages/sfm-analysis/), installable with `pip install sfm-analysis` on Windows, macOS, or Linux; `run_report.py` above is a thin wrapper around its `sfm-report` CLI. See [packages/sfm-analysis/README.md](packages/sfm-analysis/README.md).

## About

VFM is a firmware library for the Spatial Foraging Platform node. It provides non-blocking, service-oriented stepper-driven pellet dispensing, CAN bus communication with a base-station command/event/heartbeat protocol.

The Arduino library is the `firmware/` folder (not the repo root). Copy or symlink `firmware/` into `Arduino/libraries/VFM`, or zip that folder and add it via *Sketch → Include Library → Add .ZIP Library…*.

## Documentation

| Doc | What it's for |
|-----|---------------|
| [tools/dev_gui/README.md](tools/dev_gui/README.md) | Base-station GUI: install, run, CAN bring-up, experiments, session logs, and behavior reports |
| [tools/dev_gui/deploy/README.md](tools/dev_gui/deploy/README.md) | One-time Raspberry Pi CAN HAT setup (`can0`) before running against real hardware |
| [tools/dev_gui/base_station/experiment/README.md](tools/dev_gui/base_station/experiment/README.md) | How to author custom experiment templates (Python API + JSON params) |
| [packages/dev_gui/README.md](packages/dev_gui/README.md) | Base-station GUI: install, run, CAN bring-up, experiments, session logs, and behavior reports |
| [packages/dev_gui/deploy/README.md](packages/dev_gui/deploy/README.md) | One-time Raspberry Pi CAN HAT setup (`can0`) before running against real hardware |
| [packages/dev_gui/base_station/experiment/README.md](packages/dev_gui/base_station/experiment/README.md) | How to author custom experiment templates (Python API + JSON params) |
| [packages/sfm-analysis/README.md](packages/sfm-analysis/README.md) | Cross-platform session analysis and report generation — install, CLI, report design/section architecture, Python API |
| [packages/sfm-analysis/docs/ANALYSIS_GUIDE.md](packages/sfm-analysis/docs/ANALYSIS_GUIDE.md) | Analysis reference: every log column, event name, derived metric field, and tidy-table column, plus a worked example |
| [docs/WIRING.md](docs/WIRING.md) | Firmware pin names → physical jobs (motors, sensors, CAN, LEDs, discovery) |
| [docs/DISPENSE_CYCLE.md](docs/DISPENSE_CYCLE.md) | How a node loads, raises, and reports pellet take / faults during a dispense |
| [docs/HARDCODED_VALUES.md](docs/HARDCODED_VALUES.md) | Tunable firmware constants checklist (timings, speeds, thresholds) |
| [docs/BASE_STATION_HARDCODED_VALUES.md](docs/BASE_STATION_HARDCODED_VALUES.md) | Base-station timings and defaults (GUI, experiments, reports) |
| [packages/sfm-analysis/docs/PYPI.md](packages/sfm-analysis/docs/PYPI.md) | Publishing `sfm-analysis` to PyPI (`pip install sfm-analysis`) |
| [firmware/docs/WIRING.md](firmware/docs/WIRING.md) | Firmware pin names → physical jobs (motors, sensors, CAN, LEDs, discovery) |
| [firmware/docs/DISPENSE_CYCLE.md](firmware/docs/DISPENSE_CYCLE.md) | How a node loads, raises, and reports pellet take / faults during a dispense |
| [firmware/docs/HARDCODED_VALUES.md](firmware/docs/HARDCODED_VALUES.md) | Tunable firmware constants checklist (timings, speeds, thresholds) |
| [packages/dev_gui/docs/BASE_STATION_HARDCODED_VALUES.md](packages/dev_gui/docs/BASE_STATION_HARDCODED_VALUES.md) | Base-station timings and defaults (GUI, experiments, reports) |

## Project structure

```
VFM/
├── src/ # Arduino library (ESP32-S3 node firmware)
│ ├── VFM.h / VFM.cpp # Library entry point
│ ├── hardware/ # Pin definitions
│ └── services/ # CAN bus, dispenser, node identity, presence, LED services
├── examples/ # Arduino sketches
│ ├── Node/ # Main node firmware sketch
│ └── Troubleshooting/ # Hardware bring-up / diagnostic sketches
├── docs/ # Wiring, dispense cycle, and hardcoded-value reference docs
├── tools/dev_gui/ # Python base-station GUI + CAN tooling (see tools/dev_gui/README.md)
├── packages/sfm-analysis/ # Cross-platform session analysis / report SDK (pip install sfm-analysis)
└── library.properties # Arduino library metadata
├── firmware/ # Arduino library (ESP32-S3 node)
│ ├── src/ # VFM.h / services / pin definitions
│ ├── examples/ # Node sketch + hardware bring-up sketches
│ ├── docs/ # Wiring, dispense cycle, firmware tunables
│ └── library.properties
├── packages/
│ ├── dev_gui/ # Raspberry Pi developer GUI + CAN tooling
│ │ ├── experiments/ # JSON schemas for built-in experiments
│ │ ├── examples/templates/ # Copy-from custom experiment starters
│ │ └── base_station/experiment/ # Engine + templates (see its README)
│ └── sfm-analysis/ # Cross-platform session analysis / report SDK
│ ├── docs/ANALYSIS_GUIDE.md
│ ├── docs/PYPI.md # To publish a new sfm-analysis version (Developer only)
│ └── examples/ # Analysis recipes + a custom report-design starter
└── README.md
```
3 changes: 2 additions & 1 deletion docs/DISPENSE_CYCLE.md → firmware/docs/DISPENSE_CYCLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ Node → base on CAN ID `0x300 + nodeId`. Byte 0 is the event code.
| `0x0E` | `NoFeedPresented` | count LE16 (unchanged) | A no-feed raise finished; empty plate at the top. `count` does NOT increment |
| `0x0F` | `Dwelling` | count LE16 | Phase entered: holding at the drop position, M1 idle (no-feed cycle) |
| `0x10` | `PresenceCalResult` | ok(1), threshold LE32, samples LE16 | Response to a `CalibratePresence` command — see below |
| `0x11` | `ConfigApplied` | configType(1), ok(1), value LE32 | Ack of a `SetConfig` (heartbeat interval, presence factor) |


`count` is the node's running total of `Loaded` milestones — `NoFeedPresented` deliberately does not advance
Expand All @@ -226,7 +227,7 @@ the number that appears in logs or reports. The base station keeps its own per-r
at zero when a session opens, and folds the node's counter in as a **delta** so a milestone frame lost to the
bus is recovered by the next frame carrying the count (or by the next heartbeat) and reported as a gap rather
than silently dropped. The node counter is the independent witness that makes that detection possible, which
is why the firmware still sends it. See `tools/dev_gui/base_station/pellet_ledger.py`.
is why the firmware still sends it. See `packages/dev_gui/base_station/pellet_ledger.py`.

**Presence recalibration.** `CanCmd::CalibratePresence` (`0x09`, no payload, broadcast-friendly) starts a fresh
5 s idle-pad capture on the presence sensor — the same action as a short `PIN_BTN` click. The node replies with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Living note of firmware constants that may need changing after bench or field tweaks.
**Update this file when you change a default.** Source of truth remains the code; this is the checklist.

GUI / experiment / report defaults: [BASE_STATION_HARDCODED_VALUES.md](BASE_STATION_HARDCODED_VALUES.md).
GUI / experiment / report defaults: [BASE_STATION_HARDCODED_VALUES.md](../../packages/dev_gui/docs/BASE_STATION_HARDCODED_VALUES.md).

Pins (`VFMPins.h`) and CAN ID opcodes (`ServiceTypes.h`) are omitted unless they carry timing or motion meaning.
For what these timers guard and where they sit in the cycle, see [DISPENSE_CYCLE.md](DISPENSE_CYCLE.md).
Expand Down Expand Up @@ -67,7 +67,7 @@ Not overrideable via SetConfig CAN yet — only compile-time / setter before beg
**No tunables.** A no-feed cycle holds at the drop position until it sees a `Raising` event from another
node on the bus, then raises. There is no dwell constant, no clamp, and no payload on `DispenseNoFeed` —
the fed node's own raise is the timing reference, so nothing here needs tuning per rig. See
`docs/DISPENSE_CYCLE.md` § No-feed dispense.
[DISPENSE_CYCLE.md](DISPENSE_CYCLE.md) § No-feed dispense.

There is also no timeout: a node whose peer never raises holds until `Recover`.

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading