|
|
|
@ -1,22 +1,8 @@ |
|
|
|
|
#include <Arduino.h> |
|
|
|
|
#include <ArduinoJson.h> |
|
|
|
|
#include <base64.h> |
|
|
|
|
#include <ESP8266WiFi.h> |
|
|
|
|
#include <ESP8266HTTPClient.h> |
|
|
|
|
#include <ESP8266httpUpdate.h> |
|
|
|
|
#include <EEPROM.h> |
|
|
|
|
#include <Ticker.h> |
|
|
|
|
#include <time.h> |
|
|
|
|
#include "firmware.h" |
|
|
|
|
|
|
|
|
|
#if !defined(ARDUINO_ESP8266_RELEASE_2_5_0) || ARDUINOJSON_VERSION_MAJOR != 5 || ARDUINOJSON_VERSION_MINOR != 13 || ARDUINOJSON_VERSION_REVISION != 3 |
|
|
|
|
#error Incorrect library version detected. See README. |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
const char *FIRMWARE_VERSION = "MRWIZARD 0010 MRWIZARD"; |
|
|
|
|
|
|
|
|
|
const char *WIFI_SSID PROGMEM = "Protospace"; |
|
|
|
|
const char *WIFI_PASS PROGMEM = "yycmakers"; |
|
|
|
|
char wifiMACAddr[20] = ""; |
|
|
|
|
const char *LOCKOUT_FIRMWARE_VERSION = "MRWIZARD 0010 MRWIZARD"; |
|
|
|
|
const char *LOCKOUT_WIFI_SSID PROGMEM = "Protospace"; |
|
|
|
|
const char *LOCKOUT_WIFI_PASS PROGMEM = "yycmakers"; |
|
|
|
|
const String SOCKET_URL = String("http://tools-socket.protospace.ca/api/lockout/"); |
|
|
|
|
const String CARD_URL = String("http://tools-auth.protospace.ca/cards/"); |
|
|
|
|
const String INFOLOG_URL = String("http://tools-auth.protospace.ca/infolog/"); |
|
|
|
@ -24,153 +10,15 @@ const String UPDATE_URL = String("http://tools-auth.protospace.ca/update/"); |
|
|
|
|
|
|
|
|
|
Ticker ticker; |
|
|
|
|
|
|
|
|
|
#define CARD_BUFFER_LENGTH 14 |
|
|
|
|
char wifiMACAddr[20] = ""; |
|
|
|
|
char cardBuffer[CARD_BUFFER_LENGTH]; |
|
|
|
|
|
|
|
|
|
#define CARD_DATA_LENGTH 10 |
|
|
|
|
#define CARD_CHECK_LENGTH 2 |
|
|
|
|
#define CARD_HEAD_BYTE 0x2 |
|
|
|
|
#define CARD_TAIL_BYTE 0x3 |
|
|
|
|
struct __attribute__((packed)) cardData { |
|
|
|
|
char head; |
|
|
|
|
char data[CARD_DATA_LENGTH]; |
|
|
|
|
char checksum[CARD_CHECK_LENGTH]; |
|
|
|
|
char tail; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
#define RELAY_PIN D1 |
|
|
|
|
#define GREEN_BUTTON_PIN D3 |
|
|
|
|
#define RED_BUTTON_PIN D4 |
|
|
|
|
#define GREEN_LED_PIN D6 |
|
|
|
|
#define RED_LED_PIN D7 |
|
|
|
|
|
|
|
|
|
#define RELAY_CLOSED HIGH |
|
|
|
|
#define RELAY_OPEN !RELAY_CLOSED |
|
|
|
|
#define BUTTON_CLOSED LOW |
|
|
|
|
#define BUTTON_OPEN !BUTTON_CLOSED |
|
|
|
|
#define LED_PIN_ON HIGH |
|
|
|
|
#define LED_PIN_OFF !LED_PIN_ON |
|
|
|
|
|
|
|
|
|
#define DELAY_TIME 10 |
|
|
|
|
// times below are multiplied by DELAY_TIME, in ms
|
|
|
|
|
#define LOCK_ARMED_TIMEOUT 1000 |
|
|
|
|
#define COMM_LOCK_IDLE_TIME 50 |
|
|
|
|
#define COMM_CARD_IDLE_TIME 1000 |
|
|
|
|
#define COMM_INFO_IDLE_TIME 3000 |
|
|
|
|
#define LED_ARMED_BLINK_TIME 50 |
|
|
|
|
#define LED_ERROR_BLINK_TIME 50 |
|
|
|
|
|
|
|
|
|
#define EEPROM_SIZE 4095 |
|
|
|
|
#define EEPROM_END_MARKER '\0' |
|
|
|
|
|
|
|
|
|
enum wifiStates |
|
|
|
|
{ |
|
|
|
|
WIFI_DISCONNECTED, |
|
|
|
|
WIFI_CONNECTING, |
|
|
|
|
WIFI_CONNECTED, |
|
|
|
|
} wifiState = WIFI_DISCONNECTED; |
|
|
|
|
|
|
|
|
|
enum LEDStates |
|
|
|
|
{ |
|
|
|
|
LED_OFF, |
|
|
|
|
LED_ARMED, |
|
|
|
|
LED_ON, |
|
|
|
|
LED_ERROR, |
|
|
|
|
} LEDState = LED_OFF; |
|
|
|
|
|
|
|
|
|
enum lockStates |
|
|
|
|
{ |
|
|
|
|
LOCK_OFF, |
|
|
|
|
LOCK_PREARM, // prevent arming while buttons held
|
|
|
|
|
LOCK_ARMED, |
|
|
|
|
LOCK_ON_PRESSED, // to wait until button is released
|
|
|
|
|
LOCK_ON, |
|
|
|
|
} lockState = LOCK_OFF; |
|
|
|
|
|
|
|
|
|
enum commStates |
|
|
|
|
{ |
|
|
|
|
COMM_INIT, |
|
|
|
|
COMM_IDLE, |
|
|
|
|
COMM_LOCK, |
|
|
|
|
COMM_CARD, |
|
|
|
|
COMM_INFO, |
|
|
|
|
} commState = COMM_INIT; |
|
|
|
|
|
|
|
|
|
#define SERIAL_LOGGING true |
|
|
|
|
|
|
|
|
|
#define LOG_SIZE 90 // 100 blew up the stack
|
|
|
|
|
#define LOG_DATA_LENGTH CARD_DATA_LENGTH |
|
|
|
|
|
|
|
|
|
enum eventCodes |
|
|
|
|
{ |
|
|
|
|
LOG_BOOT_UP, |
|
|
|
|
LOG_INIT_COMPLETE, |
|
|
|
|
LOG_WIFI_CONNECTED, |
|
|
|
|
LOG_WIFI_DISCONNECTED, |
|
|
|
|
LOG_COMM_LOCK_ARM, |
|
|
|
|
LOG_COMM_LOCK_DISARM, |
|
|
|
|
LOG_COMM_LOCK_FAIL, |
|
|
|
|
LOG_COMM_CARD_FAIL, |
|
|
|
|
LOG_COMM_INFO_FAIL, |
|
|
|
|
LOG_LOCK_OFF, |
|
|
|
|
LOG_LOCK_ARMED, |
|
|
|
|
LOG_LOCK_TIMEOUT, |
|
|
|
|
LOG_LOCK_ON, |
|
|
|
|
LOG_LOCK_DISARM, |
|
|
|
|
LOG_LOCK_ERROR, |
|
|
|
|
LOG_CARD_GOOD_READ, |
|
|
|
|
LOG_CARD_ACCEPTED, |
|
|
|
|
LOG_CARD_DENIED, |
|
|
|
|
LOG_UPDATE_FAILED, |
|
|
|
|
LOG_TEST, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct __attribute__((packed)) logData { |
|
|
|
|
uint32_t unixTime; |
|
|
|
|
uint8_t eventCode; |
|
|
|
|
char data[LOG_DATA_LENGTH]; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
struct logData eventLog[LOG_SIZE]; |
|
|
|
|
uint16_t logPosition = 0; |
|
|
|
|
|
|
|
|
|
void logEvent(uint8_t eventCode, const char *data = nullptr, size_t num = 0) |
|
|
|
|
{ |
|
|
|
|
struct logData event; |
|
|
|
|
|
|
|
|
|
noInterrupts(); |
|
|
|
|
|
|
|
|
|
event.unixTime = time(nullptr); |
|
|
|
|
event.eventCode = eventCode; |
|
|
|
|
|
|
|
|
|
memset(event.data, 0, LOG_DATA_LENGTH); |
|
|
|
|
for (uint8_t i = 0; i < LOG_DATA_LENGTH; i++) { |
|
|
|
|
if (i >= num) break; |
|
|
|
|
event.data[i] = data[i]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (logPosition < LOG_SIZE) { |
|
|
|
|
eventLog[logPosition++] = event; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interrupts(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void removeLogRecords(uint8_t num) { |
|
|
|
|
// shift records down by num because they've been sent
|
|
|
|
|
|
|
|
|
|
if (num > logPosition) return; |
|
|
|
|
|
|
|
|
|
noInterrupts(); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < num; i++) { |
|
|
|
|
eventLog[i] = eventLog[i + num]; |
|
|
|
|
} |
|
|
|
|
logPosition -= num; |
|
|
|
|
|
|
|
|
|
interrupts(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enum wifiStates wifiState = WIFI_DISCONNECTED; |
|
|
|
|
enum LEDStates LEDState = LED_OFF; |
|
|
|
|
enum lockStates lockState = LOCK_OFF; |
|
|
|
|
enum commStates commState = COMM_INIT; |
|
|
|
|
|
|
|
|
|
void setup() |
|
|
|
|
{ |
|
|
|
@ -182,9 +30,9 @@ void setup() |
|
|
|
|
settimeofday(&tv, &tz); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Set system time to 0."); |
|
|
|
|
|
|
|
|
|
logEvent(LOG_BOOT_UP, &FIRMWARE_VERSION[9], 4); |
|
|
|
|
logEvent(LOG_BOOT_UP, &LOCKOUT_FIRMWARE_VERSION[9], 4); |
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Booting firmware version: "); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(FIRMWARE_VERSION); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(LOCKOUT_FIRMWARE_VERSION); |
|
|
|
|
|
|
|
|
|
pinMode(RELAY_PIN, OUTPUT); |
|
|
|
|
pinMode(GREEN_BUTTON_PIN, INPUT_PULLUP); |
|
|
|
@ -244,523 +92,3 @@ void loop() |
|
|
|
|
|
|
|
|
|
delay(DELAY_TIME); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int8_t charToNum(char input) |
|
|
|
|
{ |
|
|
|
|
return String("0123456789ABCDEF").indexOf(input); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool checksum(struct cardData *cardData) |
|
|
|
|
{ |
|
|
|
|
// checksum is each hex data byte xord'd together.
|
|
|
|
|
// each char is a hex nibble, so we have to work in pairs.
|
|
|
|
|
|
|
|
|
|
int8_t even = 0, odd = 0; |
|
|
|
|
|
|
|
|
|
for (int8_t i = 0; i < CARD_DATA_LENGTH; i++) { |
|
|
|
|
int8_t num = charToNum(cardData->data[i]); |
|
|
|
|
|
|
|
|
|
if (num == -1) return false; |
|
|
|
|
if (i % 2 == 0) even ^= num; |
|
|
|
|
if (i % 2 == 1) odd ^= num; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int8_t checksum_even = charToNum(cardData->checksum[0]); |
|
|
|
|
int8_t checksum_odd = charToNum(cardData->checksum[1]); |
|
|
|
|
|
|
|
|
|
if (even == checksum_even && odd == checksum_odd) { |
|
|
|
|
return true; |
|
|
|
|
} else { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void checkCard() |
|
|
|
|
{ |
|
|
|
|
struct cardData *cardData = (struct cardData *) cardBuffer; |
|
|
|
|
|
|
|
|
|
if (cardData->head == CARD_HEAD_BYTE && |
|
|
|
|
cardData->tail == CARD_TAIL_BYTE && |
|
|
|
|
checksum(cardData)) { |
|
|
|
|
|
|
|
|
|
String cardStr = String(); |
|
|
|
|
String authorizedCards = String(); |
|
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < CARD_DATA_LENGTH; i++) { |
|
|
|
|
cardStr += cardData->data[i]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (uint16_t i = 0; i < EEPROM_SIZE; i++) { |
|
|
|
|
char tmp = EEPROM.read(i); |
|
|
|
|
authorizedCards += tmp; |
|
|
|
|
if (tmp == EEPROM_END_MARKER) break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Good scan from card: " + cardStr); |
|
|
|
|
logEvent(LOG_CARD_GOOD_READ, cardStr.c_str(), cardStr.length()); |
|
|
|
|
|
|
|
|
|
if (authorizedCards.indexOf(cardStr) >= 0) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Card is authorized on machine."); |
|
|
|
|
if (lockState == LOCK_OFF) { |
|
|
|
|
lockState = LOCK_PREARM; |
|
|
|
|
} |
|
|
|
|
logEvent(LOG_CARD_ACCEPTED, cardStr.c_str(), cardStr.length()); |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Card not authorized on machine."); |
|
|
|
|
LEDState = LED_ERROR; |
|
|
|
|
logEvent(LOG_CARD_DENIED, cardStr.c_str(), cardStr.length()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void processWifiState() |
|
|
|
|
{ |
|
|
|
|
switch(wifiState) { |
|
|
|
|
case WIFI_DISCONNECTED: |
|
|
|
|
commState = COMM_INIT; |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Wifi attempting to connect..."); |
|
|
|
|
|
|
|
|
|
WiFi.begin(WIFI_SSID, WIFI_PASS); |
|
|
|
|
|
|
|
|
|
wifiState = WIFI_CONNECTING; |
|
|
|
|
break; |
|
|
|
|
case WIFI_CONNECTING: |
|
|
|
|
commState = COMM_INIT; |
|
|
|
|
|
|
|
|
|
if (WiFi.status() == WL_CONNECTED) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Wifi is connected."); |
|
|
|
|
logEvent(LOG_WIFI_CONNECTED); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Wifi IP Address: "); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(WiFi.localIP()); |
|
|
|
|
|
|
|
|
|
wifiState = WIFI_CONNECTED; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case WIFI_CONNECTED: |
|
|
|
|
if (WiFi.status() != WL_CONNECTED) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Wifi disconnected."); |
|
|
|
|
logEvent(LOG_WIFI_DISCONNECTED); |
|
|
|
|
wifiState = WIFI_DISCONNECTED; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Invalid wifi state."); |
|
|
|
|
wifiState = WIFI_DISCONNECTED; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool greenButton() { return digitalRead(GREEN_BUTTON_PIN) == BUTTON_CLOSED; } |
|
|
|
|
bool redButton() { return digitalRead(RED_BUTTON_PIN) == BUTTON_CLOSED; } |
|
|
|
|
|
|
|
|
|
void relayOn() { digitalWrite(RELAY_PIN, RELAY_CLOSED); } |
|
|
|
|
void relayOff() { digitalWrite(RELAY_PIN, RELAY_OPEN); } |
|
|
|
|
|
|
|
|
|
void processLockState() |
|
|
|
|
{ |
|
|
|
|
static uint16_t lockArmedTimeoutCount; |
|
|
|
|
|
|
|
|
|
if (lockState != LOCK_ARMED) lockArmedTimeoutCount = 0; |
|
|
|
|
|
|
|
|
|
switch (lockState) { |
|
|
|
|
case LOCK_OFF: |
|
|
|
|
if (LEDState != LED_ERROR) LEDState = LED_OFF; |
|
|
|
|
|
|
|
|
|
relayOff(); |
|
|
|
|
break; |
|
|
|
|
case LOCK_PREARM: |
|
|
|
|
if (!greenButton() && !redButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Arming interlock."); |
|
|
|
|
logEvent(LOG_LOCK_ARMED); |
|
|
|
|
lockState = LOCK_ARMED; |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Buttons held, aborting."); |
|
|
|
|
logEvent(LOG_LOCK_ERROR); |
|
|
|
|
LEDState = LED_ERROR; |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case LOCK_ARMED: |
|
|
|
|
if (LEDState != LED_ERROR) LEDState = LED_ARMED; |
|
|
|
|
|
|
|
|
|
relayOff(); |
|
|
|
|
lockArmedTimeoutCount++; |
|
|
|
|
|
|
|
|
|
if (redButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Unarming interlock."); |
|
|
|
|
logEvent(LOG_LOCK_DISARM); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
} else if (greenButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] On button pressed."); |
|
|
|
|
lockState = LOCK_ON_PRESSED; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (lockArmedTimeoutCount > LOCK_ARMED_TIMEOUT) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Arming timed out, disarming."); |
|
|
|
|
logEvent(LOG_LOCK_TIMEOUT); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
LEDState = LED_ERROR; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case LOCK_ON_PRESSED: |
|
|
|
|
if (redButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Off button pressed, aborting."); |
|
|
|
|
logEvent(LOG_LOCK_ERROR); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
} else if (!greenButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Turning machine on."); |
|
|
|
|
logEvent(LOG_LOCK_ON); |
|
|
|
|
lockState = LOCK_ON; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case LOCK_ON: |
|
|
|
|
if (LEDState != LED_ERROR) LEDState = LED_ON; |
|
|
|
|
|
|
|
|
|
relayOn(); |
|
|
|
|
|
|
|
|
|
if (redButton()) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Off button pressed."); |
|
|
|
|
logEvent(LOG_LOCK_OFF); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Invalid lock state."); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void greenLEDOn() { digitalWrite(GREEN_LED_PIN, LED_PIN_ON); } |
|
|
|
|
void greenLEDOff() { digitalWrite(GREEN_LED_PIN, LED_PIN_OFF); } |
|
|
|
|
void redLEDOn() { digitalWrite(RED_LED_PIN, LED_PIN_ON); } |
|
|
|
|
void redLEDOff() { digitalWrite(RED_LED_PIN, LED_PIN_OFF); } |
|
|
|
|
|
|
|
|
|
void processLEDState() |
|
|
|
|
{ |
|
|
|
|
static uint16_t LEDArmedBlinkCount, LEDErrorBlinkCount; |
|
|
|
|
|
|
|
|
|
if (LEDState != LED_ARMED) LEDArmedBlinkCount = 0; |
|
|
|
|
if (LEDState != LED_ERROR) LEDErrorBlinkCount = 0; |
|
|
|
|
|
|
|
|
|
switch (LEDState) { |
|
|
|
|
case LED_OFF: |
|
|
|
|
greenLEDOff(); |
|
|
|
|
redLEDOn(); |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
case LED_ARMED: |
|
|
|
|
LEDArmedBlinkCount++; |
|
|
|
|
|
|
|
|
|
if (LEDArmedBlinkCount < LED_ARMED_BLINK_TIME) { |
|
|
|
|
greenLEDOn(); |
|
|
|
|
redLEDOn(); |
|
|
|
|
} else if (LEDArmedBlinkCount < LED_ARMED_BLINK_TIME * 2) { |
|
|
|
|
greenLEDOff(); |
|
|
|
|
redLEDOn(); |
|
|
|
|
} else { |
|
|
|
|
LEDArmedBlinkCount = 0; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case LED_ON: |
|
|
|
|
greenLEDOn(); |
|
|
|
|
redLEDOn(); |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
case LED_ERROR: |
|
|
|
|
LEDErrorBlinkCount++; |
|
|
|
|
|
|
|
|
|
if (LEDErrorBlinkCount < LED_ERROR_BLINK_TIME) { |
|
|
|
|
greenLEDOff(); |
|
|
|
|
redLEDOff(); |
|
|
|
|
} else if (LEDErrorBlinkCount < LED_ERROR_BLINK_TIME * 2) { |
|
|
|
|
greenLEDOff(); |
|
|
|
|
redLEDOn(); |
|
|
|
|
} else { |
|
|
|
|
LEDErrorBlinkCount = 0; |
|
|
|
|
LEDState = LED_OFF; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Invalid LED state."); |
|
|
|
|
LEDState = LED_OFF; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// JSON functions to prevent memory leaking see:
|
|
|
|
|
// https://arduinojson.org/v5/faq/i-found-a-memory-leak-in-the-library/
|
|
|
|
|
String serializeLockJson(uint8_t lockState) |
|
|
|
|
{ |
|
|
|
|
// Generated with: https://arduinojson.org/assistant/
|
|
|
|
|
const size_t bufferSize = JSON_OBJECT_SIZE(1) + 50; |
|
|
|
|
StaticJsonBuffer<bufferSize> jsonBuffer; |
|
|
|
|
|
|
|
|
|
JsonObject& root = jsonBuffer.createObject(); |
|
|
|
|
root["lockState"] = (uint8_t) lockState; |
|
|
|
|
String postData = String(); |
|
|
|
|
root.printTo(postData); |
|
|
|
|
|
|
|
|
|
return postData; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
String deserializeLockJson(String input) |
|
|
|
|
{ |
|
|
|
|
// Generated with: https://arduinojson.org/assistant/
|
|
|
|
|
const size_t bufferSize = JSON_OBJECT_SIZE(1) + 50; |
|
|
|
|
StaticJsonBuffer<bufferSize> jsonBuffer; |
|
|
|
|
JsonObject& root = jsonBuffer.parseObject(input); |
|
|
|
|
|
|
|
|
|
String action = root["action"]; |
|
|
|
|
|
|
|
|
|
return action; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
String serializeLog() { |
|
|
|
|
size_t logLengthBytes = logPosition * sizeof(struct logData); |
|
|
|
|
return "{\"log\": \"" + base64::encode((uint8_t *) eventLog, logLengthBytes, false) + "\"}"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void deserializeInfoJson(String input, uint8_t *processed, uint32_t *unixTime, String *version) |
|
|
|
|
{ |
|
|
|
|
// Generated with: https://arduinojson.org/assistant/
|
|
|
|
|
const size_t bufferSize = JSON_OBJECT_SIZE(3) + 70; |
|
|
|
|
StaticJsonBuffer<bufferSize> jsonBuffer; |
|
|
|
|
JsonObject& root = jsonBuffer.parseObject(input); |
|
|
|
|
|
|
|
|
|
*processed = root["processed"]; |
|
|
|
|
*unixTime = root["unixTime"]; |
|
|
|
|
*version = root["version"].as<String>(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: abstract http functions
|
|
|
|
|
|
|
|
|
|
void postState() |
|
|
|
|
{ |
|
|
|
|
// Don't log more than one error at a time
|
|
|
|
|
static bool logErrors = true; |
|
|
|
|
|
|
|
|
|
HTTPClient lockHTTP; |
|
|
|
|
|
|
|
|
|
//lockHTTP.begin("https://url", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); //HTTPS
|
|
|
|
|
lockHTTP.begin(SOCKET_URL + wifiMACAddr); |
|
|
|
|
lockHTTP.addHeader("Content-Type", "application/json"); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Lock state HTTP begin."); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] HTTP POST: "); |
|
|
|
|
String postData = serializeLockJson(lockState); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(postData); |
|
|
|
|
int16_t lockHTTPCode = lockHTTP.POST(postData); |
|
|
|
|
String lockHTTPCodeStr = String(lockHTTPCode); |
|
|
|
|
|
|
|
|
|
if (lockHTTPCode > 0) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[INFO] POST success, code: %d\n", lockHTTPCode); |
|
|
|
|
|
|
|
|
|
if (lockHTTPCode == HTTP_CODE_OK) { |
|
|
|
|
logErrors = true; |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Resource found, parsing response: "); |
|
|
|
|
String lockPayload = lockHTTP.getString(); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(lockPayload); |
|
|
|
|
String action = deserializeLockJson(lockPayload); |
|
|
|
|
|
|
|
|
|
if (action == "arm" && lockState == LOCK_OFF && LEDState == LED_OFF) { |
|
|
|
|
logEvent(LOG_COMM_LOCK_ARM); |
|
|
|
|
lockState = LOCK_PREARM; |
|
|
|
|
} else if (action == "disarm" && lockState != LOCK_ON) { |
|
|
|
|
logEvent(LOG_COMM_LOCK_DISARM); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Unarming interlock."); |
|
|
|
|
logEvent(LOG_LOCK_DISARM); |
|
|
|
|
lockState = LOCK_OFF; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] action: " + action); |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Resource not found."); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_LOCK_FAIL, lockHTTPCodeStr.c_str(), lockHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[ERROR] POST failed, error: %s\n", lockHTTP.errorToString(lockHTTPCode).c_str()); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_LOCK_FAIL, lockHTTPCodeStr.c_str(), lockHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
lockHTTP.end(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void getCards() |
|
|
|
|
{ |
|
|
|
|
// Don't log more than one error at a time
|
|
|
|
|
static bool logErrors = true; |
|
|
|
|
|
|
|
|
|
HTTPClient cardHTTP; |
|
|
|
|
|
|
|
|
|
//cardHTTP.begin("https://url", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); //HTTPS
|
|
|
|
|
cardHTTP.begin(CARD_URL + wifiMACAddr + "/"); |
|
|
|
|
cardHTTP.addHeader("Content-Type", "application/json"); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Card state HTTP begin."); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] HTTP GET"); |
|
|
|
|
int16_t cardHTTPCode = cardHTTP.GET(); |
|
|
|
|
String cardHTTPCodeStr = String(cardHTTPCode); |
|
|
|
|
|
|
|
|
|
if (cardHTTPCode > 0) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[INFO] GET success, code: %d\n", cardHTTPCode); |
|
|
|
|
|
|
|
|
|
if (cardHTTPCode == HTTP_CODE_OK) { |
|
|
|
|
logErrors = true; |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Resource found, parsing response: "); |
|
|
|
|
String cardPayload = cardHTTP.getString(); |
|
|
|
|
cardPayload += String(EEPROM_END_MARKER); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(cardPayload); |
|
|
|
|
|
|
|
|
|
noInterrupts(); // commit() disables interrupts, but we want an atomic EEPROM buffer write
|
|
|
|
|
for (int i = 0; i < cardPayload.length(); i++) { |
|
|
|
|
if (i >= EEPROM_SIZE) break; |
|
|
|
|
EEPROM.write(i, cardPayload.charAt(i)); |
|
|
|
|
} |
|
|
|
|
EEPROM.commit(); |
|
|
|
|
interrupts(); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Finished getting card data."); |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Resource not found."); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_CARD_FAIL, cardHTTPCodeStr.c_str(), cardHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[ERROR] POST failed, error: %s\n", cardHTTP.errorToString(cardHTTPCode).c_str()); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_CARD_FAIL, cardHTTPCodeStr.c_str(), cardHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cardHTTP.end(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void postInfolog() |
|
|
|
|
{ |
|
|
|
|
// Don't log more than one error at a time
|
|
|
|
|
static bool logErrors = true; |
|
|
|
|
|
|
|
|
|
HTTPClient infoHTTP; |
|
|
|
|
|
|
|
|
|
//infoHTTP.begin("https://url", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); //HTTPS
|
|
|
|
|
infoHTTP.begin(INFOLOG_URL + wifiMACAddr + "/"); |
|
|
|
|
infoHTTP.addHeader("Content-Type", "application/json"); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Info state HTTP begin."); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] HTTP POST."); |
|
|
|
|
String postData = serializeLog(); |
|
|
|
|
int16_t infoHTTPCode = infoHTTP.POST(postData); |
|
|
|
|
String infoHTTPCodeStr = String(infoHTTPCode); |
|
|
|
|
|
|
|
|
|
if (infoHTTPCode > 0) { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[INFO] POST success, code: %d\n", infoHTTPCode); |
|
|
|
|
|
|
|
|
|
if (infoHTTPCode == HTTP_CODE_OK) { |
|
|
|
|
logErrors = true; |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Resource found, parsing response: "); |
|
|
|
|
String infoPayload = infoHTTP.getString(); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(infoPayload); |
|
|
|
|
uint8_t processed; |
|
|
|
|
uint32_t unixTime; |
|
|
|
|
String version = String(); |
|
|
|
|
deserializeInfoJson(infoPayload, &processed, &unixTime, &version); |
|
|
|
|
|
|
|
|
|
struct timeval tv = { .tv_sec = unixTime, .tv_usec = 0 }; |
|
|
|
|
struct timezone tz = { .tz_minuteswest = 0, .tz_dsttime = 0 }; |
|
|
|
|
settimeofday(&tv, &tz); |
|
|
|
|
|
|
|
|
|
removeLogRecords(processed); |
|
|
|
|
|
|
|
|
|
if (version != FIRMWARE_VERSION && lockState == LOCK_OFF) { |
|
|
|
|
noInterrupts(); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[INFO] Firmware out of date. Updating..."); |
|
|
|
|
WiFiClient client; |
|
|
|
|
int16_t update_response = ESPhttpUpdate.update(client, UPDATE_URL + wifiMACAddr + "/"); |
|
|
|
|
interrupts(); |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) printf("[ERROR] %s\n", ESPhttpUpdate.getLastErrorString().c_str()); |
|
|
|
|
String lastErrorNum = String(ESPhttpUpdate.getLastError()); |
|
|
|
|
logEvent(LOG_UPDATE_FAILED, lastErrorNum.c_str(), lastErrorNum.length()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (SERIAL_LOGGING) Serial.print("[INFO] Set system time to: "); |
|
|
|
|
if (SERIAL_LOGGING) Serial.println(unixTime); |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.println("[ERROR] Resource not found."); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_INFO_FAIL, infoHTTPCodeStr.c_str(), infoHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (SERIAL_LOGGING) Serial.printf("[ERROR] POST failed, error: %s\n", infoHTTP.errorToString(infoHTTPCode).c_str()); |
|
|
|
|
if (logErrors) logEvent(LOG_COMM_INFO_FAIL, infoHTTPCodeStr.c_str(), infoHTTPCodeStr.length()); |
|
|
|
|
logErrors = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
infoHTTP.end(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void processCommState() |
|
|
|
|
{ |
|
|
|
|
static uint16_t commLockIdleCount, commCardIdleCount, commInfoIdleCount; |
|
|
|
|
|
|
|
|
|
switch (commState) { |
|
|
|
|
case COMM_INIT: |
|
|
|
|
commLockIdleCount = 0; |
|
|
|
|
commCardIdleCount = 0; |
|
|
|
|
commInfoIdleCount = 0; |
|
|
|
|
|
|
|
|
|
commState = COMM_IDLE; |
|
|
|
|
break; |
|
|
|
|
case COMM_IDLE: |
|
|
|
|
commLockIdleCount++; |
|
|
|
|
commCardIdleCount++; |
|
|
|
|
commInfoIdleCount++; |
|
|
|
|
|
|
|
|
|
if (commLockIdleCount >= COMM_LOCK_IDLE_TIME) { |
|
|
|
|
commState = COMM_LOCK; |
|
|
|
|
} else if (commCardIdleCount >= COMM_CARD_IDLE_TIME) { |
|
|
|
|
commState = COMM_CARD; |
|
|
|
|
} else if (commInfoIdleCount >= COMM_INFO_IDLE_TIME) { |
|
|
|
|
commState = COMM_INFO; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case COMM_LOCK: |
|
|
|
|
{ |
|
|
|
|
postState(); |
|
|
|
|
|
|
|
|
|
commLockIdleCount = 0; |
|
|
|
|
|
|
|
|
|
commState = COMM_IDLE; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case COMM_CARD: |
|
|
|
|
{ |
|
|
|
|
getCards(); |
|
|
|
|
|
|
|
|
|
commCardIdleCount = 0; |
|
|
|
|
|
|
|
|
|
commState = COMM_IDLE; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
case COMM_INFO: |
|
|
|
|
{ |
|
|
|
|
postInfolog(); |
|
|
|
|
|
|
|
|
|
commInfoIdleCount = 0; |
|
|
|
|
|
|
|
|
|
commState = COMM_IDLE; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|