Commit WIP code
This commit is contained in:
555
firmware/firmware.ino
Normal file
555
firmware/firmware.ino
Normal file
@@ -0,0 +1,555 @@
|
||||
// Tool Lockout
|
||||
// Controls access to machines based on card scans
|
||||
//
|
||||
// Arduino IDE 2.3.4
|
||||
// Board: ESP32
|
||||
// - select "ESP32 Dev Module"
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <Wire.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <Adafruit_NeoPixel.h> // version 1.15.2
|
||||
//#include <WebServer.h>
|
||||
//#include <ArduinoJson.h> // v6.19.4
|
||||
//#include <ElegantOTA.h> // v2.2.9
|
||||
//#include <WebSerial.h> // v1.3.0
|
||||
|
||||
#include "secrets.h"
|
||||
|
||||
//String portalAPI = "https://api.my.protospace.ca";
|
||||
String portalAPI = "https://api.spaceport.dns.t0.vc";
|
||||
|
||||
WiFiClientSecure wc;
|
||||
|
||||
String authorizedCards = "";
|
||||
String scannedCard = "";
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
#define LED_PIN 18
|
||||
#define NUMPIXELS 1
|
||||
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
#define BUTTON1SW D5
|
||||
#define NUM_BUTTONS 1
|
||||
|
||||
#define BUTTON_HOLD_TIME 10000
|
||||
#define BUTTON_PRESS_TIME 20
|
||||
|
||||
#define CONTROLLER_IDLE_DELAY_MS 4500
|
||||
#define CONTROLLER_UI_DELAY_MS 1000
|
||||
#define CONTROLLER_OFFER_DELAY_MS 90000
|
||||
#define CONNECT_TIMEOUT_MS 30000
|
||||
#define ELLIPSIS_ANIMATION_DELAY_MS 1000
|
||||
#define SCROLL_ANIMATION_DELAY_MS 250
|
||||
|
||||
enum lockoutStates {
|
||||
LOCKOUT_BEGIN,
|
||||
LOCKOUT_DISENGAGED,
|
||||
LOCKOUT_ENGAGED,
|
||||
NUM_LOCKOUTSTATES
|
||||
};
|
||||
enum lockoutStates lockoutState = LOCKOUT_BEGIN;
|
||||
|
||||
enum buttonStates {
|
||||
BUTTON_OPEN,
|
||||
BUTTON_CLOSED,
|
||||
BUTTON_CHECK_PRESSED,
|
||||
BUTTON_PRESSED,
|
||||
BUTTON_HELD,
|
||||
NUM_BUTTONSTATES,
|
||||
};
|
||||
enum buttonStates button1State = BUTTON_OPEN;
|
||||
|
||||
enum controllerStates {
|
||||
CONTROLLER_BEGIN,
|
||||
CONTROLLER_WIFI_CONNECT,
|
||||
CONTROLLER_GET_TIME,
|
||||
CONTROLLER_IDLE,
|
||||
CONTROLLER_PING,
|
||||
CONTROLLER_OFFER_HOST,
|
||||
CONTROLLER_SEND_HOURS,
|
||||
CONTROLLER_IDLE_DELAY,
|
||||
CONTROLLER_IDLE_WAIT,
|
||||
CONTROLLER_UI_DELAY,
|
||||
CONTROLLER_UI_WAIT,
|
||||
};
|
||||
enum controllerStates controllerState = CONTROLLER_BEGIN;
|
||||
|
||||
enum LEDStates {
|
||||
LED_BEGIN,
|
||||
LED_INIT,
|
||||
LED_IDLE,
|
||||
};
|
||||
enum LEDStates LEDState = LED_BEGIN;
|
||||
|
||||
void rebootArduino() {
|
||||
delay(1000);
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
void processLockoutState() {
|
||||
static unsigned long timer = millis();
|
||||
|
||||
switch (lockoutState) {
|
||||
case LOCKOUT_BEGIN:
|
||||
Serial.println("[LOCK] Begin");
|
||||
|
||||
lockoutState = LOCKOUT_DISENGAGED;
|
||||
break;
|
||||
|
||||
case LOCKOUT_DISENGAGED:
|
||||
break;
|
||||
|
||||
case LOCKOUT_CHECK_CARD:
|
||||
if (scannedCard.length() == 0) {
|
||||
lockoutState = LOCKOUT_UNAUTHORIZED_BEGIN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (authorizedCards.indexOf(scannedCard) == -1) {
|
||||
lockoutState = LOCKOUT_UNAUTHORIZED_BEGIN;
|
||||
break;
|
||||
}
|
||||
|
||||
lockoutState = LOCKOUT_AUTHORIZED;
|
||||
|
||||
break;
|
||||
|
||||
case LOCKOUT_UNAUTHORIZED_BEGIN:
|
||||
timer = millis();
|
||||
LEDState = LED_DENIED;
|
||||
lockoutState = LOCKOUT_UNAUTHORIZED_DELAY;
|
||||
break;
|
||||
|
||||
case LOCKOUT_UNAUTHORIZED_DELAY:
|
||||
if (millis() - timer > 4000) { // overflow safe
|
||||
LEDState = LED_IDLE;
|
||||
lockoutState = LOCKOUT_DISENGAGED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case LOCKOUT_AUTHORIZED:
|
||||
LEDState = LED_ENGAGED;
|
||||
lockoutState = LOCKOUT_ENGAGED;
|
||||
break;
|
||||
|
||||
case LOCKOUT_ENGAGED:
|
||||
if (button1State == BUTTON_PRESSED) {
|
||||
LEDState = LED_IDLE;
|
||||
lockoutState = LOCKOUT_DISENGAGED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void processControllerState() {
|
||||
static unsigned long timer = millis();
|
||||
static unsigned long prev_scan_time = 0;
|
||||
static enum controllerStates nextControllerState;
|
||||
static int statusCode;
|
||||
static int retryCount;
|
||||
|
||||
String response;
|
||||
String postData;
|
||||
|
||||
bool failed;
|
||||
time_t now;
|
||||
struct tm timeinfo;
|
||||
int i;
|
||||
int result;
|
||||
HTTPClient https;
|
||||
|
||||
switch (controllerState) {
|
||||
case CONTROLLER_BEGIN:
|
||||
|
||||
Serial.println("[WIFI] Connecting...");
|
||||
retryCount = 0;
|
||||
|
||||
timer = millis();
|
||||
controllerState = CONTROLLER_WIFI_CONNECT;
|
||||
break;
|
||||
|
||||
case CONTROLLER_WIFI_CONNECT:
|
||||
LEDState = LED_INIT;
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.print("[WIFI] Connected. IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
Serial.println("[TIME] Setting time using NTP.");
|
||||
configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
||||
nextControllerState = CONTROLLER_GET_TIME;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
if (millis() - timer > CONNECT_TIMEOUT_MS) { // overflow safe
|
||||
WiFi.disconnect();
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
delay(5000);
|
||||
|
||||
rebootArduino();
|
||||
}
|
||||
timer = millis();
|
||||
|
||||
break;
|
||||
|
||||
case CONTROLLER_GET_TIME:
|
||||
|
||||
time(&now);
|
||||
if (now > 8 * 3600 * 2) {
|
||||
gmtime_r(&now, &timeinfo);
|
||||
Serial.print("[TIME] Current time in UTC: ");
|
||||
Serial.print(asctime(&timeinfo));
|
||||
|
||||
|
||||
Serial.println("Moving to idle state...");
|
||||
controllerState = CONTROLLER_IDLE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (millis() - timer > CONNECT_TIMEOUT_MS) { // overflow safe
|
||||
WiFi.disconnect();
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
nextControllerState = CONTROLLER_BEGIN;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CONTROLLER_IDLE:
|
||||
|
||||
LEDState = LED_IDLE;
|
||||
|
||||
break;
|
||||
|
||||
case CONTROLLER_PING:
|
||||
|
||||
LEDState = LEDS_OFF;
|
||||
|
||||
result = https.begin(wc, portalAPI + "/stats/");
|
||||
|
||||
if (!result) {
|
||||
Serial.println("[WIFI] https.begin failed.");
|
||||
|
||||
retryCount++;
|
||||
if (retryCount > 5) {
|
||||
nextControllerState = CONTROLLER_BEGIN;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
controllerState = CONTROLLER_IDLE_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
result = https.GET();
|
||||
|
||||
Serial.printf("[WIFI] Http code: %d\n", result);
|
||||
|
||||
if (result != HTTP_CODE_OK) {
|
||||
Serial.printf("[WIFI] Portal GET failed, error:\n%s\n", https.errorToString(result).c_str());
|
||||
|
||||
retryCount++;
|
||||
if (retryCount > 5) {
|
||||
// TODO: display this each time with a retry count below
|
||||
nextControllerState = CONTROLLER_BEGIN;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
controllerState = CONTROLLER_IDLE_DELAY;
|
||||
break;
|
||||
}
|
||||
retryCount = 0;
|
||||
|
||||
response = https.getString();
|
||||
|
||||
if (prev_scan_time != 0 && prev_scan_time != scan_time) {
|
||||
Serial.println("[SCAN] New scan, offering to host.");
|
||||
|
||||
|
||||
controllerState = CONTROLLER_OFFER_HOST;
|
||||
timer = millis();
|
||||
prev_scan_time = scan_time; // delete?
|
||||
break;
|
||||
}
|
||||
prev_scan_time = scan_time;
|
||||
|
||||
time(&now);
|
||||
|
||||
if (closing_time > (unsigned long) now) {
|
||||
Serial.print("[HOST] Protospace is open, showing closing time: ");
|
||||
Serial.print(closing_time_str);
|
||||
Serial.print(" host: ");
|
||||
Serial.println(host_name);
|
||||
|
||||
} else {
|
||||
}
|
||||
|
||||
controllerState = CONTROLLER_IDLE_DELAY;
|
||||
|
||||
break;
|
||||
|
||||
case CONTROLLER_OFFER_HOST:
|
||||
LEDState = LEDS_SCROLL;
|
||||
|
||||
if (button1State == BUTTON_PRESSED) {
|
||||
host_hours = 2;
|
||||
LEDState = LEDS_BUTTON1;
|
||||
controllerState = CONTROLLER_SEND_HOURS;
|
||||
break;
|
||||
} else if (button2State == BUTTON_PRESSED) {
|
||||
host_hours = 4;
|
||||
LEDState = LEDS_BUTTON2;
|
||||
controllerState = CONTROLLER_SEND_HOURS;
|
||||
break;
|
||||
} else if (button3State == BUTTON_PRESSED) {
|
||||
host_hours = 6;
|
||||
LEDState = LEDS_BUTTON3;
|
||||
controllerState = CONTROLLER_SEND_HOURS;
|
||||
break;
|
||||
} else if (millis() - timer > CONTROLLER_OFFER_DELAY_MS) { // overflow safe
|
||||
Serial.println("[HOST] Offer timed out, returning to idle state.");
|
||||
controllerState = CONTROLLER_IDLE;
|
||||
LEDState = LEDS_OFF;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CONTROLLER_SEND_HOURS:
|
||||
Serial.println("[HOST] Sending hosting hours to portal.");
|
||||
|
||||
result = https.begin(wc, portalAPI + "/hosting/offer/");
|
||||
|
||||
if (!result) {
|
||||
Serial.println("[HOST] https.begin failed.");
|
||||
nextControllerState = CONTROLLER_BEGIN;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
postData = "member_id="
|
||||
+ String(member_id)
|
||||
+ "&hours="
|
||||
+ host_hours;
|
||||
|
||||
Serial.println("[HOST] POST data:");
|
||||
Serial.println(postData);
|
||||
|
||||
https.addHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
https.addHeader("Content-Length", String(postData.length()));
|
||||
https.addHeader("Authorization", VANGUARD_API_TOKEN);
|
||||
result = https.POST(postData);
|
||||
|
||||
Serial.printf("[HOST] Http code: %d\n", result);
|
||||
|
||||
if (result != HTTP_CODE_OK) {
|
||||
Serial.printf("[HOST] Bad send, error:\n%s\n", https.errorToString(result).c_str());
|
||||
nextControllerState = CONTROLLER_BEGIN;
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
controllerState = CONTROLLER_UI_DELAY;
|
||||
nextControllerState = CONTROLLER_IDLE;
|
||||
break;
|
||||
|
||||
|
||||
case CONTROLLER_IDLE_DELAY:
|
||||
timer = millis();
|
||||
controllerState = CONTROLLER_IDLE_WAIT;
|
||||
break;
|
||||
|
||||
case CONTROLLER_IDLE_WAIT:
|
||||
if (millis() - timer > CONTROLLER_IDLE_DELAY_MS) { // overflow safe
|
||||
controllerState = CONTROLLER_IDLE;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTROLLER_UI_DELAY:
|
||||
timer = millis();
|
||||
controllerState = CONTROLLER_UI_WAIT;
|
||||
break;
|
||||
|
||||
case CONTROLLER_UI_WAIT:
|
||||
if (millis() - timer > CONTROLLER_UI_DELAY_MS) { // overflow safe
|
||||
controllerState = nextControllerState;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void setLEDOff() {
|
||||
pixels.setPixelColor(0, pixels.Color(0, 0, 0));
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void setLEDRed() {
|
||||
pixels.setPixelColor(0, pixels.Color(150, 0, 0));
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void setLEDGreen() {
|
||||
pixels.setPixelColor(0, pixels.Color(0, 150, 0));
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void setLEDBlue() {
|
||||
pixels.setPixelColor(0, pixels.Color(0, 0, 150));
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
void setLEDOrange() {
|
||||
pixels.setPixelColor(0, pixels.Color(150, 75, 0));
|
||||
pixels.show();
|
||||
}
|
||||
|
||||
|
||||
void processLEDState() {
|
||||
switch(LEDState) {
|
||||
case LED_BEGIN:
|
||||
setLEDOff();
|
||||
break;
|
||||
|
||||
case LED_INIT:
|
||||
setLEDOrange();
|
||||
break;
|
||||
|
||||
case LED_IDLE:
|
||||
setLEDRed();
|
||||
|
||||
// TODO: blink orange if there's an error
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void pollButtons() {
|
||||
static unsigned long button1Time = 0;
|
||||
|
||||
processButtonState(BUTTON1SW, button1State, button1Time);
|
||||
|
||||
if (button1State == BUTTON_PRESSED) {
|
||||
Serial.println("Button 1 pressed");
|
||||
} else if (button1State == BUTTON_HELD) {
|
||||
Serial.println("Button 1 held");
|
||||
}
|
||||
}
|
||||
|
||||
void processButtonState(int buttonPin, buttonStates &buttonState, unsigned long &buttonTime) {
|
||||
bool pinState = !digitalRead(buttonPin);
|
||||
|
||||
switch(buttonState) {
|
||||
case BUTTON_OPEN:
|
||||
if (pinState) {
|
||||
buttonState = BUTTON_CLOSED;
|
||||
buttonTime = millis();
|
||||
}
|
||||
break;
|
||||
case BUTTON_CLOSED:
|
||||
if (millis() >= buttonTime + BUTTON_HOLD_TIME) {
|
||||
buttonState = BUTTON_HELD;
|
||||
}
|
||||
if (pinState) {
|
||||
;
|
||||
} else {
|
||||
buttonState = BUTTON_CHECK_PRESSED;
|
||||
}
|
||||
break;
|
||||
case BUTTON_CHECK_PRESSED:
|
||||
if (millis() >= buttonTime + BUTTON_PRESS_TIME) {
|
||||
buttonState = BUTTON_PRESSED;
|
||||
} else {
|
||||
buttonState = BUTTON_OPEN;
|
||||
}
|
||||
break;
|
||||
case BUTTON_PRESSED:
|
||||
buttonState = BUTTON_OPEN;
|
||||
break;
|
||||
case BUTTON_HELD:
|
||||
if (!pinState) {
|
||||
buttonState = BUTTON_OPEN;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("======= BOOT UP =======");
|
||||
|
||||
pinMode(BUTTON1LED, OUTPUT);
|
||||
|
||||
pinMode(BUTTON1SW, INPUT_PULLUP);
|
||||
|
||||
delay(1000);
|
||||
|
||||
pixels.begin();
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(WIFI_SSID, WIFI_PASS);
|
||||
|
||||
//X509List cert(lets_encrypt_ca);
|
||||
//wc.setTrustAnchors(&cert);
|
||||
wc.setInsecure(); // disables all SSL checks. don't use in production
|
||||
|
||||
Serial.println("Setup complete.");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
pollButtons();
|
||||
processLockoutState();
|
||||
processControllerState();
|
||||
processLEDState();
|
||||
|
||||
if (Serial2.available() > 0) {
|
||||
String data = Serial2.readString();
|
||||
|
||||
Serial.print("RFID scan: ");
|
||||
Serial.print(data);
|
||||
Serial.print(", len: ");
|
||||
Serial.println(data.length());
|
||||
|
||||
if (data.substring(1, 11) == "0700B5612A") {
|
||||
rebootArduino();
|
||||
}
|
||||
|
||||
if (controllerState == CONTROLLER_IDLE && lockoutState == LOCKOUT_DISENGAGED) {
|
||||
scannedCard = data.substring(1, 11);
|
||||
|
||||
Serial.print("Card: ");
|
||||
Serial.println(scannedCard);
|
||||
|
||||
lockoutState = LOCKOUT_CHECK_CARD;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user