Last active
December 19, 2025 23:52
-
-
Save Tech500/3f6b0540f3fa074851d8479e403b3e75 to your computer and use it in GitHub Desktop.
E220 LoRa, Remote Switch --Receiver and Transmitter 12/17/2025 @ 17:03 EST
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //E220_Remote_Switch_Sender.ino Vrsion XI | |
| //William Lucid in colboration with ChatGPT Updated 12/17/2025 @ 07:27 EST | |
| //E220 Module is set to ADDL 3 | |
| //Fully connectd schema AUX connected to ESP32, GPIO18 | |
| //Ardino IDE: ESP32 Board Manager, Version 2.0.17 | |
| // See library downloads for each library license. | |
| // With FIXED SENDER configuration | |
| #include <Arduino.h> | |
| #include "WiFi.h" | |
| #include <WiFiUdp.h> | |
| #include <HTTPClient.h> | |
| #include <time.h> | |
| #include <Ticker.h> | |
| #include "LoRa_E220.h" | |
| #include <AsyncTCP.h> | |
| #include "ESPAsyncWebServer.h" | |
| #include "esp_sleep.h" | |
| #include <Wire.h> | |
| #include <queue> | |
| #include "Message.h" | |
| #include "XorWake.h" | |
| #include "stdio.h" | |
| #import "index7.h" //Video feed HTML; do not remove | |
| // Replace with your network details | |
| const char *ssid = "R2D2"; | |
| const char *password = "Sky7388500"; | |
| #define DESTINATION_ADDL 2 | |
| #define FREQENCY_927 | |
| #define CHANNEL 23 | |
| #define RXD2 16 | |
| #define TXD2 17 | |
| #define M0_PIN GPIO_NUM_21 | |
| #define M1_PIN GPIO_NUM_19 | |
| #define AUX_PIN GPIO_NUM_27 | |
| #define CONTROL_PIN 13 //RTC_IO wake capable for XOR function | |
| // ---------- esp32 pins ---------------- | |
| LoRa_E220 e220ttl(&Serial2, 27, 21, 19); // RX AUX M0 M1 | |
| // Struct to hold date and time components | |
| struct DateTime { | |
| int year; | |
| int month; | |
| int day; | |
| int hour; | |
| int minute; | |
| int second; | |
| }; | |
| // Define the maximum length for the timestamp | |
| const int MAX_dateTime_LENGTH = 30; | |
| int data = 0; | |
| int switchState; | |
| struct Messages { | |
| int switchState; | |
| char dateTime[MAX_dateTime_LENGTH]; // Array to hold date/time string | |
| }; | |
| Messages outgoing; | |
| void updateTimestamp() { | |
| String timestamp = get_time(); | |
| timestamp.toCharArray(outgoing.dateTime, MAX_dateTime_LENGTH); | |
| } | |
| struct Task { | |
| int id; | |
| void (*func)(); | |
| }; | |
| std::queue<Task> taskQueue; | |
| void runTask(Task t); | |
| int delayTime = 100; //setmode delay duration | |
| //bool sendWORFlag = false; | |
| char time_output[MAX_dateTime_LENGTH]; | |
| WiFiClient client; | |
| ///Are we currently connected? | |
| boolean connected = false; | |
| WiFiUDP udp; | |
| // local port to listen for UDP packets | |
| const int udpPort = 1337; | |
| char incomingPacket[255]; | |
| char replyPacket[] = "Hi there! Got the message :-)"; | |
| //NTP Time Servers | |
| const char *udpAddress1 = "pool.ntp.org"; | |
| const char *udpAddress2 = "time.nist.gov"; | |
| #define TZ "EST+5EDT,M3.2.0/2,M11.1.0/2" | |
| // Function to get current date and time | |
| DateTime getCurrentDateTime() { | |
| DateTime currentDateTime; | |
| time_t now = time(nullptr); | |
| struct tm *ti = localtime(&now); | |
| // Extract individual components | |
| currentDateTime.year = ti->tm_year + 1900; | |
| currentDateTime.month = ti->tm_mon + 1; | |
| currentDateTime.day = ti->tm_mday; | |
| currentDateTime.hour = ti->tm_hour; | |
| currentDateTime.minute = ti->tm_min; | |
| currentDateTime.second = ti->tm_sec; | |
| return currentDateTime; | |
| } | |
| // Function to get the dateTime | |
| String get_time() { | |
| time_t now; | |
| time(&now); | |
| strftime(time_output, MAX_dateTime_LENGTH, "%a %m/%d/%y %T", localtime(&now)); | |
| return String(time_output); // returns dateTime in the specified format | |
| } | |
| AsyncWebServer server(80); | |
| volatile bool switchWebFlag = false; | |
| void IRAM_ATTR wakeUp() { | |
| // Do not use Serial on interrupt callback | |
| switchWebFlag = true; | |
| } | |
| Ticker oneTick; | |
| Ticker onceTick; | |
| Ticker sendDelay; | |
| String linkAddress = "192.123.12.27:80"; | |
| portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; | |
| volatile int watchdogCounter; | |
| int totalwatchdogCounter; | |
| int cameraPowerOff = 0; | |
| int watchDog; | |
| void ISRwatchdog() { | |
| portENTER_CRITICAL_ISR(&mux); | |
| //watchdogCounter++; | |
| if (watchdogCounter >= 5000) { | |
| watchDog = 1; | |
| } | |
| portEXIT_CRITICAL_ISR(&mux); | |
| } | |
| void sendWOR() { | |
| if(switchWebFlag){ | |
| switchWebFlag = false; | |
| uint16_t duration_ms = 2600; | |
| e220ttl.setMode(MODE_1_WOR_TRANSMITTER); | |
| uint8_t preamble[4] = { 0xAA, 0xAA, 0xAA, 0xAA }; | |
| uint32_t start = millis(); | |
| while ((millis() - start) < duration_ms) { | |
| ResponseStatus rs = e220ttl.sendFixedMessage(0, 0x02, CHANNEL, preamble, sizeof(preamble)); | |
| while (digitalRead(AUX_PIN) == LOW) { delay(1); } // pace by AUX | |
| // Send actual message | |
| rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &outgoing, sizeof(Message)); | |
| Serial.println(rs.getResponseDescription()); | |
| } | |
| // Return to normal mode | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| } | |
| } | |
| int switchOne(int data) { | |
| switchState = data; | |
| if (data == 1) { | |
| data = 1; | |
| Serial.println("\nESP32 waking from Deep Sleep"); | |
| Serial.println("Battery Switch is ON\n"); | |
| } | |
| if (data == 2) { | |
| data = 2; | |
| Serial.println("\nBattery power switched OFF"); | |
| Serial.println("ESP32 going to Deep Sleep\n"); | |
| } | |
| // 1. Prepare Message | |
| memset(&outgoing, 0, sizeof(Message)); | |
| outgoing.switchState = data; | |
| strncpy(outgoing.dateTime, time_output, MAX_dateTime_LENGTH); | |
| Serial.print("Sending WOR message for switchState: "); | |
| Serial.println(outgoing.switchState); | |
| Serial.println(outgoing.dateTime); | |
| sendWOR(); | |
| /* | |
| ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &outgoing, sizeof(Message)); | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| Serial.println(rs.getResponseDescription()); | |
| */ | |
| //delay(1000); | |
| switchWebFlag = true; | |
| return data; | |
| } | |
| int cameraFlag; | |
| int needAnotherCountdown = 0; | |
| int lastXorResult = 0; | |
| void batteryOff() { | |
| int data = 2; | |
| switchOne(data); | |
| oneTick.detach(); | |
| } | |
| void ISRcamera() { | |
| batteryOff(); | |
| } | |
| //e220 state --when to wakeup to prevent crashing xor function | |
| // LoRa State Machine | |
| enum LoRaState { LORA_IDLE, | |
| LORA_BUSY, | |
| LORA_READY }; | |
| LoRaState currentState = LORA_IDLE; | |
| void updateState() { | |
| if (digitalRead(AUX_PIN) == LOW) currentState = LORA_BUSY; | |
| else currentState = LORA_READY; | |
| } | |
| void processTasks() { | |
| updateState(); | |
| if (currentState == LORA_READY && !taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| runTask(t); | |
| currentState = LORA_BUSY; | |
| } | |
| } | |
| void enterDeepSleep() { | |
| Serial.println("Preparing for deep sleep..."); | |
| Serial.flush(); | |
| delay(100); // Allow time for final serial output | |
| // Set E220 to WOR receiver mode | |
| e220ttl.setMode(MODE_2_WOR_RECEIVER); | |
| delay(50); | |
| // Hold E220 mode pins | |
| gpio_hold_en(GPIO_NUM_21); // M0 | |
| gpio_hold_en(GPIO_NUM_19); // M1 | |
| gpio_deep_sleep_hold_en(); | |
| // Set EXT0 wake on AUX pin (LOW) | |
| pinMode(GPIO_NUM_33, INPUT); // external pulldown/pullup required | |
| esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0); | |
| Serial.println("Entering deep sleep now..."); | |
| Serial.flush(); | |
| delay(50); | |
| esp_deep_sleep_start(); // 🔌💤 | |
| } | |
| void prepareForSleep() { | |
| detachInterrupt(AUX_PIN); | |
| esp_sleep_enable_ext0_wakeup((gpio_num_t)AUX_PIN, 0); | |
| enterDeepSleep(); | |
| } | |
| void setup() { | |
| Serial.begin(9600); | |
| while (!Serial) {}; | |
| Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); | |
| e220ttl.begin(); | |
| e220ttl.setMode(MODE_1_WOR_TRANSMITTER); | |
| wifi_Start(); | |
| pinMode(M0_PIN, OUTPUT); | |
| pinMode(M1_PIN, OUTPUT); | |
| pinMode(AUX_PIN, INPUT); | |
| pinMode(CONTROL_PIN, INPUT); | |
| //XOR e220 AUX Control | |
| int xorResult = switchWebFlag ^ cameraFlag; | |
| digitalWrite(CONTROL_PIN, xorResult); | |
| lastXorResult = xorResult; // Initialize latch variable | |
| attachInterrupt(GPIO_NUM_27, wakeUp, FALLING); | |
| Serial.println("\n\n\nWebserver and"); | |
| Serial.println("E220-900T30D Remote Switch\n"); | |
| configTime(0, 0, "pool.ntp.org", "time.nist.gov"); | |
| // See https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv for Timezone codes for your region | |
| setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3); // this sets TZ to Indianapolis, Indiana | |
| server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request) { | |
| request->send_P(200, PSTR("text/html"), HTML7, processor7); | |
| data = 1; | |
| }); | |
| oneTick.attach(1.0, ISRwatchdog); //watchdog ISR triggers every 1 second | |
| attachInterrupt(GPIO_NUM_27, wakeUp, FALLING); | |
| esp_sleep_wakeup_cause_t wakeup_reason; | |
| wakeup_reason = esp_sleep_get_wakeup_cause(); | |
| if (!ESP_SLEEP_WAKEUP_EXT0 == wakeup_reason) { | |
| Serial.println("Waked up from Power on, Reset or Other!"); | |
| enterDeepSleep(); | |
| } else { | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| delay(1000); | |
| Serial.println("Waked up from external GPIO!"); | |
| gpio_hold_dis(GPIO_NUM_21); | |
| gpio_hold_dis(GPIO_NUM_19); | |
| gpio_deep_sleep_hold_dis(); | |
| e220ttl.sendFixedMessage(0, DESTINATION_ADDL, 23, "We have waked up from message, but we can't read It!"); | |
| e220ttl.setMode(MODE_1_WOR_TRANSMITTER); //Lowest LoRa Power setting | |
| delay(1000); | |
| Serial.println(); | |
| Serial.println("Start sleep!"); | |
| delay(100); | |
| if (ESP_OK == gpio_hold_en(GPIO_NUM_21)) { | |
| Serial.println("HOLD 21"); | |
| } else { | |
| Serial.println("NO HOLD 21"); | |
| } | |
| if (ESP_OK == gpio_hold_en(GPIO_NUM_19)) { | |
| Serial.println("HOLD 19"); | |
| } else { | |
| Serial.println("NO HOLD 19"); | |
| } | |
| esp_sleep_enable_ext0_wakeup(GPIO_NUM_27, 0); | |
| gpio_deep_sleep_hold_en(); | |
| //Go to sleep now | |
| Serial.println("Going to sleep now"); | |
| enterDeepSleep(); | |
| delay(1); | |
| } | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| delay(1000); | |
| Serial.println("\n\nWake and start listening!"); | |
| } | |
| void loop() { | |
| if (switchWebFlag) { | |
| switchWebFlag = false; | |
| detachInterrupt(AUX_PIN); | |
| switchOne(data); | |
| needAnotherCountdown = 1; | |
| countdownTrigger(); | |
| } | |
| int xorResult = switchState ^ cameraFlag; | |
| if (xorResult != lastXorResult) { | |
| digitalWrite(CONTROL_PIN, xorResult); | |
| lastXorResult = xorResult; | |
| } | |
| if (xorWakeCondition(AUX_PIN, CONTROL_PIN)) { | |
| Serial.println("Wake condition met."); | |
| if (!taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| if (t.func) t.func(); // now valid | |
| } | |
| } | |
| Task t = taskQueue.front(); | |
| currentState = (digitalRead(AUX_PIN) == LOW) ? LORA_BUSY : LORA_READY; | |
| if (xorWakeCondition(AUX_PIN, CONTROL_PIN)) { | |
| Serial.println("Wake condition met."); | |
| if (!taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| } | |
| if (t.func) t.func(); | |
| } | |
| get_time(); | |
| DateTime currentDateTime = currentDateTime; | |
| //udp only send data when connected | |
| if (connected) { | |
| //Send a packet | |
| udp.beginPacket(udpAddress1, udpPort); | |
| udp.printf("Seconds since boot: %u", millis() / 1000); | |
| udp.endPacket(); | |
| } | |
| // If data available | |
| if (e220ttl.available() > 1) { | |
| // read the String message | |
| ResponseContainer rc = e220ttl.receiveMessage(); | |
| // Is something goes wrong print error | |
| if (rc.status.code != 1) { | |
| Serial.println(rc.status.getResponseDescription()); | |
| } else { | |
| // Print the data received | |
| Serial.println(rc.status.getResponseDescription()); | |
| Serial.println(rc.data); | |
| } | |
| } | |
| } | |
| String processor7(const String &var) { | |
| //index7.h | |
| if (var == F("LINK")) | |
| return linkAddress; | |
| return String(); | |
| } | |
| void configTime() { | |
| configTime(0, 0, udpAddress1, udpAddress2); | |
| setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3); // this sets TZ to Indianapolis, Indiana | |
| tzset(); | |
| //udp only send data when connected | |
| if (connected) { | |
| //Send a packet | |
| udp.beginPacket(udpAddress1, udpPort); | |
| udp.printf("Seconds since boot: %u", millis() / 1000); | |
| udp.endPacket(); | |
| } | |
| Serial.print("wait for first valid dateTime"); | |
| while (time(nullptr) < 100000ul) { | |
| Serial.print("."); | |
| delay(5000); | |
| } | |
| Serial.println("\nSystem Time set\n"); | |
| get_time(); | |
| Serial.println(outgoing.dateTime); | |
| } | |
| void countdownTrigger() { | |
| // Perform countdown actions here | |
| Serial.println("\nCountdown timer triggered!\n"); | |
| //getDateTime(); | |
| // Schedule the next countdown if needed | |
| if (needAnotherCountdown == 1) { | |
| onceTick.once(60, ISRcamera); | |
| data = 1; | |
| switchOne(data); | |
| needAnotherCountdown = 0; | |
| } | |
| } | |
| void webInterface() { | |
| String data = "http://192.123.12.27/relay"; | |
| if (WiFi.status() == WL_CONNECTED) { | |
| HTTPClient http; // Declare object of class HTTPClient | |
| http.begin(data); // Specify request destination | |
| // No need to add content-type header for a simple GET request | |
| int httpCode = http.GET(); // Send the GET request | |
| if (httpCode == HTTP_CODE_OK) { | |
| String payload = http.getString(); // Get the response payload | |
| Serial.print("HttpCode: "); | |
| Serial.print(httpCode); // Print HTTP return code | |
| Serial.println("\n"); | |
| //Serial.print(" Data echoed back from Hosted website: "); | |
| //Serial.println(payload); // Print payload response | |
| http.end(); // Close HTTPClient | |
| } else { | |
| Serial.print("HttpCode: "); | |
| Serial.print(httpCode); // Print HTTP return code | |
| Serial.println(" URL Request failed."); | |
| http.end(); // Close HTTPClient | |
| } | |
| } else { | |
| Serial.println("Error in WiFi connection"); | |
| } | |
| } | |
| void printParameters(struct Configuration configuration) { | |
| DEBUG_PRINTLN("----------------------------------------"); | |
| DEBUG_PRINT(F("HEAD : ")); | |
| DEBUG_PRINT(configuration.COMMAND, HEX); | |
| DEBUG_PRINT(" "); | |
| DEBUG_PRINT(configuration.STARTING_ADDRESS, HEX); | |
| DEBUG_PRINT(" "); | |
| DEBUG_PRINTLN(configuration.LENGHT, HEX); | |
| DEBUG_PRINTLN(F(" ")); | |
| DEBUG_PRINT(F("AddH : ")); | |
| DEBUG_PRINTLN(configuration.ADDH, HEX); | |
| DEBUG_PRINT(F("AddL : ")); | |
| DEBUG_PRINTLN(configuration.ADDL, HEX); | |
| DEBUG_PRINTLN(F(" ")); | |
| DEBUG_PRINT(F("Chan : ")); | |
| DEBUG_PRINT(configuration.CHAN, DEC); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.getChannelDescription()); | |
| DEBUG_PRINTLN(F(" ")); | |
| DEBUG_PRINT(F("SpeedParityBit : ")); | |
| DEBUG_PRINT(configuration.SPED.uartParity, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.SPED.getUARTParityDescription()); | |
| DEBUG_PRINT(F("SpeedUARTDatte : ")); | |
| DEBUG_PRINT(configuration.SPED.uartBaudRate, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.SPED.getUARTBaudRateDescription()); | |
| DEBUG_PRINT(F("SpeedAirDataRate : ")); | |
| DEBUG_PRINT(configuration.SPED.airDataRate, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.SPED.getAirDataRateDescription()); | |
| DEBUG_PRINTLN(F(" ")); | |
| DEBUG_PRINT(F("OptionSubPacketSett: ")); | |
| DEBUG_PRINT(configuration.OPTION.subPacketSetting, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.OPTION.getSubPacketSetting()); | |
| DEBUG_PRINT(F("OptionTranPower : ")); | |
| DEBUG_PRINT(configuration.OPTION.transmissionPower, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.OPTION.getTransmissionPowerDescription()); | |
| DEBUG_PRINT(F("OptionRSSIAmbientNo: ")); | |
| DEBUG_PRINT(configuration.OPTION.RSSIAmbientNoise, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.OPTION.getRSSIAmbientNoiseEnable()); | |
| DEBUG_PRINTLN(F(" ")); | |
| DEBUG_PRINT(F("TransModeWORPeriod : ")); | |
| DEBUG_PRINT(configuration.TRANSMISSION_MODE.WORPeriod, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getWORPeriodByParamsDescription()); | |
| DEBUG_PRINT(F("TransModeEnableLBT : ")); | |
| DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableLBT, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getLBTEnableByteDescription()); | |
| DEBUG_PRINT(F("TransModeEnableRSSI: ")); | |
| DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableRSSI, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getRSSIEnableByteDescription()); | |
| DEBUG_PRINT(F("TransModeFixedTrans: ")); | |
| DEBUG_PRINT(configuration.TRANSMISSION_MODE.fixedTransmission, BIN); | |
| DEBUG_PRINT(" -> "); | |
| DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getFixedTransmissionDescription()); | |
| DEBUG_PRINTLN("----------------------------------------"); | |
| } | |
| void wifi_Start() { | |
| // Static IP configuration | |
| IPAddress local_IP(192, 168, 12, 27); // your chosen IP | |
| IPAddress gateway(192, 168, 12, 1); // router gateway | |
| IPAddress subnet(255, 255, 255, 0); // subnet mask | |
| IPAddress primaryDNS(8, 8, 8, 8); // optional | |
| IPAddress secondaryDNS(8, 8, 4, 4); | |
| // Configure static IP BEFORE WiFi.begin() | |
| if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { | |
| //Serial.println("STA Failed to configure"); | |
| } | |
| WiFi.begin(ssid, password); | |
| Serial.print("Connecting"); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| delay(500); | |
| Serial.print("."); | |
| } | |
| Serial.println("\nConnected!"); | |
| Serial.print("IP address: "); | |
| Serial.println(WiFi.localIP()); | |
| server.begin(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //E220_Remote_Switch_Sender.ino | |
| //William Lucid in colboration with ChatGPT Updated 12/17/2025 @ 17:03 EST | |
| // Both skwtces compile with bo errors. Transmitter continues to trigger wakeup by ext 0 GPIO when no web request! | |
| //E220 Module is set to ADDL 3 | |
| //Fully connectd schema AUX connected to ESP32, GPIO18 | |
| //Ardino IDE: ESP32 Board Manager, Version 2.0.17 | |
| // See library downloads for each library license. | |
| // With FIXED SENDER configuration | |
| #include <Arduino.h> | |
| #include "WiFi.h" | |
| #include <WiFiUdp.h> | |
| #include <HTTPClient.h> | |
| #include <time.h> | |
| #include <Ticker.h> | |
| #include "LoRa_E220.h" | |
| #include <AsyncTCP.h> | |
| #include "ESPAsyncWebServer.h" | |
| #include "esp_sleep.h" | |
| #include <Wire.h> | |
| #include <queue> | |
| #include "Message.h" | |
| #include "XorWake.h" | |
| #include "stdio.h" | |
| #import "index7.h" //Video feed HTML; do not remove | |
| // Replace with your network details | |
| const char *ssid = "R2D2"; | |
| const char *password = "Sky7388500"; | |
| #define DESTINATION_ADDL 2 | |
| #define FREQENCY_915 | |
| #define CHANNEL 23 | |
| #define RXD2 16 | |
| #define TXD2 17 | |
| #define M0_PIN GPIO_NUM_21 | |
| #define M1_PIN GPIO_NUM_19 | |
| #define AUX_PIN GPIO_NUM_15 | |
| #define CONTROL_PIN 13 //RTC_IO wake capable for XORR function | |
| // ---------- esp32 pins ---------------- | |
| LoRa_E220 e220ttl(&Serial2, 15, 21, 19); // RX AUX M0 M1 | |
| // Struct to hold date and time components | |
| struct DateTime { | |
| int year; | |
| int month; | |
| int day; | |
| int hour; | |
| int minute; | |
| int second; | |
| }; | |
| // Define the maximum length for the timestamp | |
| const int MAX_dateTime_LENGTH = 30; | |
| int data = 0; | |
| int switchState; | |
| struct Messages { | |
| int switchState; | |
| char dateTime[MAX_dateTime_LENGTH]; // Array to hold date/time string | |
| }; | |
| Messages outgoing; | |
| void updateTimestamp() { | |
| String timestamp; | |
| timestamp.toCharArray(outgoing.dateTime, MAX_dateTime_LENGTH); | |
| } | |
| struct Task { | |
| int id; | |
| void (*func)(); | |
| }; | |
| std::queue<Task> taskQueue; | |
| void runTask(Task t); | |
| int delayTime = 100; //setmode delay duration | |
| bool sendWORFlag = false; | |
| char time_output[MAX_dateTime_LENGTH]; | |
| WiFiClient client; | |
| ///Are we currently connected? | |
| boolean connected = false; | |
| WiFiUDP udp; | |
| // local port to listen for UDP packets | |
| const int udpPort = 1337; | |
| char incomingPacket[255]; | |
| char replyPacket[] = "Hi there! Got the message :-)"; | |
| //NTP Time Servers | |
| const char *udpAddress1 = "pool.ntp.org"; | |
| const char *udpAddress2 = "time.nist.gov"; | |
| #define TZ "EST+5EDT,M3.2.0/2,M11.1.0/2" | |
| AsyncWebServer server(80); | |
| Ticker oneTick; | |
| Ticker onceTick; | |
| Ticker sendDelay; | |
| String linkAddress = "192.123.12.27:80"; | |
| portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; | |
| volatile int watchdogCounter; | |
| int totalwatchdogCounter; | |
| int cameraPowerOff = 0; | |
| int watchDog; | |
| void ISRwatchdog() { | |
| portENTER_CRITICAL_ISR(&mux); | |
| //watchdogCounter++; | |
| if (watchdogCounter >= 5000) { | |
| watchDog = 1; | |
| } | |
| portEXIT_CRITICAL_ISR(&mux); | |
| } | |
| int cameraFlag; | |
| int needAnotherCountdown = 0; | |
| void batteryOff() { | |
| int data = 2; | |
| switchOne(data); | |
| oneTick.detach(); | |
| } | |
| void ISRcamera() { | |
| batteryOff(); | |
| } | |
| volatile bool switchWebFlag = false; | |
| void IRAM_ATTR wakeUp() { | |
| // Do not use Serial on interrupt callback | |
| switchWebFlag = true; | |
| } | |
| void sendWOR() { | |
| uint16_t duration_ms = 2600; | |
| e220ttl.setMode(MODE_1_WOR_TRANSMITTER); | |
| uint8_t preamble[4] = { 0xAA, 0xAA, 0xAA, 0xAA }; | |
| uint32_t start = millis(); | |
| while ((millis() - start) < duration_ms) { | |
| ResponseStatus rs = e220ttl.sendFixedMessage(0, 0x02, CHANNEL, preamble, sizeof(preamble)); | |
| while (digitalRead(AUX_PIN) == LOW) { delay(1); } // pace by AUX | |
| // Send actual message | |
| rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &outgoing, sizeof(Message)); | |
| Serial.println(rs.getResponseDescription()); | |
| // Return to normal mode | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| switchWebFlag = true; | |
| } | |
| } | |
| int switchOne(int data) { | |
| if (data == 1) { | |
| data = 1; | |
| Serial.println("\nESP32 waking from Deep Sleep"); | |
| Serial.println("Battery Switch is ON\n"); | |
| } | |
| if (data == 2) { | |
| data = 2; | |
| Serial.println("\nBattery power switched OFF"); | |
| Serial.println("ESP32 going to Deep Sleep\n"); | |
| } | |
| // 1. Prepare Message | |
| memset(&outgoing, 0, sizeof(Message)); | |
| outgoing.switchState = data; | |
| strncpy(outgoing.dateTime, time_output, MAX_dateTime_LENGTH); | |
| Serial.print("Sending WOR message for switchState: "); | |
| Serial.println(outgoing.switchState); | |
| Serial.println(outgoing.dateTime); | |
| sendWOR(); | |
| /* | |
| ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, &outgoing, sizeof(Message)); | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| Serial.println(rs.getResponseDescription()); | |
| //delay(1000); | |
| // 4. Wait for Transmission to Complete | |
| // Use the AUX pin to check for completion, or just delay | |
| // (Preamble time + Message time + buffer time). | |
| // Since preamble is 2000ms, wait a little longer than that. | |
| // 2500ms is a safe minimum. | |
| */ | |
| return data; | |
| } | |
| //e220 state --when to wakeup to prevent crashing xor function | |
| // LoRa State Machine | |
| enum LoRaState { LORA_IDLE, | |
| LORA_BUSY, | |
| LORA_READY }; | |
| LoRaState currentState = LORA_IDLE; | |
| void updateState() { | |
| if (digitalRead(AUX_PIN) == LOW) currentState = LORA_BUSY; | |
| else currentState = LORA_READY; | |
| } | |
| void processTasks() { | |
| updateState(); | |
| if (currentState == LORA_READY && !taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| runTask(t); | |
| currentState = LORA_BUSY; | |
| } | |
| } | |
| #include "driver/rtc_io.h" | |
| void enterDeepSleep() { | |
| Serial.println("Preparing for deep sleep..."); | |
| // 1. Flush and Close Serial to prevent UART glitches | |
| Serial2.flush(); | |
| delay(50); | |
| Serial2.end(); | |
| // 2. Set E220 Mode Pins (M0=0, M1=1 for WOR Transmitter) | |
| digitalWrite(GPIO_NUM_21, LOW); // M0 | |
| digitalWrite(GPIO_NUM_19, HIGH); // M1 | |
| // 3. SET CONTROL PIN LOW (To mismatch with AUX=1) | |
| pinMode(GPIO_NUM_13, OUTPUT); | |
| digitalWrite(GPIO_NUM_13, LOW); | |
| // 4. LOCK PINS IN RTC DOMAIN | |
| // This is more robust than standard gpio_hold for deep sleep | |
| rtc_gpio_hold_en(GPIO_NUM_21); | |
| rtc_gpio_hold_en(GPIO_NUM_19); | |
| rtc_gpio_hold_en(GPIO_NUM_13); | |
| // 5. Final check of the XOR output before diving in | |
| // If it's already 0, something is wrong (AUX is low or logic is flipped) | |
| if (digitalRead(GPIO_NUM_33) == 0) { | |
| Serial.println("Warning: XOR output is LOW. Sleep will trigger immediately!"); | |
| } | |
| // 6. Set EXT0 wake on LOW (0) | |
| esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0); | |
| Serial.println("Entering deep sleep..."); | |
| Serial.flush(); | |
| esp_deep_sleep_start(); | |
| } | |
| void prepareForSleep() { | |
| detachInterrupt(AUX_PIN); | |
| esp_sleep_enable_ext0_wakeup((gpio_num_t)AUX_PIN, 0); | |
| enterDeepSleep(); | |
| } | |
| void setup() { | |
| Serial.begin(9600); | |
| while (!Serial) {}; | |
| Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); | |
| e220ttl.begin(); | |
| ResponseStructContainer c = e220ttl.getConfiguration(); | |
| Configuration configuration = *(Configuration *)c.data; | |
| Serial.println("\n=== Current Configuration ==="); | |
| Serial.print("ADDH: 0x"); | |
| Serial.println(configuration.ADDH, HEX); | |
| Serial.print("ADDL: 0x"); | |
| Serial.println(configuration.ADDL, HEX); | |
| Serial.print("Channel: "); | |
| Serial.println(configuration.CHAN); | |
| Serial.print("WOR Period: "); | |
| Serial.println(configuration.TRANSMISSION_MODE.WORPeriod, BIN); | |
| Serial.print("Fixed Trans: "); | |
| Serial.println(configuration.TRANSMISSION_MODE.fixedTransmission, BIN); | |
| c.close(); | |
| e220ttl.setMode(MODE_1_WOR_TRANSMITTER); | |
| wifi_Start(); | |
| pinMode(M0_PIN, OUTPUT); | |
| pinMode(M1_PIN, OUTPUT); | |
| pinMode(AUX_PIN, INPUT); | |
| attachInterrupt(GPIO_NUM_15, wakeUp, FALLING); | |
| Serial.println("\n\n\nWebserver and"); | |
| Serial.println("E220-900T30D Remote Switch\n"); | |
| configTime(0, 0, "pool.ntp.org", "time.nist.gov"); | |
| // See https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv for Timezone codes for your region | |
| setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3); // this sets TZ to Indianapolis, Indiana | |
| server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request) { | |
| request->send_P(200, PSTR("text/html"), HTML7, processor7); | |
| }); | |
| oneTick.attach(1.0, ISRwatchdog); //watchdog ISR triggers every 1 second | |
| attachInterrupt(GPIO_NUM_15, wakeUp, FALLING); | |
| esp_sleep_wakeup_cause_t wakeup_reason; | |
| wakeup_reason = esp_sleep_get_wakeup_cause(); | |
| if (!ESP_SLEEP_WAKEUP_EXT0 == wakeup_reason) { | |
| Serial.println("Waked up from Power on, Reset or Other!"); | |
| enterDeepSleep(); | |
| } else { | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| delay(1000); | |
| Serial.println("Waked up from external GPIO!"); | |
| gpio_hold_dis(GPIO_NUM_21); | |
| gpio_hold_dis(GPIO_NUM_19); | |
| gpio_deep_sleep_hold_dis(); | |
| e220ttl.sendFixedMessage(0, DESTINATION_ADDL, 23, "We have waked up from message, but we can't read It!"); | |
| e220ttl.setMode(MODE_3_SLEEP); //Lowest LoRa Power setting | |
| delay(1000); | |
| Serial.println(); | |
| Serial.println("Start sleep!"); | |
| delay(100); | |
| if (ESP_OK == gpio_hold_en(GPIO_NUM_21)) { | |
| Serial.println("HOLD 21"); | |
| } else { | |
| Serial.println("NO HOLD 21"); | |
| } | |
| if (ESP_OK == gpio_hold_en(GPIO_NUM_19)) { | |
| Serial.println("HOLD 19"); | |
| } else { | |
| Serial.println("NO HOLD 19"); | |
| } | |
| esp_sleep_enable_ext0_wakeup(GPIO_NUM_15, 0); | |
| gpio_deep_sleep_hold_en(); | |
| //Go to sleep now | |
| Serial.println("Going to sleep now"); | |
| enterDeepSleep(); | |
| delay(1); | |
| } | |
| e220ttl.setMode(MODE_0_NORMAL); | |
| delay(1000); | |
| Serial.println("\n\nWake and start listening!"); | |
| } | |
| void loop() { | |
| if (switchWebFlag) { | |
| switchWebFlag = false; | |
| detachInterrupt(AUX_PIN); | |
| data = 1; | |
| needAnotherCountdown = 1; | |
| countdownTrigger(); | |
| switchOne(data); //Remote switch is on or off | |
| } | |
| if (xorWakeCondition(AUX_PIN, CONTROL_PIN)) { | |
| Serial.println("Wake condition met."); | |
| if (!taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| if (t.func) t.func(); // now valid | |
| } | |
| } | |
| Task t = taskQueue.front(); | |
| currentState = (digitalRead(AUX_PIN) == LOW) ? LORA_BUSY : LORA_READY; | |
| if (xorWakeCondition(AUX_PIN, CONTROL_PIN)) { | |
| Serial.println("Wake condition met."); | |
| if (!taskQueue.empty()) { | |
| Task t = taskQueue.front(); | |
| taskQueue.pop(); | |
| } | |
| if (t.func) t.func(); | |
| } | |
| get_time(); | |
| DateTime currentDateTime = currentDateTime; | |
| //udp only send data when connected | |
| if (connected) { | |
| //Send a packet | |
| udp.beginPacket(udpAddress1, udpPort); | |
| udp.printf("Seconds since boot: %u", millis() / 1000); | |
| udp.endPacket(); | |
| } | |
| // If data available | |
| if (e220ttl.available() > 1) { | |
| // read the String message | |
| ResponseContainer rc = e220ttl.receiveMessage(); | |
| // Is something goes wrong print error | |
| if (rc.status.code != 1) { | |
| Serial.println(rc.status.getResponseDescription()); | |
| } else { | |
| // Print the data received | |
| Serial.println(rc.status.getResponseDescription()); | |
| Serial.println(rc.data); | |
| } | |
| } | |
| } | |
| String processor7(const String &var) { | |
| //index7.h | |
| if (var == F("LINK")) | |
| return linkAddress; | |
| return String(); | |
| } | |
| void configTime() { | |
| configTime(0, 0, udpAddress1, udpAddress2); | |
| setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3); // this sets TZ to Indianapolis, Indiana | |
| tzset(); | |
| //udp only send data when connected | |
| if (connected) { | |
| //Send a packet | |
| udp.beginPacket(udpAddress1, udpPort); | |
| udp.printf("Seconds since boot: %u", millis() / 1000); | |
| udp.endPacket(); | |
| } | |
| Serial.print("wait for first valid dateTime"); | |
| while (time(nullptr) < 100000ul) { | |
| Serial.print("."); | |
| delay(5000); | |
| } | |
| Serial.println("\nSystem Time set\n"); | |
| get_time(); | |
| Serial.println(outgoing.dateTime); | |
| } | |
| void countdownTrigger() { | |
| // Perform countdown actions here | |
| Serial.println("\nCountdown timer triggered!\n"); | |
| //getDateTime(); | |
| // Schedule the next countdown if needed | |
| if (needAnotherCountdown == 1) { | |
| onceTick.once(60, ISRcamera); | |
| data = 1; | |
| switchOne(data); | |
| needAnotherCountdown = 0; | |
| } | |
| } | |
| // Function to get current date and time | |
| DateTime getCurrentDateTime() { | |
| DateTime currentDateTime; | |
| time_t now = time(nullptr); | |
| struct tm *ti = localtime(&now); | |
| // Extract individual components | |
| currentDateTime.year = ti->tm_year + 1900; | |
| currentDateTime.month = ti->tm_mon + 1; | |
| currentDateTime.day = ti->tm_mday; | |
| currentDateTime.hour = ti->tm_hour; | |
| currentDateTime.minute = ti->tm_min; | |
| currentDateTime.second = ti->tm_sec; | |
| return currentDateTime; | |
| } | |
| // Function to get the dateTime | |
| String get_time() { | |
| time_t now; | |
| time(&now); | |
| strftime(time_output, MAX_dateTime_LENGTH, "%a %m/%d/%y %T", localtime(&now)); | |
| return String(time_output); // returns dateTime in the specified format | |
| } | |
| void webInterface() { | |
| String data = "http://192.123.12.27/relay"; | |
| if (WiFi.status() == WL_CONNECTED) { | |
| HTTPClient http; // Declare object of class HTTPClient | |
| http.begin(data); // Specify request destination | |
| // No need to add content-type header for a simple GET request | |
| int httpCode = http.GET(); // Send the GET request | |
| if (httpCode == HTTP_CODE_OK) { | |
| String payload = http.getString(); // Get the response payload | |
| Serial.print("HttpCode: "); | |
| Serial.print(httpCode); // Print HTTP return code | |
| Serial.println("\n"); | |
| //Serial.print(" Data echoed back from Hosted website: "); | |
| //Serial.println(payload); // Print payload response | |
| http.end(); // Close HTTPClient | |
| } else { | |
| Serial.print("HttpCode: "); | |
| Serial.print(httpCode); // Print HTTP return code | |
| Serial.println(" URL Request failed."); | |
| http.end(); // Close HTTPClient | |
| } | |
| } else { | |
| Serial.println("Error in WiFi connection"); | |
| } | |
| } | |
| void wifi_Start() { | |
| // Static IP configuration | |
| IPAddress local_IP(192, 168, 12, 27); // your chosen IP | |
| IPAddress gateway(192, 168, 12, 1); // router gateway | |
| IPAddress subnet(255, 255, 255, 0); // subnet mask | |
| IPAddress primaryDNS(8, 8, 8, 8); // optional | |
| IPAddress secondaryDNS(8, 8, 4, 4); | |
| // Configure static IP BEFORE WiFi.begin() | |
| if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { | |
| //Serial.println("STA Failed to configure"); | |
| } | |
| WiFi.begin(ssid, password); | |
| Serial.print("Connecting"); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| delay(500); | |
| Serial.print("."); | |
| } | |
| Serial.println("\nConnected!"); | |
| Serial.print("IP address: "); | |
| Serial.println(WiFi.localIP()); | |
| server.begin(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment