mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
An ESP32-S3 DevKitC-1 on the bench network drives three relays at the machine's connectors so the acceptance tool can open the lid loop, pull the interlock loop and press the button with nobody in the room. Two contacts are normally closed in loops the safety chain already reads, the third is normally open across the button input and only ever pulsed (20 to 500 ms, the end armed before the line rises); every line is low at boot and after any reset, the task watchdog panics and reboots, and the button channel needs a jumper. HTTP on port 80 under a key in X-Fixture-Key; the hostname announced over DHCP and mDNS. ESP-IDF v5.5 native, the mDNS component pinned in dependencies.lock; fixture.env (git-ignored) is the one input: the wifi, the key, the hostname. fixture.sh builds with idf.py or in the espressif/idf container, flashes with esptool from pip. The decisions that need no hardware live in policy.c with a gcc host test; CI runs it and builds the firmware in the pinned container.
54 lines
2.1 KiB
C
54 lines
2.1 KiB
C
/*
|
|
* forgefixture policy: the decisions that need no hardware, kept apart
|
|
* so the host test can hold them to account. What a channel is called,
|
|
* what a request may ask of it, and how long a button press may last.
|
|
*
|
|
* (C) Copyright 2026
|
|
* Scott Wiederhold, s.e.wiederhold@gmail.com
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
#pragma once
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
|
|
/* The three channels, in the order of the GPIO table in relays.c. */
|
|
typedef enum {
|
|
CH_LID = 0, /* NC contact in the lid-switch loop: energized = loop open */
|
|
CH_INTERLOCK = 1, /* NC contact in the interlock loop: energized = loop open */
|
|
CH_BUTTON = 2, /* NO contact across the button input: energized = pressed */
|
|
CH_COUNT = 3
|
|
} channel_t;
|
|
|
|
/* A button press is a pulse and nothing else: never held, never longer
|
|
* than BUTTON_MAX_MS whatever the request says, never shorter than the
|
|
* debounce the machine's input needs to see it. */
|
|
#define BUTTON_DEFAULT_MS 200
|
|
#define BUTTON_MIN_MS 20
|
|
#define BUTTON_MAX_MS 500
|
|
|
|
/* The channel's name on the API and in the log. */
|
|
const char *policy_channel_name(channel_t ch);
|
|
|
|
/* The channel named by an API path ("/lid", "/interlock", "/button"), or
|
|
* -1. */
|
|
int policy_channel_from_path(const char *path);
|
|
|
|
/* The level a lid or interlock request asks for: "open" energizes the
|
|
* channel (the loop opens), "close" or "closed" releases it. Returns
|
|
* false for any other word. */
|
|
bool policy_parse_loop_state(const char *word, bool *energize);
|
|
|
|
/* The pulse a button request gets: the default for 0 or an absent
|
|
* value, otherwise the request clamped into [BUTTON_MIN_MS,
|
|
* BUTTON_MAX_MS]. A negative request is the default too. */
|
|
int policy_button_ms(int requested);
|
|
|
|
/* What the state of a channel is called on the API: a loop channel is
|
|
* "open" or "closed", the button "pressed" or "idle". */
|
|
const char *policy_state_name(channel_t ch, bool energized);
|
|
|
|
/* Constant-time equality of two keys, so a wrong key costs the same
|
|
* whichever byte is wrong. */
|
|
bool policy_key_matches(const char *presented, const char *expected);
|