#include #include #include #include const char* WIFI_SSID = "Protospace"; const char* WIFI_PASS = "yycmakers"; char wifiMACAddr[18]; const String SOCKET_URL = String("http://tools-socket.protospace.ca/api/lockout/"); const String CARD_URL = String("http://tools-auth.protospace.ca/cards/"); #define CARD_BUFFER_LENGTH 14 char cardBuffer[CARD_BUFFER_LENGTH]; #define CARD_DATA_LENGTH 10 #define CARD_CHECK_LENGTH 2 typedef struct __attribute__((packed)) cardData { char head; char data[CARD_DATA_LENGTH]; char checksum[CARD_CHECK_LENGTH]; char tail; } cardData_t; String authorizedCards = String("00000C5123,00000D1234,00000C5999,00000C5998,00000C5666"); #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_ON HIGH #define LED_OFF !LED_ON #define LOGGING true #define ONE_SECOND 1000 #define DELAY_TIME ONE_SECOND / 100 #define COMM_LOCK_IDLE_TIME ONE_SECOND / 2 #define COMM_CARD_IDLE_TIME ONE_SECOND * 60 * 5 enum wifiStates { WIFI_DISCONNECTED, WIFI_CONNECTING, WIFI_CONNECTED, } wifiState = WIFI_DISCONNECTED; enum lockStates { LOCK_OFF, LOCK_PREARM, LOCK_ARMED, LOCK_ON_PRESSED, // to wait until button is released LOCK_ON, } lockState = LOCK_OFF; enum commStates { COMM_INIT, COMM_IDLE, COMM_GET_LOCK, COMM_GET_CARD, } commState = COMM_INIT; void setup() { Serial.begin(9600); if (LOGGING) Serial.println("[INFO] Serial started."); pinMode(RELAY_PIN, OUTPUT); pinMode(GREEN_BUTTON_PIN, INPUT_PULLUP); pinMode(RED_BUTTON_PIN, INPUT_PULLUP); pinMode(GREEN_LED_PIN, OUTPUT); pinMode(RED_LED_PIN, OUTPUT); } void loop() { processWifiState(); processLockState(); processCommState(); if (Serial.available() >= CARD_BUFFER_LENGTH) { uint8_t bufPos = 0; while (true) { char readChar = Serial.read(); if (readChar == -1) { break; } else if (readChar == 0x2) { bufPos = 0; } if (bufPos >= CARD_BUFFER_LENGTH) { break; } cardBuffer[bufPos++] = readChar; if (readChar == 0x3 && bufPos == CARD_BUFFER_LENGTH) { checkCard(); break; } } } delay(DELAY_TIME); } int8_t charToNum(char input) { return String("0123456789ABCDEF").indexOf(input); } bool checksum(cardData_t *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 (int 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() { cardData_t *cardData = (cardData_t *) cardBuffer; if (cardData->head == 0x2 && cardData->tail == 0x3 && checksum(cardData)) { String cardStr = String(); for (int i = 0; i < CARD_DATA_LENGTH; i++) cardStr += cardData->data[i]; if (LOGGING) Serial.println("[INFO] Good scan from card: " + cardStr); if (authorizedCards.indexOf(cardStr) >= 0) { if (LOGGING) Serial.println("[INFO] Card is authorized on machine."); if (lockState == LOCK_OFF) { lockState = LOCK_PREARM; } } else { if (LOGGING) Serial.println("[INFO] Card not authorized or machine."); } } } void processWifiState() { switch(wifiState) { case WIFI_DISCONNECTED: commState = COMM_INIT; if (LOGGING) Serial.println("[INFO] Wifi is disconnected. 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 (LOGGING) Serial.println("[INFO] Wifi is connected."); if (LOGGING) Serial.print("[INFO] Wifi IP Address: "); if (LOGGING) Serial.println(WiFi.localIP()); byte ar[6]; WiFi.macAddress(ar); sprintf(wifiMACAddr, "%02X%02X%02X%02X%02X%02X", ar[0], ar[1], ar[2], ar[3], ar[4], ar[5]); if (LOGGING) Serial.print("[INFO] Wifi MAC Address: "); if (LOGGING) Serial.println(wifiMACAddr); wifiState = WIFI_CONNECTED; } break; case WIFI_CONNECTED: if (WiFi.status() != WL_CONNECTED) { wifiState = WIFI_DISCONNECTED; } break; default: if (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 greenLEDOn() { digitalWrite(GREEN_LED_PIN, LED_ON); } void greenLEDOff() { digitalWrite(GREEN_LED_PIN, LED_OFF); } void redLEDOn() { digitalWrite(RED_LED_PIN, LED_ON); } void redLEDOff() { digitalWrite(RED_LED_PIN, LED_OFF); } void relayOn() { digitalWrite(RELAY_PIN, RELAY_CLOSED); } void relayOff() { digitalWrite(RELAY_PIN, RELAY_OPEN); } void processLockState() { switch (lockState) { case LOCK_OFF: greenLEDOff(); redLEDOff(); relayOff(); break; case LOCK_PREARM: if (!greenButton() && !redButton()) { if (LOGGING) Serial.println("[INFO] Arming interlock."); lockState = LOCK_ARMED; } else { lockState = LOCK_OFF; } break; case LOCK_ARMED: greenLEDOn(); redLEDOff(); relayOff(); if (redButton()) { if (LOGGING) Serial.println("[INFO] Unarming interlock."); lockState = LOCK_OFF; } else if (greenButton()) { if (LOGGING) Serial.println("[INFO] On button pressed."); lockState = LOCK_ON_PRESSED; } break; case LOCK_ON_PRESSED: if (redButton()) { if (LOGGING) Serial.println("[ERROR] Off button pressed, aborting."); lockState = LOCK_OFF; } else if (!greenButton()) { if (LOGGING) Serial.println("[INFO] Turning machine on."); lockState = LOCK_ON; } break; case LOCK_ON: greenLEDOff(); redLEDOn(); relayOn(); if (redButton()) { if (LOGGING) Serial.println("[INFO] Off button pressed."); lockState = LOCK_OFF; } break; default: if (LOGGING) Serial.println("[ERROR] Invalid lock state."); lockState = LOCK_OFF; break; } } // JSON functions to prevent memory leaking String serializeLockJson(uint8_t lockState) { // Generated with: https://arduinojson.org/assistant/ const size_t bufferSize = JSON_OBJECT_SIZE(1) + 50; DynamicJsonBuffer jsonBuffer(bufferSize); 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; DynamicJsonBuffer jsonBuffer(bufferSize); JsonObject& root = jsonBuffer.parseObject(input); String action = root["action"]; return action; } void processCommState() { static uint16_t commLockIdleCount = 0; static uint16_t commCardIdleCount = 0; switch (commState) { case COMM_INIT: commLockIdleCount = 0; commCardIdleCount = 0; commState = COMM_IDLE; break; case COMM_IDLE: commLockIdleCount++; commCardIdleCount++; if (commLockIdleCount >= COMM_LOCK_IDLE_TIME / DELAY_TIME) { commState = COMM_GET_LOCK; } else if (commCardIdleCount >= COMM_CARD_IDLE_TIME / DELAY_TIME) { commState = COMM_GET_CARD; } break; case COMM_GET_LOCK: if (LOGGING) Serial.println("[INFO] Lock state HTTP begin."); HTTPClient http; //http.begin("https://url", "7a 9c f4 db 40 d3 62 5a 6e 21 bc 5c cc 66 c8 3e a1 45 59 38"); //HTTPS http.begin(SOCKET_URL + wifiMACAddr); http.addHeader("Content-Type", "application/json"); if (LOGGING) Serial.print("[INFO] HTTP POST: "); String postData = serializeLockJson(lockState); if (LOGGING) Serial.println(postData); uint16_t httpCode = http.POST(postData); if (httpCode > 0) { if (LOGGING) Serial.printf("[INFO] POST success, code: %d\n", httpCode); if (httpCode == HTTP_CODE_OK) { if (LOGGING) Serial.print("[INFO] Resource found, parsing response: "); String payload = http.getString(); if (LOGGING) Serial.println(payload); String action = deserializeLockJson(payload); if (action == "arm" && lockState == LOCK_OFF) { lockState = LOCK_PREARM; } else if (action == "disarm") { lockState = LOCK_OFF; } if (LOGGING) Serial.println("[INFO] action: " + action); } else { if (LOGGING) Serial.println("[ERROR] Resource not found."); } } else { if (LOGGING) Serial.printf("[ERROR] POST failed, error: %s\n", http.errorToString(httpCode).c_str()); } commLockIdleCount = 0; commState = COMM_IDLE; break; } }