diff --git a/README.md b/README.md index 1683eed..546a0a1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# VFM +# SFM Spatial Foraging Platform: node firmware, base-station developer GUI, and session analysis. Each application lives in its own directory. @@ -6,9 +6,9 @@ session analysis. Each application lives in its own directory. # Project structure ``` -VFM/ +SFM/ ├── firmware/ # Arduino library (ESP32-S3 node) -│ ├── src/ # VFM.h / services / pin definitions +│ ├── src/ # SFM.h / services / pin definitions │ ├── examples/ # Node sketch + hardware bring-up sketches │ ├── docs/ # Wiring, dispense cycle, firmware tunables │ └── library.properties @@ -27,14 +27,14 @@ VFM/ ## Launch the GUI ``` -cd Project/VFM/packages/dev_gui +cd Project/SFM/packages/dev_gui python run.py ``` ## Generate a behavior report ``` -cd Project/VFM/packages/dev_gui +cd Project/SFM/packages/dev_gui python run_report.py ``` @@ -44,9 +44,9 @@ Report generation itself is cross-platform — it doesn't need a Raspberry Pi or ## About -VFM is a firmware library for the Spatial Foraging Platform node. It provides non-blocking, service-oriented stepper-driven pellet dispensing, and talks to the base station over **CAN** (Controller Area Network) — the shared communication bus every node is wired onto. A **CAN event** is a message a node posted on that bus (Loaded, Pellet Taken, Fault, …). +SFM is a firmware library for the Spatial Foraging Platform node. It provides non-blocking, service-oriented stepper-driven pellet dispensing, and talks to the base station over **CAN** (Controller Area Network) — the shared communication bus every node is wired onto. A **CAN event** is a message a node posted on that bus (Loaded, Pellet Taken, Fault, …). -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…*. +The Arduino library is the `firmware/` folder (not the repo root). Copy or symlink `firmware/` into `Arduino/libraries/SFM`, or zip that folder and add it via *Sketch → Include Library → Add .ZIP Library…*. ## Documentation @@ -66,9 +66,9 @@ The Arduino library is the `firmware/` folder (not the repo root). Copy or symli ## Project structure ``` -VFM/ +SFM/ ├── firmware/ # Arduino library (ESP32-S3 node) -│ ├── src/ # VFM.h / services / pin definitions +│ ├── src/ # SFM.h / services / pin definitions │ ├── examples/ # Node sketch + hardware bring-up sketches │ ├── docs/ # Wiring, dispense cycle, firmware tunables │ └── library.properties diff --git a/firmware/docs/DISPENSE_CYCLE.md b/firmware/docs/DISPENSE_CYCLE.md index 0f930c2..6e2a832 100644 --- a/firmware/docs/DISPENSE_CYCLE.md +++ b/firmware/docs/DISPENSE_CYCLE.md @@ -1,6 +1,6 @@ # Dispense cycle -How a VFM node delivers a pellet, how it knows the pellet was taken, and what it reports along the way. +How a SFM node delivers a pellet, how it knows the pellet was taken, and what it reports along the way. This is the reference for the sensing model and the event vocabulary; tunable timings live in [HARDCODED_VALUES.md](HARDCODED_VALUES.md). Pin / motor and sensor wiring: [WIRING.md](WIRING.md). @@ -145,7 +145,7 @@ trial and the animal can't use sound alone to find the baited one. **The raise is triggered by another node, not by a timer.** A node in `Dwelling` watches the bus and raises the instant it sees a `Raising` event (`0x09`) from any other node. Every node's TWAI filter is `TWAI_FILTER_CONFIG_ACCEPT_ALL`, so peer event frames already arrive in each node's RX queue; `CanService` -surfaces them via `onPeerEvent`, and `VFM` forwards a peer `Raising` to `DispenserService::notifyPeerRaise()`. +surfaces them via `onPeerEvent`, and `SFM` forwards a peer `Raising` to `DispenserService::notifyPeerRaise()`. End to end the empty plate starts rising about one CAN frame time (~0.5 ms at 250 kbps) after the fed one. This replaced a commanded dwell, which could not do the job: a fed cycle's hold lasts however long M1 takes to diff --git a/firmware/docs/HARDCODED_VALUES.md b/firmware/docs/HARDCODED_VALUES.md index acd23da..c7f784a 100644 --- a/firmware/docs/HARDCODED_VALUES.md +++ b/firmware/docs/HARDCODED_VALUES.md @@ -5,7 +5,7 @@ Living note of firmware constants that may need changing after bench or field tw 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. +Pins (`SFMPins.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). --- @@ -164,16 +164,16 @@ the reading did not change, the decision boundary did. -## UI / LED / button (`VFM`) +## UI / LED / button (`SFM`) | Value | Where | Notes | | -------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| 3 s | `VFM` ctor → `btnHoldMs_(3000)` | Hold to arm NVS clear (`VFM.h` in-class default `1000` is overridden by ctor) | -| 50 ms | `kBtnClickMinMs` (`VFM.h`) | Minimum press for a click to count as "recalibrate presence"; shorter = bounce | -| 100 ms | `VFM.cpp` LED9 blink while hold armed | Rapid blink warning | +| 3 s | `SFM` ctor → `btnHoldMs_(3000)` | Hold to arm NVS clear (`SFM.h` in-class default `1000` is overridden by ctor) | +| 50 ms | `kBtnClickMinMs` (`SFM.h`) | Minimum press for a click to count as "recalibrate presence"; shorter = bounce | +| 100 ms | `SFM.cpp` LED9 blink while hold armed | Rapid blink warning | | 1.5 s / 150 ms | `kPingBlinkMs` / `kPingBlinkPeriodMs` | Status LED “which node” blink on Ping | -| 500 ms (50–5000 ms clamp) | `kDefaultSyncFlashMs` / `kMinSyncFlashMs` / `kMaxSyncFlashMs` (`VFM.h`) | Status LED solid-ON hold on `CanCmd::SyncFlash` (camera sync at session start) | +| 500 ms (50–5000 ms clamp) | `kDefaultSyncFlashMs` / `kMinSyncFlashMs` / `kMaxSyncFlashMs` (`SFM.h`) | Status LED solid-ON hold on `CanCmd::SyncFlash` (camera sync at session start) | | 500 ms | LED9 blink at boot | Fast blink = booting | | 1 s | LED9 / status blink | Slow = waiting for discovery | | — | LED9 during presence calibration | Solid ON for the whole capture; yields back to the dome mirror when done | diff --git a/firmware/docs/WIRING.md b/firmware/docs/WIRING.md index 31808e6..4e4d4f7 100644 --- a/firmware/docs/WIRING.md +++ b/firmware/docs/WIRING.md @@ -1,6 +1,6 @@ # Wiring map -Firmware pin names → physical job. Source of truth: `[src/hardware/VFMPins.h](../src/hardware/VFMPins.h)`. +Firmware pin names → physical job. Source of truth: `[src/hardware/SFMPins.h](../src/hardware/SFMPins.h)`. Cycle behaviour for the sensors: [DISPENSE_CYCLE.md](DISPENSE_CYCLE.md). ## Symbol → job diff --git a/firmware/examples/Node/Node.ino b/firmware/examples/Node/Node.ino index b9785a4..f1aa66c 100644 --- a/firmware/examples/Node/Node.ino +++ b/firmware/examples/Node/Node.ino @@ -1,7 +1,7 @@ -// Node – Full VFM node sketch. +// Node – Full SFM node sketch. // // Bring-up checklist: -// 1. Flash to an ESP32-S3-MINI-1 with the VFM hardware attached. +// 1. Flash to an ESP32-S3-MINI-1 with the SFM hardware attached. // 2. Open Serial Monitor at 115200 baud. // 3. On first boot the node has no ID; it will print "WaitAEI" until the // base station (or the test bench) drives GPIO14 HIGH. @@ -24,9 +24,9 @@ // Events node->base : 0x300 + nodeId on OnPlate/Loaded/DomeOpened/PelletTaken/Fault // Discovery node<->base: 0x080-0x083 -#include +#include -vfm::VFM gVfm; +sfm::SFM gSfm; // --------------------------------------------------------------------------- // Serial command helpers @@ -45,70 +45,70 @@ static void printHelp() { Serial.println(F(" clr clear NVS node ID (forces first-boot next reset)")); } -static const char *discStr(vfm::DiscoveryState s) { +static const char *discStr(sfm::DiscoveryState s) { switch (s) { - case vfm::DiscoveryState::WaitAEI: return "WaitAEI"; - case vfm::DiscoveryState::CheckNVS: return "CheckNVS"; - case vfm::DiscoveryState::Announce: return "Announce"; - case vfm::DiscoveryState::WaitAssign: return "WaitAssign"; - case vfm::DiscoveryState::Rejoin: return "Rejoin"; - case vfm::DiscoveryState::Enabled: return "Enabled"; + case sfm::DiscoveryState::WaitAEI: return "WaitAEI"; + case sfm::DiscoveryState::CheckNVS: return "CheckNVS"; + case sfm::DiscoveryState::Announce: return "Announce"; + case sfm::DiscoveryState::WaitAssign: return "WaitAssign"; + case sfm::DiscoveryState::Rejoin: return "Rejoin"; + case sfm::DiscoveryState::Enabled: return "Enabled"; } return "?"; } -static const char *stateStr(vfm::DispenseState s) { +static const char *stateStr(sfm::DispenseState s) { switch (s) { - case vfm::DispenseState::Idle: return "Idle"; - case vfm::DispenseState::Seeking: return "Seeking"; - case vfm::DispenseState::Lowering: return "Lowering"; - case vfm::DispenseState::Loading: return "Loading"; - case vfm::DispenseState::Raising: return "Raising"; - case vfm::DispenseState::Loaded: return "Loaded"; - case vfm::DispenseState::Dwelling: return "Dwelling"; - case vfm::DispenseState::Fault: return "Fault"; + case sfm::DispenseState::Idle: return "Idle"; + case sfm::DispenseState::Seeking: return "Seeking"; + case sfm::DispenseState::Lowering: return "Lowering"; + case sfm::DispenseState::Loading: return "Loading"; + case sfm::DispenseState::Raising: return "Raising"; + case sfm::DispenseState::Loaded: return "Loaded"; + case sfm::DispenseState::Dwelling: return "Dwelling"; + case sfm::DispenseState::Fault: return "Fault"; } return "?"; } static void printStatus() { - Serial.print(F("[VFM] nodeId=")); Serial.print(gVfm.identity().nodeId()); - Serial.print(F(" discovery=")); Serial.print(discStr(gVfm.identity().discoveryState())); - Serial.print(F(" dispense=")); Serial.print(stateStr(gVfm.dispenser().state())); - Serial.print(F(" pellets=")); Serial.print(gVfm.dispenser().pelletCount()); - Serial.print(F(" presence=")); Serial.print(gVfm.mousePresent()); - Serial.print(F(" pellet=")); Serial.print(gVfm.dispenser().pelletOnPlate()); - Serial.print(F(" load_position=")); Serial.print(gVfm.dispenser().atLoadPosition()); - Serial.print(F(" dome_open=")); Serial.println(gVfm.dispenser().domeOpen()); + Serial.print(F("[SFM] nodeId=")); Serial.print(gSfm.identity().nodeId()); + Serial.print(F(" discovery=")); Serial.print(discStr(gSfm.identity().discoveryState())); + Serial.print(F(" dispense=")); Serial.print(stateStr(gSfm.dispenser().state())); + Serial.print(F(" pellets=")); Serial.print(gSfm.dispenser().pelletCount()); + Serial.print(F(" presence=")); Serial.print(gSfm.mousePresent()); + Serial.print(F(" pellet=")); Serial.print(gSfm.dispenser().pelletOnPlate()); + Serial.print(F(" load_position=")); Serial.print(gSfm.dispenser().atLoadPosition()); + Serial.print(F(" dome_open=")); Serial.println(gSfm.dispenser().domeOpen()); } static void printPresence() { Serial.print(F("[PRESENCE] raw=")); - Serial.print(gVfm.presenceRaw()); + Serial.print(gSfm.presenceRaw()); Serial.print(F(" thr=")); - Serial.print(gVfm.presenceThreshold()); + Serial.print(gSfm.presenceThreshold()); Serial.print(F(" factor=")); - Serial.print(gVfm.presence().factor(), 2); - if (gVfm.presence().hasCalStats()) { + Serial.print(gSfm.presence().factor(), 2); + if (gSfm.presence().hasCalStats()) { Serial.print(F(" mean=")); - Serial.print(gVfm.presence().calMean(), 1); + Serial.print(gSfm.presence().calMean(), 1); Serial.print(F(" σ=")); - Serial.print(gVfm.presence().calStdDev(), 1); + Serial.print(gSfm.presence().calStdDev(), 1); } Serial.print(F(" -> ")); - Serial.println(gVfm.mousePresent() ? F("PRESENT") : F("clear")); + Serial.println(gSfm.mousePresent() ? F("PRESENT") : F("clear")); } // Report the outcome of a calibration started by serial or by the button. static void reportPresenceEvents() { - switch (gVfm.takePresenceEvent()) { - case vfm::PresenceEvent::CalibrationStarted: + switch (gSfm.takePresenceEvent()) { + case sfm::PresenceEvent::CalibrationStarted: Serial.print(F("[PRESENCE] CAL START - keep pad CLEAR for 5 s (LED9 solid) factor=")); - Serial.println(gVfm.presence().factor(), 2); + Serial.println(gSfm.presence().factor(), 2); break; - case vfm::PresenceEvent::CalibrationDone: { - const vfm::PresenceCalibration &c = gVfm.presence().lastCalibration(); + case sfm::PresenceEvent::CalibrationDone: { + const sfm::PresenceCalibration &c = gSfm.presence().lastCalibration(); Serial.print(F("[PRESENCE] CAL DONE samples=")); Serial.print(c.samples); Serial.print(F(" mean=")); Serial.print(c.mean, 1); Serial.print(F(" std_dev=")); Serial.print(c.stdDev, 1); @@ -119,7 +119,7 @@ static void reportPresenceEvents() { break; } - case vfm::PresenceEvent::CalibrationFailed: + case sfm::PresenceEvent::CalibrationFailed: Serial.println(F("[PRESENCE] CAL FAILED - not enough samples; threshold unchanged")); break; @@ -138,28 +138,28 @@ static void handleSerialLine(const char *line) { if (strncmp(line, "id ", 3) == 0) { uint8_t id = (uint8_t)atoi(line + 3); if (id > 0) { - gVfm.identity().assignId(id); + gSfm.identity().assignId(id); Serial.print(F("Node ID set to ")); Serial.println(id); } } else if (strcmp(line, "d") == 0) { - if (gVfm.dispenser().dispense()) Serial.println(F("Dispense started.")); - else { Serial.print(F("Cannot dispense - ")); Serial.println(stateStr(gVfm.dispenser().state())); } + if (gSfm.dispenser().dispense()) Serial.println(F("Dispense started.")); + else { Serial.print(F("Cannot dispense - ")); Serial.println(stateStr(gSfm.dispenser().state())); } } else if (strcmp(line, "a") == 0) { - gVfm.dispenser().recover(); + gSfm.dispenser().recover(); Serial.println(F("Recovered.")); } else if (strcmp(line, "s") == 0) { printStatus(); } else if (strcmp(line, "cal") == 0) { - if (!gVfm.startPresenceCalibration()) { + if (!gSfm.startPresenceCalibration()) { Serial.println(F("[PRESENCE] Calibration already running")); } } else if (strcmp(line, "thr") == 0) { printPresence(); } else if (strcmp(line, "factor") == 0) { Serial.print(F("[PRESENCE] factor=")); - Serial.println(gVfm.presence().factor(), 2); + Serial.println(gSfm.presence().factor(), 2); Serial.println(F("Usage: factor (e.g. factor 3 or factor 2.5)")); - if (!gVfm.presence().hasCalStats()) { + if (!gSfm.presence().hasCalStats()) { Serial.println(F("No cal stats yet – run 'cal' or press button first to re-apply.")); } } else if (strncmp(line, "factor ", 7) == 0) { @@ -168,10 +168,10 @@ static void handleSerialLine(const char *line) { float f = strtof(p, &end); if (end == p || f <= 0.0f) { Serial.println(F("Invalid factor. Usage: factor ")); - } else if (!gVfm.presence().setFactor(f)) { + } else if (!gSfm.presence().setFactor(f)) { Serial.println(F("Invalid factor.")); } else { - if (gVfm.presence().hasCalStats()) { + if (gSfm.presence().hasCalStats()) { Serial.println(F("[PRESENCE] Factor saved; threshold re-applied from last cal.")); } else { Serial.println(F("[PRESENCE] Factor saved; run 'cal' to apply (no cal stats yet).")); @@ -179,11 +179,11 @@ static void handleSerialLine(const char *line) { printPresence(); } } else if (strcmp(line, "thrclr") == 0) { - gVfm.presence().clearStoredThreshold(); + gSfm.presence().clearStoredThreshold(); Serial.println(F("Saved presence cal cleared; using compile-time defaults.")); printPresence(); } else if (strcmp(line, "clr") == 0) { - gVfm.identity().clearId(); + gSfm.identity().clearId(); Serial.println(F("NVS id cleared. Node waits for AEI / discovery to ANNOUNCE.")); } else if (strcmp(line, "h") == 0 || strcmp(line, "help") == 0) { printHelp(); @@ -196,24 +196,24 @@ static void handleSerialLine(const char *line) { void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM Node =====")); + Serial.println(F("\n===== SFM Node =====")); - if (!gVfm.begin()) { + if (!gSfm.begin()) { Serial.println(F("WARNING: one or more services failed to initialise")); } // Print MAC UUID - const uint8_t *m = gVfm.identity().mac(); + const uint8_t *m = gSfm.identity().mac(); Serial.printf("MAC UUID: %02X:%02X:%02X:%02X:%02X:%02X\n", m[0], m[1], m[2], m[3], m[4], m[5]); - Serial.printf("Saved nodeId: %d\n", gVfm.identity().nodeId()); + Serial.printf("Saved nodeId: %d\n", gSfm.identity().nodeId()); printPresence(); Serial.println(F("BTN: click = presence calibration, 3 s hold = clear node ID")); Serial.println(F("Type 'h' for help.")); } void loop() { - gVfm.update(); + gSfm.update(); reportPresenceEvents(); // Non-blocking serial line reader diff --git a/firmware/examples/Troubleshooting/DispenseTest/DispenseTest.ino b/firmware/examples/Troubleshooting/DispenseTest/DispenseTest.ino index ec185da..9e84ed6 100644 --- a/firmware/examples/Troubleshooting/DispenseTest/DispenseTest.ino +++ b/firmware/examples/Troubleshooting/DispenseTest/DispenseTest.ino @@ -1,4 +1,4 @@ -// DispenseTest – serial-driven bench test for the VFM DispenserService. +// DispenseTest – serial-driven bench test for the SFM DispenserService. // // Open the Arduino Serial Monitor at 115200 baud. // @@ -19,7 +19,7 @@ // raiseSteps is measured from the pellet-drop position (kDefaultGrabSteps below // the load sensor), not from the load sensor itself. -#include +#include static constexpr float kMotorSpeed = 300.0f; static constexpr long kLowerSteps = 3072; @@ -28,23 +28,23 @@ static constexpr long kGrabSteps = 280; static constexpr long kRaiseSteps = 1480; static constexpr uint32_t kFeedTimeoutMs = 30000; -vfm::DispenserService dispenser; -vfm::LedService leds; +sfm::DispenserService dispenser; +sfm::LedService leds; float currentSpeed = kMotorSpeed; long currentRaiseSteps = kRaiseSteps; static char lineBuf[48]; static uint8_t lineIdx = 0; -static const char *stateStr(vfm::DispenseState s) { +static const char *stateStr(sfm::DispenseState s) { switch (s) { - case vfm::DispenseState::Idle: return "Idle"; - case vfm::DispenseState::Seeking: return "Seeking"; - case vfm::DispenseState::Lowering: return "Lowering"; - case vfm::DispenseState::Loading: return "Loading"; - case vfm::DispenseState::Raising: return "Raising"; - case vfm::DispenseState::Loaded: return "Loaded"; - case vfm::DispenseState::Fault: return "Fault"; + case sfm::DispenseState::Idle: return "Idle"; + case sfm::DispenseState::Seeking: return "Seeking"; + case sfm::DispenseState::Lowering: return "Lowering"; + case sfm::DispenseState::Loading: return "Loading"; + case sfm::DispenseState::Raising: return "Raising"; + case sfm::DispenseState::Loaded: return "Loaded"; + case sfm::DispenseState::Fault: return "Fault"; } return "?"; } @@ -134,14 +134,14 @@ void handleLine(const char *line) { void setup() { Serial.begin(115200); while (!Serial) {} - Serial.println(F("VFM DispenseTest")); + Serial.println(F("SFM DispenseTest")); Serial.println(F("Commands: d=dispense a=recover s=status +=faster -=slower")); Serial.println(F(" r = show raiseSteps")); Serial.println(F(" r = set raiseSteps (e.g. r 1480)")); Serial.println(F("PelletTaken returns to Idle; DomeOpened reports each dome lift")); Serial.println(F("LEDs: 10=pellet present 9=dome open")); - if (leds.begin() != vfm::ServiceStatus::Ok) { + if (leds.begin() != sfm::ServiceStatus::Ok) { Serial.println(F("ERROR: leds.begin() failed")); } @@ -152,7 +152,7 @@ void setup() { dispenser.setRaiseSteps(kRaiseSteps); dispenser.setFeedTimeoutMs(kFeedTimeoutMs); - if (dispenser.begin() != vfm::ServiceStatus::Ok) { + if (dispenser.begin() != sfm::ServiceStatus::Ok) { Serial.println(F("ERROR: dispenser.begin() failed")); } else { Serial.println(F("Dispenser ready.")); @@ -163,38 +163,38 @@ void setup() { void loop() { dispenser.update(); - // Live sensor mirrors (same mapping as VFM::updateSensorLeds). + // Live sensor mirrors (same mapping as SFM::updateSensorLeds). leds.setLed10(dispenser.pelletOnPlate()); leds.setLed9(dispenser.domeOpen()); switch (dispenser.takeEvent()) { - case vfm::DispenseEvent::OnPlate: + case sfm::DispenseEvent::OnPlate: Serial.println(F("[Event] OnPlate")); break; - case vfm::DispenseEvent::Loaded: + case sfm::DispenseEvent::Loaded: Serial.print(F("[Event] Loaded total=")); Serial.println(dispenser.pelletCount()); break; - case vfm::DispenseEvent::DomeOpened: + case sfm::DispenseEvent::DomeOpened: Serial.println(F("[Event] DomeOpened")); break; - case vfm::DispenseEvent::PelletTaken: + case sfm::DispenseEvent::PelletTaken: Serial.print(F("[Event] PelletTaken taken=")); Serial.println(dispenser.takenCount()); break; - case vfm::DispenseEvent::FeedSkipped: + case sfm::DispenseEvent::FeedSkipped: Serial.println(F("[Event] FeedSkipped (plate occupied)")); break; - case vfm::DispenseEvent::DomeOpenWarning: + case sfm::DispenseEvent::DomeOpenWarning: Serial.println(F("[Event] DomeOpenWarning (>30s open)")); break; - case vfm::DispenseEvent::Fault: + case sfm::DispenseEvent::Fault: Serial.print(F("[Event] FAULT – ")); Serial.println( - dispenser.faultCode() == vfm::ServiceStatus::FeedTimeout ? F("FeedTimeout (out of pellets / refill hopper)") : - dispenser.faultCode() == vfm::ServiceStatus::ActuatorTimeout ? F("ActuatorTimeout (sensor or M2 position)") : - dispenser.faultCode() == vfm::ServiceStatus::Jam ? F("Jam") : - dispenser.faultCode() == vfm::ServiceStatus::PelletLost ? F("PelletLost") : + dispenser.faultCode() == sfm::ServiceStatus::FeedTimeout ? F("FeedTimeout (out of pellets / refill hopper)") : + dispenser.faultCode() == sfm::ServiceStatus::ActuatorTimeout ? F("ActuatorTimeout (sensor or M2 position)") : + dispenser.faultCode() == sfm::ServiceStatus::Jam ? F("Jam") : + dispenser.faultCode() == sfm::ServiceStatus::PelletLost ? F("PelletLost") : F("?")); break; default: diff --git a/firmware/examples/Troubleshooting/HardwareExamples/ActuatorCalTest/ActuatorCalTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/ActuatorCalTest/ActuatorCalTest.ino index 02554dc..917f5f3 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/ActuatorCalTest/ActuatorCalTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/ActuatorCalTest/ActuatorCalTest.ino @@ -34,10 +34,10 @@ // + / - speed ±50 steps/s // h / help this help -#include +#include #include -using namespace vfm; +using namespace sfm; // --- Match library defaults --- static constexpr float kDefaultSpeed = kDefaultMotorSpeed; // 500 @@ -267,7 +267,7 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM ActuatorCalTest (M2) =====")); + Serial.println(F("\n===== SFM ActuatorCalTest (M2) =====")); Serial.println(F("M2 = actuator GPIO40-43 | load sensor = loading home")); Serial.print(F("Load sensor pin=GPIO")); Serial.println(kLoadPositionPin); Serial.print(F("Speed=")); Serial.print(kDefaultSpeed, 0); diff --git a/firmware/examples/Troubleshooting/HardwareExamples/CanBusTest/CanBusTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/CanBusTest/CanBusTest.ino index ebf3fe8..4caa451 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/CanBusTest/CanBusTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/CanBusTest/CanBusTest.ino @@ -1,6 +1,6 @@ // CanBusTest – two-device CAN bus communication test. // -// Flash this same sketch onto both VFM modules, then assign different IDs. +// Flash this same sketch onto both SFM modules, then assign different IDs. // // Two-device wiring (daisy chain via RJ45): // Device-A OUT -> Device-B IN @@ -26,13 +26,13 @@ // c print AEI / AEO chain pin levels // h help -#include +#include #include static constexpr uint32_t kAutoPingMs = 2000; static constexpr uint32_t kStatsMs = 5000; -vfm::CanService can; +sfm::CanService can; uint8_t nodeId = 0; uint8_t peerId = 0; @@ -67,9 +67,9 @@ void printHelp() { void printChain() { Serial.print(F("[CHAIN] AEI (GPIO14)=")); - Serial.print(digitalRead(vfm::PIN_AEI) == HIGH ? F("HIGH") : F("LOW")); + Serial.print(digitalRead(sfm::PIN_AEI) == HIGH ? F("HIGH") : F("LOW")); Serial.print(F(" AEO (GPIO47)=")); - Serial.println(digitalRead(vfm::PIN_AEO) == HIGH ? F("HIGH") : F("LOW")); + Serial.println(digitalRead(sfm::PIN_AEO) == HIGH ? F("HIGH") : F("LOW")); } void printBusStats() { @@ -106,9 +106,9 @@ void sendPing(uint8_t target) { return; } twai_message_t msg = {}; - msg.identifier = vfm::CAN_CMD_BASE + target; + msg.identifier = sfm::CAN_CMD_BASE + target; msg.data_length_code = 1; - msg.data[0] = static_cast(vfm::CanCmd::Ping); + msg.data[0] = static_cast(sfm::CanCmd::Ping); if (twai_transmit(&msg, pdMS_TO_TICKS(100)) == ESP_OK) { txPingCount++; @@ -129,7 +129,7 @@ void assignNodeId(uint8_t id) { // Drive AEO HIGH to enable the downstream node's AEI. // (Has no effect if this device is the last in the chain.) - digitalWrite(vfm::PIN_AEO, HIGH); + digitalWrite(sfm::PIN_AEO, HIGH); autoPing = (peerId > 0); @@ -163,24 +163,24 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM CanBusTest =====")); + Serial.println(F("\n===== SFM CanBusTest =====")); Serial.println(F("250 kbps TWAI | TX=GPIO33 RX=GPIO13")); Serial.println(F("Auto-termination: hardware-controlled via OUT RJ45 port")); Serial.println(F(" OUT connected -> 120 ohm OFF | OUT open -> 120 ohm ON")); printHelp(); - pinMode(vfm::PIN_AEI, INPUT_PULLDOWN); - pinMode(vfm::PIN_AEO, OUTPUT); - digitalWrite(vfm::PIN_AEO, LOW); + pinMode(sfm::PIN_AEI, INPUT_PULLDOWN); + pinMode(sfm::PIN_AEO, OUTPUT); + digitalWrite(sfm::PIN_AEO, LOW); - can.onCommand([](vfm::CanCmd cmd, const uint8_t *, uint8_t) { - if (cmd != vfm::CanCmd::Ping) return; + can.onCommand([](sfm::CanCmd cmd, const uint8_t *, uint8_t) { + if (cmd != sfm::CanCmd::Ping) return; rxPingCount++; Serial.println(F("[CAN] RX Ping -> TX Pong")); - can.sendEvent(vfm::CanEvent::Pong); + can.sendEvent(sfm::CanEvent::Pong); }); - if (can.begin(0) != vfm::ServiceStatus::Ok) { + if (can.begin(0) != sfm::ServiceStatus::Ok) { Serial.println(F("ERROR: CAN driver failed to start")); Serial.println(F(" Check TX/RX wiring and transceiver power")); } else { diff --git a/firmware/examples/Troubleshooting/HardwareExamples/LEDTest/LEDTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/LEDTest/LEDTest.ino index 997b707..d1ebf89 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/LEDTest/LEDTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/LEDTest/LEDTest.ino @@ -1,4 +1,4 @@ -// LEDTest – hardware bring-up test for VFM module LEDs. +// LEDTest – hardware bring-up test for SFM module LEDs. // // Pins: // GPIO39 – Status LED @@ -16,11 +16,11 @@ // 3 – LED 10 only // r – resume auto cycle -#include +#include static constexpr uint32_t kStepMs = 500; -vfm::LedService leds; +sfm::LedService leds; enum class Mode { Auto, Manual }; Mode mode = Mode::Auto; @@ -56,11 +56,11 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM LEDTest =====")); + Serial.println(F("\n===== SFM LEDTest =====")); Serial.println(F("GPIO39 = Status | GPIO9 = LED9 | GPIO10 = LED10")); Serial.println(F("Auto cycle running. Commands: a=all on o=all off 1/2/3=single r=auto")); - if (leds.begin() != vfm::ServiceStatus::Ok) { + if (leds.begin() != sfm::ServiceStatus::Ok) { Serial.println(F("ERROR: LED init failed")); } diff --git a/firmware/examples/Troubleshooting/HardwareExamples/MousePresenceTest/MousePresenceTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/MousePresenceTest/MousePresenceTest.ino index 674abd8..86cb00b 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/MousePresenceTest/MousePresenceTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/MousePresenceTest/MousePresenceTest.ino @@ -28,10 +28,10 @@ // c start 5 s calibration (same as button) // h help -#include +#include #include -using namespace vfm; +using namespace sfm; static constexpr uint32_t kSampleMs = 200; static constexpr uint32_t kCalSampleMs = 25; @@ -289,7 +289,7 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM Mouse Presence Test =====")); + Serial.println(F("\n===== SFM Mouse Presence Test =====")); Serial.println(F("PIN_PRESENCE=GPIO5 BTN=GPIO11 LED9=GPIO9")); Serial.println(F("presence = raw > thr")); Serial.println(F("Cal: thr = mean + factor*std_dev (5 s, pad CLEAR)")); diff --git a/firmware/examples/Troubleshooting/HardwareExamples/PhotogateTest/PhotogateTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/PhotogateTest/PhotogateTest.ino index 75ce436..dc18dbe 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/PhotogateTest/PhotogateTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/PhotogateTest/PhotogateTest.ino @@ -1,4 +1,4 @@ -// PhotogateTest – hardware bring-up test for VFM photogates. +// PhotogateTest – hardware bring-up test for SFM photogates. // // Polarity (matches DispenserService): // Pellet (GPIO46): TRIGGERED = pin LOW (beam break) @@ -13,9 +13,9 @@ // m – toggle continuous raw monitor (200 ms) // h – help -#include +#include -using namespace vfm; +using namespace sfm; static constexpr uint32_t kDebounceMs = 100; static constexpr uint32_t kBannerMs = 5000; @@ -104,7 +104,7 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM PhotogateTest =====")); + Serial.println(F("\n===== SFM PhotogateTest =====")); Serial.println(F("Pellet=GPIO46 Load position=GPIO45 Dome=GPIO44")); Serial.println(F("Dome idle=LOW, open=HIGH (active HIGH)")); printHelp(); diff --git a/firmware/examples/Troubleshooting/HardwareExamples/StepperMotorTest/StepperMotorTest.ino b/firmware/examples/Troubleshooting/HardwareExamples/StepperMotorTest/StepperMotorTest.ino index 30f7523..be1ae48 100644 --- a/firmware/examples/Troubleshooting/HardwareExamples/StepperMotorTest/StepperMotorTest.ino +++ b/firmware/examples/Troubleshooting/HardwareExamples/StepperMotorTest/StepperMotorTest.ino @@ -1,4 +1,4 @@ -// StepperMotorTest – hardware bring-up test for VFM 28BYJ-48 steppers. +// StepperMotorTest – hardware bring-up test for SFM 28BYJ-48 steppers. // // Motor 1 (M1) – Pellet feeder : GPIO 35(Orange) 36(Yellow) 37(Pink) 38(Blue) // Motor 2 (M2) – Actuator : GPIO 40(Orange) 41(Yellow) 42(Pink) 43(Blue) @@ -21,10 +21,10 @@ // p – print step counters and speed // h – help -#include +#include #include -using namespace vfm; +using namespace sfm; static constexpr long kJogSteps = 500; static constexpr long kContBurstSteps = 500; @@ -160,7 +160,7 @@ void setup() { Serial.begin(115200); while (!Serial && millis() < 3000) {} - Serial.println(F("\n===== VFM StepperMotorTest =====")); + Serial.println(F("\n===== SFM StepperMotorTest =====")); Serial.println(F("M1 = feeder (GPIO35-38) | M2 = actuator (GPIO40-43)")); Serial.println(F("Red wire -> +5 V on both motors")); Serial.print(F("Default speed: ")); Serial.print(kDefaultSpeed, 0); diff --git a/firmware/library.properties b/firmware/library.properties index cd400a5..3ad7737 100644 --- a/firmware/library.properties +++ b/firmware/library.properties @@ -1,10 +1,10 @@ -name=VFM +name=SFM version=0.1.0 author=Neurotech Hub maintainer=Neurotech Hub -sentence=Firmware library for the Spatial Foraging Platform VFM module (ESP32-S3-MINI-1). +sentence=Firmware library for the Spatial Foraging Platform SFM module (ESP32-S3-MINI-1). paragraph=Non-blocking, service-oriented library providing stepper-driven pellet dispensing, TWAI CAN bus communication with base-station command/heartbeat protocol, and daisy-chain node-ID assignment via AEO/AEI addressing. category=Device Control -url=https://github.com/Neurotech-Hub/VFM +url=https://github.com/Neurotech-Hub/SFM architectures=esp32 depends=AccelStepper diff --git a/firmware/src/VFM.cpp b/firmware/src/SFM.cpp similarity index 96% rename from firmware/src/VFM.cpp rename to firmware/src/SFM.cpp index c302056..2187baf 100644 --- a/firmware/src/VFM.cpp +++ b/firmware/src/SFM.cpp @@ -1,12 +1,12 @@ -#include "VFM.h" +#include "SFM.h" -namespace vfm { +namespace sfm { -VFM::VFM() +SFM::SFM() : can_(), @@ -26,7 +26,7 @@ VFM::VFM() // --------------------------------------------------------------------------- -bool VFM::begin() { +bool SFM::begin() { bool ok = true; @@ -288,7 +288,7 @@ bool VFM::begin() { // --------------------------------------------------------------------------- -void VFM::update() { +void SFM::update() { can_.update(); // pump RX first so callbacks (discovery, commands) fire @@ -373,7 +373,7 @@ void VFM::update() { -void VFM::handleDispenserEvents() { +void SFM::handleDispenserEvents() { DispenseEvent ev = dispenser_.takeEvent(); @@ -490,7 +490,7 @@ void VFM::handleDispenserEvents() { // OnPlate and Loaded milestones are sent by handleDispenserEvents(). // --------------------------------------------------------------------------- -void VFM::handleDispensePhaseEvents() { +void SFM::handleDispensePhaseEvents() { if (!identity_.isEnabled() || can_.nodeId() == 0) return; @@ -526,7 +526,7 @@ void VFM::handleDispensePhaseEvents() { } -void VFM::sendPhaseEvent(CanEvent ev) { +void SFM::sendPhaseEvent(CanEvent ev) { uint8_t extra[2]; uint32_t count = dispenser_.pelletCount(); @@ -542,7 +542,7 @@ void VFM::sendPhaseEvent(CanEvent ev) { // path used by the GUI event log and circular input indicators. // --------------------------------------------------------------------------- -void VFM::handleInputEvents() { +void SFM::handleInputEvents() { // Do not publish operational events until this node has a valid CAN ID. if (!identity_.isEnabled() || can_.nodeId() == 0) return; @@ -577,7 +577,7 @@ void VFM::handleInputEvents() { // bench sees one pattern for "stored something". // --------------------------------------------------------------------------- -void VFM::handlePresenceEvents() { +void SFM::handlePresenceEvents() { PresenceEvent ev = presence_.takeEvent(); if (ev != PresenceEvent::None) { @@ -588,7 +588,7 @@ void VFM::handlePresenceEvents() { break; case PresenceEvent::CalibrationDone: - // Non-blocking blink (see calConfirmActive_ in VFM.h) instead + // Non-blocking blink (see calConfirmActive_ in SFM.h) instead // of LedService::flashConfirm() — that call blocks on delay() // for ~600ms, which stalled can_.update() and delayed the // PresenceCalResult publish below by the same amount. @@ -638,7 +638,7 @@ void VFM::handlePresenceEvents() { } -PresenceEvent VFM::takePresenceEvent() { +PresenceEvent SFM::takePresenceEvent() { PresenceEvent ev = pendingPresenceEvent_; pendingPresenceEvent_ = PresenceEvent::None; @@ -646,7 +646,7 @@ PresenceEvent VFM::takePresenceEvent() { } -void VFM::sendInputChanged(InputId input, bool active) { +void SFM::sendInputChanged(InputId input, bool active) { uint8_t payload[2] = { static_cast(input), @@ -691,7 +691,7 @@ static HeartbeatPayload buildHeartbeat(const DispenserService &d, bool presence) -void VFM::sendHeartbeatIfDue() { +void SFM::sendHeartbeatIfDue() { if (!can_.heartbeatDue()) return; @@ -701,7 +701,7 @@ void VFM::sendHeartbeatIfDue() { -void VFM::sendHeartbeatNow() { +void SFM::sendHeartbeatNow() { can_.sendHeartbeat(buildHeartbeat(dispenser_, presence_.present())); @@ -709,7 +709,7 @@ void VFM::sendHeartbeatNow() { -void VFM::blinkStatusLedForPing() { +void SFM::blinkStatusLedForPing() { // Don't interrupt a solid fault indication with a blink. if (dispenser_.state() == DispenseState::Fault) return; @@ -722,7 +722,7 @@ void VFM::blinkStatusLedForPing() { -void VFM::updatePingBlink() { +void SFM::updatePingBlink() { if (!pingBlinkActive_) return; @@ -737,7 +737,7 @@ void VFM::updatePingBlink() { -void VFM::syncFlash(uint16_t durationMs) { +void SFM::syncFlash(uint16_t durationMs) { // Don't interrupt a solid fault indication — it already holds the LED // solid ON for a different reason, and the flash would be invisible. @@ -760,7 +760,7 @@ void VFM::syncFlash(uint16_t durationMs) { -void VFM::updateSyncFlash() { +void SFM::updateSyncFlash() { if (!syncFlashActive_) return; @@ -774,7 +774,7 @@ void VFM::updateSyncFlash() { -void VFM::updateCalConfirmBlink() { +void SFM::updateCalConfirmBlink() { if (!calConfirmActive_) return; @@ -798,7 +798,7 @@ void VFM::updateCalConfirmBlink() { // calibration capture, and its post-calibration confirm blink, all of which // keep it until they are done. -void VFM::updateSensorLeds() { +void SFM::updateSensorLeds() { leds_.setLed10(dispenser_.pelletOnPlate()); @@ -832,7 +832,7 @@ void VFM::updateSensorLeds() { // --------------------------------------------------------------------------- -void VFM::updateButton() { +void SFM::updateButton() { bool pressed = (digitalRead(PIN_BTN) == LOW); @@ -913,13 +913,13 @@ void VFM::updateButton() { // ~600 ms total and only fires on a deliberate 3-second button hold. -void VFM::flashLedsClear() { +void SFM::flashLedsClear() { // Shared confirm pattern lives in LedService (also used by MousePresenceTest). leds_.flashConfirm(); } -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/VFM.h b/firmware/src/SFM.h similarity index 94% rename from firmware/src/VFM.h rename to firmware/src/SFM.h index b8b9c20..29a9236 100644 --- a/firmware/src/VFM.h +++ b/firmware/src/SFM.h @@ -1,22 +1,22 @@ #pragma once // --------------------------------------------------------------------------- -// VFM – Spatial Foraging Platform module library +// SFM – Spatial Foraging Platform module library // // Facade class that aggregates all services. Typical sketch usage: // -// #include -// vfm::VFM vfm; +// #include +// sfm::SFM sfm; // // void setup() { -// vfm.begin(); // init all services + start discovery +// sfm.begin(); // init all services + start discovery // } // void loop() { -// vfm.update(); // drive FSMs, pump CAN, heartbeat +// sfm.update(); // drive FSMs, pump CAN, heartbeat // } // --------------------------------------------------------------------------- -#include "hardware/VFMPins.h" +#include "hardware/SFMPins.h" #include "services/ServiceTypes.h" #include "services/DispenserService.h" #include "services/CanService.h" @@ -24,11 +24,11 @@ #include "services/LedService.h" #include "services/PresenceService.h" -namespace vfm { +namespace sfm { -class VFM { +class SFM { public: - VFM(); + SFM(); // Initialise all services in the correct order. // Returns false if any critical service fails to initialise. @@ -56,7 +56,7 @@ class VFM { bool startPresenceCalibration() { return presence_.startCalibration(); } bool presenceCalibrating() const { return presence_.calibrating(); } - // Calibration notifications, re-latched after VFM has driven the LEDs so a + // Calibration notifications, re-latched after SFM has driven the LEDs so a // sketch can report the result. Returns None when there is nothing new. PresenceEvent takePresenceEvent(); @@ -153,4 +153,4 @@ class VFM { void flashLedsClear(); // visual confirmation of NVS clear }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/hardware/VFMPins.h b/firmware/src/hardware/SFMPins.h similarity index 99% rename from firmware/src/hardware/VFMPins.h rename to firmware/src/hardware/SFMPins.h index 857c7d6..0fa59a1 100644 --- a/firmware/src/hardware/VFMPins.h +++ b/firmware/src/hardware/SFMPins.h @@ -2,7 +2,7 @@ #include -namespace vfm { +namespace sfm { // --------------------------------------------------------------------------- // CAN (TWAI) @@ -70,4 +70,4 @@ constexpr uint8_t PIN_PGX = PIN_USER_IO_6; // alternate load-sensor bring constexpr uint8_t PIN_USER_IO_7 = 7; // User_IO_7 constexpr uint8_t PIN_POWER_ST = 15; // Power_ST -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/CanService.cpp b/firmware/src/services/CanService.cpp index cabebac..6932bf7 100644 --- a/firmware/src/services/CanService.cpp +++ b/firmware/src/services/CanService.cpp @@ -1,6 +1,6 @@ #include "CanService.h" -namespace vfm { +namespace sfm { CanService::CanService() : nodeId_(0), @@ -172,4 +172,4 @@ void CanService::dispatchRx(const twai_message_t &msg) { } } -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/CanService.h b/firmware/src/services/CanService.h index 96af762..f3944c5 100644 --- a/firmware/src/services/CanService.h +++ b/firmware/src/services/CanService.h @@ -4,9 +4,9 @@ #include #include "driver/twai.h" #include "ServiceTypes.h" -#include "../hardware/VFMPins.h" +#include "../hardware/SFMPins.h" -namespace vfm { +namespace sfm { // Heartbeat interval when no explicit SetConfig has been applied (milliseconds). // Runtime-configurable via CanCmd::SetConfig / ConfigType::HeartbeatInterval @@ -112,4 +112,4 @@ class CanService { void dispatchRx(const twai_message_t &msg); }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/DispenserService.cpp b/firmware/src/services/DispenserService.cpp index 0a1a0f0..65aadcf 100644 --- a/firmware/src/services/DispenserService.cpp +++ b/firmware/src/services/DispenserService.cpp @@ -1,6 +1,6 @@ #include "DispenserService.h" -namespace vfm { +namespace sfm { // --------------------------------------------------------------------------- // AccelStepper HALF4WIRE: (A1, A3, A2, A4) = Orange, Pink, Yellow, Blue @@ -593,4 +593,4 @@ bool DispenserService::phaseTimedOut(uint32_t timeoutMs) const { return (millis() - motionStartMs_) >= timeoutMs; } -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/DispenserService.h b/firmware/src/services/DispenserService.h index 149aba0..3ee9034 100644 --- a/firmware/src/services/DispenserService.h +++ b/firmware/src/services/DispenserService.h @@ -3,9 +3,9 @@ #include #include #include "ServiceTypes.h" -#include "../hardware/VFMPins.h" +#include "../hardware/SFMPins.h" -namespace vfm { +namespace sfm { // --------------------------------------------------------------------------- // Tunable defaults (override before begin() via setters) @@ -186,4 +186,4 @@ class DispenserService { void beginOccupiedDispense(); }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/LedService.h b/firmware/src/services/LedService.h index 1ee8dc5..6984efc 100644 --- a/firmware/src/services/LedService.h +++ b/firmware/src/services/LedService.h @@ -1,10 +1,10 @@ #pragma once -#include "../hardware/VFMPins.h" +#include "../hardware/SFMPins.h" #include "ServiceTypes.h" #include -namespace vfm { +namespace sfm { // LED service for the status LED and the two user IO LEDs. // @@ -125,4 +125,4 @@ class LedService { bool led10On_ = false; }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/NodeIdentity.cpp b/firmware/src/services/NodeIdentity.cpp index d5aa20a..42b205f 100644 --- a/firmware/src/services/NodeIdentity.cpp +++ b/firmware/src/services/NodeIdentity.cpp @@ -1,7 +1,7 @@ #include "NodeIdentity.h" #include "esp_mac.h" -namespace vfm { +namespace sfm { NodeIdentity::NodeIdentity(CanService &can) : can_(can), @@ -198,4 +198,4 @@ void NodeIdentity::onDiscoveryFrame(uint32_t frameId, const uint8_t *payload, ui // ANNOUNCE, ACK, REJOIN originate from nodes, not the base; ignore them. } -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/NodeIdentity.h b/firmware/src/services/NodeIdentity.h index d5494b4..309a635 100644 --- a/firmware/src/services/NodeIdentity.h +++ b/firmware/src/services/NodeIdentity.h @@ -4,9 +4,9 @@ #include #include "ServiceTypes.h" #include "CanService.h" -#include "../hardware/VFMPins.h" +#include "../hardware/SFMPins.h" -namespace vfm { +namespace sfm { // NVS key used to persist the node ID (namespace kNvsNamespace) constexpr char kNvsKeyNodeId[] = "nodeId"; @@ -96,4 +96,4 @@ class NodeIdentity { void onDiscoveryFrame(uint32_t frameId, const uint8_t *payload, uint8_t len); }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/PresenceService.cpp b/firmware/src/services/PresenceService.cpp index 5ad39aa..ac5889d 100644 --- a/firmware/src/services/PresenceService.cpp +++ b/firmware/src/services/PresenceService.cpp @@ -1,7 +1,7 @@ #include "PresenceService.h" #include -namespace vfm { +namespace sfm { // --------------------------------------------------------------------------- ServiceStatus PresenceService::begin() { @@ -181,7 +181,7 @@ void PresenceService::finishCalibration() { // Single NVS transaction (not 3 separate begin/end round trips) — each // commit is a flash erase/write and can stall for a while if NVS needs // to compact a page, so cut that time to a third right before the - // non-blocking confirm blink in VFM::handlePresenceEvents(). + // non-blocking confirm blink in SFM::handlePresenceEvents(). prefs_.begin(kNvsNamespace, false); prefs_.putFloat(kNvsKeyPresenceMean, mean); prefs_.putFloat(kNvsKeyPresenceStd, stdDev); @@ -227,4 +227,4 @@ void PresenceService::loadFromNvs() { prefs_.end(); } -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/PresenceService.h b/firmware/src/services/PresenceService.h index 64a65c0..ea08ba5 100644 --- a/firmware/src/services/PresenceService.h +++ b/firmware/src/services/PresenceService.h @@ -3,9 +3,9 @@ #include #include #include "ServiceTypes.h" -#include "../hardware/VFMPins.h" +#include "../hardware/SFMPins.h" -namespace vfm { +namespace sfm { // NVS keys (namespace kNvsNamespace) constexpr char kNvsKeyPresenceThr[] = "presThr"; @@ -132,4 +132,4 @@ class PresenceService { void loadFromNvs(); }; -} // namespace vfm +} // namespace sfm diff --git a/firmware/src/services/ServiceTypes.h b/firmware/src/services/ServiceTypes.h index cf743a6..094971c 100644 --- a/firmware/src/services/ServiceTypes.h +++ b/firmware/src/services/ServiceTypes.h @@ -2,13 +2,13 @@ #include -namespace vfm { +namespace sfm { // --------------------------------------------------------------------------- // Persistent storage (NVS) — one namespace shared by every service that // stores a value. Keys live next to the service that owns them. // --------------------------------------------------------------------------- -constexpr char kNvsNamespace[] = "vfm"; +constexpr char kNvsNamespace[] = "sfm"; // --------------------------------------------------------------------------- // Debounce applied to every sensor input before any logic or reporting acts on @@ -153,4 +153,4 @@ enum class DiscoveryState : uint8_t { Enabled, // Identity resolved – AEO driven HIGH, normal operation }; -} // namespace vfm +} // namespace sfm diff --git a/packages/dev_gui/README.md b/packages/dev_gui/README.md index 53de0c2..c5f1518 100644 --- a/packages/dev_gui/README.md +++ b/packages/dev_gui/README.md @@ -1,7 +1,7 @@ # SFM Developer GUI Python desktop application (DearPyGui) for the **Spatial Foraging Module (SFM)** — -a base station plus multiple **VFM** nodes on a Raspberry Pi 5. They share one +a base station plus multiple **SFM** nodes on a Raspberry Pi 5. They share one **CAN** bus (Controller Area Network): the communication wire all nodes are connected to. A **CAN event** is a frame a node posted on that bus (Loaded, Pellet Taken, Fault, sensor edge, …), as opposed to an experiment-engine @@ -311,7 +311,7 @@ packages/dev_gui/ ├── run.py # GUI entry point ├── run_report.py # Thin wrapper around sfm_analysis.cli.report │ # (see packages/sfm-analysis/ below) -├── node_simulator.py # Fake VFM nodes for vcan0 testing +├── node_simulator.py # Fake SFM nodes for vcan0 testing ├── experiments/ # JSON parameter schemas (drives the GUI form) │ ├── free_feeding.json │ ├── fixed_and_random.json diff --git a/packages/dev_gui/base_station/node_registry.py b/packages/dev_gui/base_station/node_registry.py index 01b6d03..fd27372 100644 --- a/packages/dev_gui/base_station/node_registry.py +++ b/packages/dev_gui/base_station/node_registry.py @@ -39,7 +39,7 @@ def offline_timeout_for_heartbeat(hb_interval_s: float) -> float: @dataclass class NodeState: - """Live state for one VFM node.""" + """Live state for one SFM node.""" node_id: int label: str # user-editable (GUI-only) diff --git a/packages/dev_gui/base_station/pellet_ledger.py b/packages/dev_gui/base_station/pellet_ledger.py index 2bfbc06..7347b09 100644 --- a/packages/dev_gui/base_station/pellet_ledger.py +++ b/packages/dev_gui/base_station/pellet_ledger.py @@ -22,7 +22,7 @@ the counter — the following ``DomeOpened``, ``PelletTaken``, or simply the next heartbeat — and the gap is reported so it lands in the log instead of vanishing. -Which counter each frame carries (see ``VFM::sendDispenseEvent``): +Which counter each frame carries (see ``SFM::sendDispenseEvent``): * ``PelletTaken`` carries ``takenCount_``; only it advances the taken counter. * Every other count-carrying event carries ``pelletCount_`` (pellets presented); diff --git a/packages/dev_gui/base_station/protocol.py b/packages/dev_gui/base_station/protocol.py index cea9a54..f356986 100644 --- a/packages/dev_gui/base_station/protocol.py +++ b/packages/dev_gui/base_station/protocol.py @@ -1,6 +1,6 @@ """protocol.py — compatibility shim. -The VFM CAN protocol now lives in the sfm-analysis SDK at +The SFM CAN protocol now lives in the sfm-analysis SDK at ``sfm_analysis.protocol`` so that log-analysis tooling can decode frames without a base-station checkout. This module re-exports it unchanged so that every existing ``from base_station.protocol import X`` (and diff --git a/packages/dev_gui/deploy/README.md b/packages/dev_gui/deploy/README.md index 3edfa66..9c6fe8b 100644 --- a/packages/dev_gui/deploy/README.md +++ b/packages/dev_gui/deploy/README.md @@ -99,7 +99,7 @@ sudo ip link set can0 type can bitrate 250000 loopback off sudo ip link set can0 up ``` -With at least one VFM node powered and connected, `candump can0` should show +With at least one SFM node powered and connected, `candump can0` should show `080` (ANNOUNCE) or `083` (REJOIN) discovery frames, followed by `2xx` heartbeat frames at 1 Hz once the node is assigned an ID. diff --git a/packages/dev_gui/node_simulator.py b/packages/dev_gui/node_simulator.py index dec6e3c..9f21c25 100644 --- a/packages/dev_gui/node_simulator.py +++ b/packages/dev_gui/node_simulator.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -node_simulator.py — Simulates N VFM nodes on a SocketCAN interface. +node_simulator.py — Simulates N SFM nodes on a SocketCAN interface. Run alongside the GUI on vcan0 for hardware-free development and testing. @@ -204,7 +204,7 @@ def hb_payload(self) -> HeartbeatPayload: # --------------------------------------------------------------------------- class NodeSimulator: - """Runs N simulated VFM nodes on a SocketCAN interface.""" + """Runs N simulated SFM nodes on a SocketCAN interface.""" # Dispense sequence timings (seconds after command received) LOWERING_DELAY = 1.0 @@ -746,7 +746,7 @@ def _send_input_changed(self, node: SimNode, input_id: InputId, active: bool) -> def main() -> None: parser = argparse.ArgumentParser( - description="VFM Node Simulator — fake VFM nodes on a SocketCAN interface", + description="SFM Node Simulator — fake SFM nodes on a SocketCAN interface", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("--interface", "-i", default="vcan0", @@ -769,7 +769,7 @@ def main() -> None: skip_discovery=args.skip_discovery, ) - print(f"VFM Node Simulator — {args.nodes} node(s) on {args.interface}") + print(f"SFM Node Simulator — {args.nodes} node(s) on {args.interface}") print("Press Ctrl+C to stop.\n") try: sim.start() diff --git a/packages/dev_gui/tests/test_hat.py b/packages/dev_gui/tests/test_hat.py index 25abc91..79fefd3 100644 --- a/packages/dev_gui/tests/test_hat.py +++ b/packages/dev_gui/tests/test_hat.py @@ -4,7 +4,7 @@ This is NOT a pytest suite (despite the filename, chosen to match the plan) — it is a manual, interactive checklist you run directly on the Raspberry Pi -with the HAT (and ideally at least one VFM node) connected: +with the HAT (and ideally at least one SFM node) connected: cd packages/dev_gui python tests/test_hat.py @@ -118,8 +118,8 @@ def check_can_loopback(interface: str) -> None: def check_can_node_discovery(interface: str) -> None: - _section("Live node discovery (power at least one VFM node)") - if not _prompt_yes("Is at least one VFM node powered and connected to the CAN bus?"): + _section("Live node discovery (power at least one SFM node)") + if not _prompt_yes("Is at least one SFM node powered and connected to the CAN bus?"): _record("Node discovery frames observed", False) return print(f" Run `candump {interface}` in another terminal now and watch for discovery frames.") diff --git a/packages/sfm-analysis/Bandit_test_01_report.html b/packages/sfm-analysis/Bandit_test_01_report.html index ade5bdd..1616dec 100644 --- a/packages/sfm-analysis/Bandit_test_01_report.html +++ b/packages/sfm-analysis/Bandit_test_01_report.html @@ -145,7 +145,7 @@

