mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -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.
77 lines
1.9 KiB
C
77 lines
1.9 KiB
C
/*
|
|
* forgefixture policy (see policy.h). No ESP-IDF here: test/policy_test.c
|
|
* compiles this file with a host compiler.
|
|
*
|
|
* (C) Copyright 2026
|
|
* Scott Wiederhold, s.e.wiederhold@gmail.com
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
#include "policy.h"
|
|
|
|
#include <string.h>
|
|
|
|
static const char *const NAMES[CH_COUNT] = {"lid", "interlock", "button"};
|
|
|
|
const char *policy_channel_name(channel_t ch)
|
|
{
|
|
return (ch >= 0 && ch < CH_COUNT) ? NAMES[ch] : "?";
|
|
}
|
|
|
|
int policy_channel_from_path(const char *path)
|
|
{
|
|
if (path == NULL || path[0] != '/')
|
|
return -1;
|
|
for (int i = 0; i < CH_COUNT; i++)
|
|
if (strcmp(path + 1, NAMES[i]) == 0)
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
bool policy_parse_loop_state(const char *word, bool *energize)
|
|
{
|
|
if (word == NULL)
|
|
return false;
|
|
if (strcmp(word, "open") == 0) {
|
|
*energize = true;
|
|
return true;
|
|
}
|
|
if (strcmp(word, "close") == 0 || strcmp(word, "closed") == 0) {
|
|
*energize = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int policy_button_ms(int requested)
|
|
{
|
|
if (requested <= 0)
|
|
return BUTTON_DEFAULT_MS;
|
|
if (requested < BUTTON_MIN_MS)
|
|
return BUTTON_MIN_MS;
|
|
if (requested > BUTTON_MAX_MS)
|
|
return BUTTON_MAX_MS;
|
|
return requested;
|
|
}
|
|
|
|
const char *policy_state_name(channel_t ch, bool energized)
|
|
{
|
|
if (ch == CH_BUTTON)
|
|
return energized ? "pressed" : "idle";
|
|
return energized ? "open" : "closed";
|
|
}
|
|
|
|
bool policy_key_matches(const char *presented, const char *expected)
|
|
{
|
|
if (presented == NULL || expected == NULL)
|
|
return false;
|
|
size_t lp = strlen(presented), le = strlen(expected);
|
|
size_t n = lp > le ? lp : le;
|
|
unsigned diff = (unsigned)(lp != le);
|
|
for (size_t i = 0; i < n; i++) {
|
|
unsigned char a = i < lp ? (unsigned char)presented[i] : 0;
|
|
unsigned char b = i < le ? (unsigned char)expected[i] : 0;
|
|
diff |= (unsigned)(a ^ b);
|
|
}
|
|
return diff == 0;
|
|
}
|