phronesis / locus / firmware

firmware recipe

Logger shape that emits schema-valid sample lines for grow and harvester runs.

Not a full binary — a recipe any ESP32 / Pico / laptop logger can follow. Samples go JSONL during the run; wrap into a locus-lab-log/1 object at the end with environment.world for Earth / Moon / Mars / analog. Examples: /locus/runs · /locus/worlds.

Grow: ≥1 Hz electrical + G during comparison windows. Harvester: ≥1–10 Hz with drive_on. Channels listed on /locus/measure.

grow (P0–P3 samples)

// locus-logger grow sketch (Arduino/ESP32 shape)
// Emits one JSON object per line — locus-lab-log samples[]

#include <WiFi.h> // optional NTP
// INA219, SHT, lux as your wiring

const char* RUN_ID = "YYYYMMDD-HHMM-p0-slug";
const float DT_S = 1.0;

void setup() {
  Serial.begin(115200);
  // init sensors; optional configTime for UTC
}

void loop() {
  float G = readIrradiance();      // W/m2 or lux_proxy
  float V = readPanelV();
  float I = readPanelI();
  float P = V * I;
  // optional P2:
  // float Tw, RHw, Td, RHd, OD;

  Serial.print("{\"t\":\"");
  Serial.print(utcIsoNow());
  Serial.print("\",\"G\":");
  Serial.print(G, 1);
  Serial.print(",\"V_pv\":");
  Serial.print(V, 2);
  Serial.print(",\"I_pv\":");
  Serial.print(I, 3);
  Serial.print(",\"P_pv\":");
  Serial.print(P, 2);
  Serial.print(",\"P_load\":");
  Serial.print(readLoadP(), 2);
  Serial.println("}");
  delay((int)(DT_S * 1000));
}

// After the run: wrap samples into a locus-lab-log/1 object with
// schema, id, prototype, status, goal, parts, summary.
// Validate against /locus/lab-log.schema.json

resonance harvester (RP0 samples)

// locus-logger RP0 harvester sketch shape
// Channels: f_drive, f0, P_piezo, drive_on

const float F0_HZ = 1.01; // measure or estimate
const float DT_S = 0.1;   // 10 Hz if MCU allows

void loop() {
  bool drive_on = readDriveActive(); // foot switch, load cell threshold, or operator flag
  float f_drive = drive_on ? F0_HZ : 0;
  float P_piezo = readHarvestPowerW(); // V_harv * I_harv after rectifier

  Serial.print("{\"t\":\"");
  Serial.print(utcIsoNow());
  Serial.print("\",\"f0\":");
  Serial.print(F0_HZ, 3);
  Serial.print(",\"f_drive\":");
  Serial.print(f_drive, 3);
  Serial.print(",\"P_piezo\":");
  Serial.print(P_piezo, 4);
  Serial.print(",\"drive_on\":");
  Serial.print(drive_on ? "true" : "false");
  Serial.println("}");
  delay((int)(DT_S * 1000));
}

wrap to lab-log

{
  "schema": "locus-lab-log/1",
  "id": "YYYYMMDD-HHMM-p0-slug",
  "prototype": "P0",
  "scheme": "grow_your_own_energy",
  "status": "pass",
  "goal": "…fixed for the whole run…",
  "parts": [{ "role": "pv", "model": "…" }],
  "summary": { "energy_baseline_wh": 0, "energy_treatment_wh": 0 },
  "samples": [ /* lines from Serial */ ]
}

File under public/locus/runs/ and list in index.json. Kit wiring: /locus/kit.