Bandit_test_01 · run 1

Identityno subject parsed from session name Duration5s End reasonstopped normally - Source file/home/base-station-00/Project/VFM/packages/sfm-analysis/src/sfm_analysis/report/demo/Bandit_test_01.csv + Source file/home/base-station-00/Project/SFM/packages/sfm-analysis/src/sfm_analysis/report/demo/Bandit_test_01.csv

Configured parameters

arm_a1
arm_b2
block_size10
mimic1
next_trial_waitfixed_delay
nodes[1, 2]
p_high0.8999999761581421
seed1025782770
@@ -160,7 +160,7 @@

Bandit_test_01 · run 2

Identityno subject parsed from session name Duration21m 32s End reasonstopped normally - Source file/home/base-station-00/Project/VFM/packages/sfm-analysis/src/sfm_analysis/report/demo/Bandit_test_01.csv + Source file/home/base-station-00/Project/SFM/packages/sfm-analysis/src/sfm_analysis/report/demo/Bandit_test_01.csv

Configured parameters

arm_a1
arm_b2
block_size10
mimic1
next_trial_waitpresence_clear
nodes[1, 2]
p_high0.8999999761581421
seed2011668843
@@ -220,7 +220,7 @@

Bandit_test_01 · run 2

win-stay 10/12, lose-shift 2/3 (95% Wilson CI shown).
Win-stay / lose-shift00.250.50.751raterate: 0.833333win-stayrate: 0.666667lose-shift
-