Public: anyone can read this; the crew can change it.
Migrated from github.com/Trevato/SO-ARM100
workbenchthis machine
Close the loop: sensors (pH, EC, temperatures, level, flow) and dosing pumps (nutrients, pH, top-off) with hard safety limits; suggest first, then auto; the crew can drive it via MCP #604
smith/arm:smith/586-close-the-loop-sensors-ph-ec-temperature → main 8 commits fork
1 file
changed +6
−0
Software/so101/src/relay.ts +6
| @@ -169,6 +169,8 @@ export interface RelayOptions { | ||
| 169 | 169 | stations?: StationHub; |
| 170 | 170 | /** The plant care app's log book and plant profile (src/care-routes.ts); by default in memory, with the built-in spinach profile. */ |
| 171 | 171 | care?: { log: CareLog; profile: Profile; doser?: Doser; dosingFile?: string }; |
| 172 | /** How often automation looks at the dosing loops; a minute by default. */ | |
| 173 | doseEveryMs?: number; | |
| 172 | 174 | /** How often the Pi's pH/EC probes are read into the care log, ms; unset, they are not sampled (the page still shows them and walks the calibration). */ |
| 173 | 175 | probeEveryMs?: number; |
| 174 | 176 | /** The grow box's stills, log and how often a still is taken (src/grow-stills.ts); without it nothing is taken and the card shows no timelapse. */ |
| @@ -341,6 +343,9 @@ export async function startRelay(options: RelayOptions): Promise<RunningRelay> { | ||
| 341 | 343 | /** The Pi's dosing pumps (src/dose-pumps.ts), reached through the arm server like the grow light; set up below with armGrow. */ |
| 342 | 344 | const pumpHooks: PumpHooks = { read: () => pumpRead.read(), write: (body, timeoutMs) => pumpWrite(body, timeoutMs) }; |
| 343 | 345 | const doser = care.doser ?? makeDoser(care.log, armPumps(pumpHooks), care.dosingFile); |
| 346 | /** Automation: each loop in auto gets its dose when there is one to give, inside the controller's limits. */ | |
| 347 | const doseTimer = setInterval(() => void doser.tick().catch((err) => console.error(`dosing: ${(err as Error).message}`)), options.doseEveryMs ?? 60_000); | |
| 348 | doseTimer.unref?.(); | |
| 344 | 349 | const cameras = options.cameras ?? new CameraHub({ feed: { ...options.camera, metrics }, seed: remote ? [{ name: "Camera 1", source: remote.cameras[0]! }] : [] }); |
| 345 | 350 | const leaders = options.leaders ?? new LeaderCalibrations(); |
| 346 | 351 | const videos = new Map<number, VideoFeed>(Object.entries(options.video ?? {}).map(([id, sources]) => [Number(id), new VideoFeed(sources, {}, options.env ?? process.env)])); |
| @@ -1641,6 +1646,7 @@ export async function startRelay(options: RelayOptions): Promise<RunningRelay> { | ||
| 1641 | 1646 | clearInterval(sampleTimer); |
| 1642 | 1647 | clearInterval(stillTimer); |
| 1643 | 1648 | clearInterval(probeTimer); |
| 1649 | clearInterval(doseTimer); | |
| 1644 | 1650 | clearInterval(sensorTimer); |
| 1645 | 1651 | readBacks?.(); |
| 1646 | 1652 | arrived?.(); |