#include #include #include #include const char* WIFI_SSID = "Protospace"; const char* WIFI_PASS = "yycmakers"; char wifiMACAddr[18]; const String API_ROUTE = String("http://tools-api.tannercollin.com/api/lockout/"); #define RELAY_PIN D1 #define ON_BUTTON_PIN D3 #define OFF_BUTTON_PIN D4 #define ARMED_LED_PIN D5 #define ON_LED_PIN D6 #define OFF_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 DELAY_TIME 10 #define COMM_IDLE_TIME 500 / DELAY_TIME enum wifiStates { WIFI_DISCONNECTED, WIFI_CONNECTING, WIFI_CONNECTED, } wifiState = WIFI_DISCONNECTED; enum lockStates { LOCK_OFF, LOCK_ARMED, LOCK_ON_PRESSED, LOCK_ON, LOCK_OFF_PRESSED, } lockState = LOCK_OFF; enum commStates { COMM_INIT, COMM_IDLE, COMM_SEND, } commState = COMM_INIT; void setup() { if (LOGGING) Serial.begin(115200); if (LOGGING) Serial.println("[INFO] Serial started."); pinMode(RELAY_PIN, OUTPUT); pinMode(ON_BUTTON_PIN, INPUT_PULLUP); pinMode(OFF_BUTTON_PIN, INPUT_PULLUP); pinMode(ARMED_LED_PIN, OUTPUT); pinMode(ON_LED_PIN, OUTPUT); pinMode(OFF_LED_PIN, OUTPUT); } void loop() { processWifiState(); processLockState(); processCommState(); delay(DELAY_TIME); } void processWifiState() { switch(wifiState) { case WIFI_DISCONNECTED: lockState = LOCK_OFF; 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: lockState = LOCK_OFF; 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; } } void processLockState() { switch (lockState) { case LOCK_OFF: digitalWrite(ARMED_LED_PIN, LED_OFF); digitalWrite(ON_LED_PIN, LED_OFF); digitalWrite(OFF_LED_PIN, LED_ON); digitalWrite(RELAY_PIN, RELAY_OPEN); break; case LOCK_ARMED: digitalWrite(ARMED_LED_PIN, LED_ON); digitalWrite(ON_LED_PIN, LED_OFF); digitalWrite(OFF_LED_PIN, LED_ON); digitalWrite(RELAY_PIN, RELAY_OPEN); if (digitalRead(OFF_BUTTON_PIN) == BUTTON_CLOSED) { if (LOGGING) Serial.println("[INFO] Unarming interlock."); lockState = LOCK_OFF_PRESSED; } else if (digitalRead(ON_BUTTON_PIN) == BUTTON_CLOSED) { if (LOGGING) Serial.println("[INFO] On button pressed."); lockState = LOCK_ON_PRESSED; } break; case LOCK_ON_PRESSED: if (digitalRead(OFF_BUTTON_PIN) == BUTTON_CLOSED) { if (LOGGING) Serial.println("[ERROR] Both buttons pressed, aborting."); lockState = LOCK_OFF_PRESSED; } else if (digitalRead(ON_BUTTON_PIN) == BUTTON_OPEN) { if (LOGGING) Serial.println("[INFO] Turning machine on."); lockState = LOCK_ON; } break; case LOCK_ON: digitalWrite(ARMED_LED_PIN, LED_ON); digitalWrite(ON_LED_PIN, LED_ON); digitalWrite(OFF_LED_PIN, LED_OFF); digitalWrite(RELAY_PIN, RELAY_CLOSED); if (digitalRead(OFF_BUTTON_PIN) == BUTTON_CLOSED) { if (LOGGING) Serial.println("[INFO] Off button pressed."); lockState = LOCK_OFF_PRESSED; } break; case LOCK_OFF_PRESSED: if (digitalRead(OFF_BUTTON_PIN) == BUTTON_OPEN) { if (LOGGING) Serial.println("[INFO] Turning machine off."); lockState = LOCK_OFF; } break; default: if (LOGGING) Serial.println("[ERROR] Invalid lock state."); lockState = LOCK_OFF; break; } } // JSON functions to prevent memory leaking String serializeJson(int 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"] = (int) lockState; String postData = String(); root.printTo(postData); return postData; } String deserializeJson(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 int commIdleCount = 0; switch (commState) { case COMM_INIT: commIdleCount = 0; commState = COMM_IDLE; break; case COMM_IDLE: commIdleCount++; if (commIdleCount >= COMM_IDLE_TIME) { commState = COMM_SEND; } break; case COMM_SEND: if (LOGGING) Serial.println("[INFO] 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(API_ROUTE + wifiMACAddr); http.addHeader("Content-Type", "application/json"); if (LOGGING) Serial.print("[INFO] HTTP POST: "); String postData = serializeJson(lockState); if (LOGGING) Serial.println(postData); int 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 = deserializeJson(payload); if (action == "arm" && lockState == LOCK_OFF && digitalRead(ON_BUTTON_PIN) == BUTTON_OPEN) { lockState = LOCK_ARMED; } else if (action == "disarm" && lockState == LOCK_ARMED) { lockState = LOCK_OFF; } else if (action == "disarm" && lockState == LOCK_ON) { 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()); } commState = COMM_INIT; break; } }