Skip to content

Instantly share code, notes, and snippets.

@Tech500
Last active January 2, 2026 16:43
Show Gist options
  • Select an option

  • Save Tech500/fc7d84c687260065bf84061352be4d1d to your computer and use it in GitHub Desktop.

Select an option

Save Tech500/fc7d84c687260065bf84061352be4d1d to your computer and use it in GitHub Desktop.
E220 LoRa Transmitter and WOR Receiver Remote Load Controller. Utilizing ESP32 dual cores. ESP32 Receive uses deep sleep conserving battery power.
/*
* "E220_WOR_Remote_Switch_Receiver_XII_DC1.ino"
* 01/02/2026 @ 1115 EST Arduino IDE, Board Manager 3.3.5 ESP32 DevKit v1 used for Transmitter and Receiver. *
*
* Dual-core WOR receiver for KY002S MOSFET latch
* Sender protocol:
* switchState = 1 -> turn load ON
* switchState = 2 -> turn load OFF
*
* Hardware:
* ESP32
* E220-900/915-TTL-xxx
* M0 -> GPIO 21
* M1 -> GPIO 19
* AUX -> GPIO 15 (also EXT0 wake source)
* RX2 -> GPIO 16
* TX2 -> GPIO 17
*
* KY002S MOSFET latch
* Trigger (control) -> GPIO 32 (ESP32 output)
* State output -> GPIO 33 (ESP32 input, HIGH = ON, LOW = OFF)
*/
#include <Arduino.h>
#include "LoRa_E220.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
// ---------------------- E220 pins and config ----------------------
#define RXD2 16
#define TXD2 17
#define M0_PIN GPIO_NUM_21
#define M1_PIN GPIO_NUM_19
#define AUX_PIN GPIO_NUM_15 // also EXT0 wake pin
LoRa_E220 e220ttl(&Serial2, AUX_PIN, M0_PIN, M1_PIN);
// ---------------------- KY002S pins -------------------------------
const uint8_t TRIGGER_PIN = 32; // ESP32 -> KY002S Trigger
const uint8_t STATE_PIN = 33; // KY002S -> ESP32 state (Vo)
const uint16_t PULSE_MS = 200; // HIGH duration before falling edge
// ---------------------- WOR / protocol ----------------------------
const int MAX_dateTime_LENGTH = 40;
struct Message {
int switchState; // 1 = ON, 2 = OFF
char dateTime[MAX_dateTime_LENGTH]; // timestamp string from sender
};
volatile bool messageReady = false; // Core0 -> Core1 flag
Message incoming; // shared struct
// ---------------------- Pulse state (Core1) -----------------------
bool pulseActive = false;
uint32_t pulseStartMs = 0;
// ---------------------- Boot diagnostics -------------------------
RTC_DATA_ATTR int bootCount = 0;
// Forward declarations
void enterDeepSleep();
void core0Task(void* parameter);
void core1Task(void* parameter);
// ================================================================
// Utility: read actual KY002S state from STATE_PIN
// ================================================================
bool readLatchState() {
int level = digitalRead(STATE_PIN);
return (level == HIGH); // HIGH = load ON, LOW = load OFF (assumed)
}
// ================================================================
// Utility: start non-blocking KY002S trigger pulse (HIGH -> LOW)
// ================================================================
void startTriggerPulse() {
if (pulseActive) return; // avoid overlapping pulses
// Start pulse: drive HIGH
digitalWrite(TRIGGER_PIN, HIGH);
pulseStartMs = millis();
pulseActive = true;
}
// ================================================================
// Utility: handle ongoing pulse (call often in Core1 loop)
// ================================================================
void handleTriggerPulse() {
if (!pulseActive) return;
if (millis() - pulseStartMs >= PULSE_MS) {
// End pulse: drive LOW → falling edge toggles KY002S
digitalWrite(TRIGGER_PIN, LOW);
pulseActive = false;
}
}
// ================================================================
// Enter deep sleep with E220 held in WOR receiver mode
// ================================================================
void enterDeepSleep() {
Serial.println();
Serial.println("Preparing for deep sleep...");
Serial.flush();
// Put E220 into WOR receiver mode before sleep
e220ttl.setMode(MODE_2_WOR_RECEIVER);
delay(50);
// Hold M0/M1 levels across deep sleep so E220 stays in WOR receiver
gpio_hold_en(M0_PIN);
gpio_hold_en(M1_PIN);
gpio_deep_sleep_hold_en();
// Configure EXT0 wake on AUX pin (falling edge, logic 0)
esp_sleep_enable_ext0_wakeup(AUX_PIN, 0);
Serial.println("Entering deep sleep now...");
Serial.flush();
// Stop Serial2 cleanly
Serial2.end();
delay(20);
esp_deep_sleep_start();
}
// ================================================================
// Core 0: COMM core - E220 WOR receive
// ================================================================
void core0Task(void* parameter) {
for (;;) {
// Check if the E220 has a complete Message available
if (e220ttl.available() > 0) {
ResponseStructContainer rsc =
e220ttl.receiveMessageRSSI(sizeof(Message));
// Copy into global 'incoming' struct
Message* msgPtr = (Message*) rsc.data;
// Small, bounded copy - safe for volatile handoff
incoming.switchState = msgPtr->switchState;
strncpy(incoming.dateTime, msgPtr->dateTime, MAX_dateTime_LENGTH);
incoming.dateTime[MAX_dateTime_LENGTH - 1] = '\0';
rsc.close();
// Raise flag for Core1
messageReady = true;
}
// Yield to other tasks, no blocking
taskYIELD();
}
}
// ================================================================
// Core 1: LOGIC core - KY002S + logging + deep sleep
// ================================================================
void core1Task(void* parameter) {
for (;;) {
// Check if a new message is available
if (messageReady) {
messageReady = false;
// Make a local copy to avoid races
Message msg = incoming;
Serial.println();
Serial.println("WOR Message Received");
Serial.print(" switchState: ");
Serial.println(msg.switchState);
Serial.print(" timestamp : ");
Serial.println(msg.dateTime);
// Read actual hardware state before acting
bool actualState = readLatchState();
Serial.print(" KY002S state before: ");
Serial.println(actualState ? "ON" : "OFF");
bool wantOn = (msg.switchState == 1);
bool wantOff = (msg.switchState == 2);
// Decide whether we need to toggle KY002S
if (wantOn && !actualState) {
Serial.println(" Command: TURN ON (pulse needed)");
startTriggerPulse();
} else if (wantOff && actualState) {
Serial.println(" Command: TURN OFF (pulse needed)");
startTriggerPulse();
} else {
Serial.println(" Command matches current state, no pulse.");
}
// Wait (non-blocking) for pulse to complete if one was started
uint32_t pulseWaitStart = millis();
while (pulseActive && (millis() - pulseWaitStart < (PULSE_MS + 200))) {
handleTriggerPulse();
delay(5); // gentle pacing, not strict timing critical
}
// Final state read
bool newState = readLatchState();
Serial.print(" KY002S state after : ");
Serial.println(newState ? "ON" : "OFF");
if (newState != actualState) {
Serial.println(" Result: Latch toggled successfully.");
} else {
Serial.println(" Warning: Latch state did NOT change.");
}
// After handling command, go back to deep sleep
enterDeepSleep();
}
// Keep pulse logic alive even if message arrives during pulse
handleTriggerPulse();
// Yield a bit
taskYIELD();
}
}
// ================================================================
// Setup
// ================================================================
void setup() {
Serial.begin(115200);
while (!Serial) { }
++bootCount;
Serial.println();
Serial.println("E220 WOR Remote Switch Receiver (Clean Dual-Core)");
Serial.print("Boot number: ");
Serial.println(bootCount);
// --- Pin setup ---
pinMode(M0_PIN, OUTPUT);
pinMode(M1_PIN, OUTPUT);
pinMode(AUX_PIN, INPUT);
pinMode(TRIGGER_PIN, OUTPUT);
digitalWrite(TRIGGER_PIN, LOW); // idle LOW for KY002S
pinMode(STATE_PIN, INPUT);
// --- Serial2 for E220 ---
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
// --- E220 init ---
e220ttl.begin();
// Normal mode first (for any config if needed), then WOR receiver
e220ttl.setMode(MODE_0_NORMAL);
delay(50);
e220ttl.setMode(MODE_2_WOR_RECEIVER);
delay(50);
// Disable any previous deep sleep holds
gpio_hold_dis(M0_PIN);
gpio_hold_dis(M1_PIN);
gpio_deep_sleep_hold_dis();
// Configure EXT0 wake on AUX pin
esp_sleep_enable_ext0_wakeup(AUX_PIN, 0);
// Initial KY002S state report
bool initialState = readLatchState();
Serial.print("Initial KY002S state: ");
Serial.println(initialState ? "ON" : "OFF");
// --- Create tasks on both cores ---
// Core 0: COMM (E220)
xTaskCreatePinnedToCore(
core0Task,
"Core0Comm",
4096,
nullptr,
2,
nullptr,
0
);
// Core 1: LOGIC (KY002S + sleep)
xTaskCreatePinnedToCore(
core1Task,
"Core1Logic",
4096,
nullptr,
1,
nullptr,
1
);
}
void loop() {
// Not used. All work is done in the two tasks.
}
/*
*
* "E220_WOR_Remote_Switch_Transmitter_XII_DC1.ino"
* 01/02/2026 @ 1115 EST Arduino IDE, Board Manager 3.3.5 ESP32 DevKit v1 used for Transmitter and Receiver.
* *
* Based on xReef's E220 Library:
* examples/06_wakeUPLoRaFromWOR/06_wakeUPLoRaFromWOR.ino
*
* William Lucid and Team, AI: Claude, Copilot, and Gemini
*
* Microsoft Copilot's Dual-core refactor:
* ---------------------------------------
* - WiFi + web server on Core 0
* - WOR / radio logic on Core 1
* - AUX interrupt (FALLING) -> task notification (no polling)
* - switchFlag consumed only in switchOne()
* - sendPreamble() and sendOutgoing() run unconditionally
* - No blocking loops on Core 0 (WiFi stays healthy)
*
* EBYTE LoRa E220
* Stay in sleep mode and wait a wake up WOR message
*
* You must configure the address with 0 2 23 (FIXED RECEIVER configuration)
* and pay attention that WOR period must be the same of sender.
*
* E220 ----- WeMos D1 mini ----- esp32 ----- Arduino Nano 33 IoT
* M0 ----- D7 (or GND) ----- 19 (or GND)
* M1 ----- D6 (or 3.3v) ----- 21 (or 3.3v)
* TX ----- D3 (PullUP) ----- TX2 (PullUP)
* RX ----- D4 (PullUP) ----- RX2 (PullUP)
* AUX ----- D5 (PullUP) ----- 15 (PullUP)
* VCC ----- 3.3v/5v ----- 3.3v/5v
* GND ----- GND ----- GND
*/
// With FIXED SENDER configuration
#define DESTINATION_ADDL 3
#define FREQUENCY_915
#define CHANNEL 23
#include "Arduino.h"
#include "LoRa_E220.h"
#include "WiFi.h"
#include <WiFiUdp.h>
#include <HTTPClient.h>
#include <time.h>
#include <Ticker.h>
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc.h"
#include "driver/rtc_io.h"
#include "index7.h"
// ---------------------------
// E220 wiring: RX AUX M0 M1
// ---------------------------
LoRa_E220 e220ttl(&Serial2, 15, 21, 19);
#define RXD2 16
#define TXD2 17
#define M0_PIN GPIO_NUM_21
#define M1_PIN GPIO_NUM_19
#define AUX_PIN GPIO_NUM_15 // GPIO_NUM_15 is used for AUX and deep-sleep wake
#define TRIGGER 32 // KY002S MOSFET Bi-Stable Switch (drive)
#define KY002S_PIN 33 // KY002S MOSFET Bi-Stable Switch (sense)
#define ALERT 4 // ina226 Battery Monitor (input)
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR int activations = 0;
// ---------------------------
// Network / NTP
// ---------------------------
const char *ssid = "R2D2";
const char *password = "Sky7388500";
WiFiClient client;
boolean connected = false;
AsyncWebServer server(80);
// Used by processor7() for index7.h
String linkAddress = "192.168.12.27:80";
WiFiUDP udp;
const int udpPort = 1157;
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" // Time zone set to Indianapolis
int delayTime = 2000; // setMode delay duration
int pulseDuration = 100;
int data;
// 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 dateTime
const int MAX_dateTime_LENGTH = 40;
char time_output[MAX_dateTime_LENGTH];
int switchState;
struct Message {
int switchState;
char dateTime[MAX_dateTime_LENGTH]; // Array to hold date/time string
};
Message outgoing;
// Tickers and timing
Ticker oneTick;
Ticker onceTick;
int cameraPowerOff = 0;
// 1. Volatile flags for ISR contexts
volatile bool interruptExecuted = false;
volatile bool countdownExpired = false;
// 2. Countdown management
int needAnotherCountdown = 0;
// ---------------------------
// Dual-core WOR / task state
// ---------------------------
// WOR task handle
TaskHandle_t worTaskHandle = NULL;
// Cross-core event flags
volatile bool switchFlag = false; // set true when a WOR send is requested
volatile int switchData = 0; // payload/state indicator for switchOne
// Critical section mutex
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
// ---------------------------
// Forward declarations
// ---------------------------
void wifi_Start();
void configTimeCustom();
String get_time();
DateTime getCurrentDateTime();
void webInterface();
void enterDeepSleep();
// ---------------------------
// ISR: countdown expiry
// ---------------------------
void IRAM_ATTR countdownTrigger() {
countdownExpired = true;
}
// ---------------------------
// ISR: generic wake flag (legacy)
// ---------------------------
void IRAM_ATTR wakeUp() {
interruptExecuted = true;
}
// ---------------------------
// AUX ISR: FALLING edge (HIGH -> LOW)
// Used as "module idle / TX complete" notifier
// ---------------------------
void IRAM_ATTR auxISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (worTaskHandle != NULL) {
vTaskNotifyGiveFromISR(worTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// ---------------------------
// Utility: updateTimestamp into time_output
// ---------------------------
void updateTimestamp() {
time_t now;
time(&now);
strftime(time_output, MAX_dateTime_LENGTH, "%a %m/%d/%y %T", localtime(&now));
}
// ---------------------------
// Optional: print reset reason
// ---------------------------
void printResetReason() {
esp_reset_reason_t reason = esp_reset_reason();
switch (reason) {
case ESP_RST_POWERON: Serial.println("Reset due to power-on"); break;
case ESP_RST_SW: Serial.println("Software reset"); break;
case ESP_RST_PANIC: Serial.println("Software reset due to panic"); break;
case ESP_RST_INT_WDT: Serial.println("Interrupt watchdog reset"); break;
case ESP_RST_TASK_WDT: Serial.println("Task watchdog reset"); break;
case ESP_RST_WDT: Serial.println("Other watchdog reset"); break;
case ESP_RST_DEEPSLEEP: Serial.println("Reset after deep sleep"); break;
case ESP_RST_BROWNOUT: Serial.println("Brownout reset"); break;
case ESP_RST_SDIO: Serial.println("SDIO reset"); break;
default: Serial.println("Unknown reset reason"); break;
}
}
// ---------------------------
// Deep sleep entry
// ---------------------------
void enterDeepSleep() {
Serial.println("Preparing for deep sleep...");
Serial.println("Waiting for web request");
Serial.flush();
Serial2.end();
delay(100); // Allow time for final serial output
// Hold E220 mode pins
gpio_hold_en(GPIO_NUM_19); // M0
gpio_hold_en(GPIO_NUM_21); // M1
gpio_deep_sleep_hold_en();
// External wakeup on AUX pin (LOW)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_15, 0);
Serial.flush();
Serial2.end();
delay(50);
esp_deep_sleep_start();
}
// ---------------------------
// WOR: wait for AUX via task notification (no polling)
// ---------------------------
void waitForAux() {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
// ---------------------------
// WOR: send preamble (no switchFlag logic)
// ---------------------------
void sendPreamble() {
waitForAux(); // ensure module idle
e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
vTaskDelay(pdMS_TO_TICKS(delayTime));
ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL,
"Hello, world? WOR!");
Serial.println(rs.getResponseDescription());
waitForAux(); // TX complete
vTaskDelay(pdMS_TO_TICKS(100));
}
// ---------------------------
// WOR: send outgoing structured payload (no switchFlag logic)
// ---------------------------
int sendOutgoing(int data) {
waitForAux(); // ensure module idle
e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
// Prepare payload
memset(&outgoing, 0, sizeof(Message));
outgoing.switchState = data;
updateTimestamp();
strncpy(outgoing.dateTime, time_output, MAX_dateTime_LENGTH);
Serial.print("Sending outgoing message for switchState: ");
Serial.println(outgoing.switchState);
Serial.println(outgoing.dateTime);
// Send payload
ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL,
&outgoing, sizeof(Message));
Serial.println(rs.getResponseDescription());
Serial.println("Sending outgoing Payload");
return 0; // event consumed
}
// ---------------------------
// Dispatcher: the ONLY consumer of switchFlag
// ---------------------------
int switchOne(int data) {
Serial.print("Value of data: ");
Serial.println(data);
// Prevent duplicate WOR sends
if (!switchFlag) {
Serial.println("WOR send blocked: switchFlag is false");
return 0;
}
// Consume the event ONCE
switchFlag = false;
if (data == 1) {
Serial.println("Echoing Receiver messaging");
Serial.println("\nESP32 waking from Deep Sleep");
Serial.println("Battery Switch is ON\n");
}
if (data == 2) {
Serial.println("Echoing Receiver messaging");
Serial.println("\nBattery power switched OFF");
Serial.println("ESP32 going to Deep Sleep\n");
}
Serial.println("Gets to switchOne sendOutgoing(data)");
sendPreamble();
return sendOutgoing(data);
}
// ---------------------------
// Template processor for index7.h
// ---------------------------
String processor7(const String &var) {
// index7.h placeholders
if (var == F("LINK"))
return linkAddress;
return String();
}
// ---------------------------
// E220 configuration printer (from library)
// ---------------------------
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("----------------------------------------");
}
// ---------------------------
// WOR Task on Core 1
// ---------------------------
void WORTask(void *pvParameters) {
Serial.print("WORTask running on core ");
Serial.println(xPortGetCoreID());
for (;;) {
// Sleep until AUX or external event notifies us
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
bool localFlag;
int localData;
// Safely copy shared state
portENTER_CRITICAL(&mux);
localFlag = switchFlag;
localData = switchData;
portEXIT_CRITICAL(&mux);
if (localFlag) {
switchOne(localData);
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
// ---------------------------
// Event trigger: called from web or countdown
// ---------------------------
void triggerSwitchEvent(int value) {
portENTER_CRITICAL(&mux);
switchData = value;
switchFlag = true;
portEXIT_CRITICAL(&mux);
if (worTaskHandle != NULL) {
xTaskNotifyGive(worTaskHandle); // wake WOR task
}
}
// ---------------------------
// WiFi setup with static IP
// ---------------------------
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());
connected = true;
server.begin();
}
// ---------------------------
// Setup
// ---------------------------
void setup() {
Serial.begin(9600);
while (!Serial) {};
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // TX = 17, RX = 16
while (!Serial) {};
Serial.println("\n\nE220 WOR Remote Switch Transmitter\n");
Serial.println("Wake up from External GPIO...");
// Increment boot number and print it every reboot
++bootCount;
Serial.println("Boot number: " + String(bootCount));
pinMode(M0_PIN, OUTPUT);
pinMode(M1_PIN, OUTPUT);
pinMode(AUX_PIN, INPUT);
pinMode(TRIGGER, OUTPUT); // Drives power for switch (KY002S)
pinMode(KY002S_PIN, INPUT); // Reads Trigger status (KY002S)
pinMode(ALERT, INPUT); // ESP32, GPIO4
// Legacy wake ISR (not used in new WOR logic, but kept for completeness)
attachInterrupt(GPIO_NUM_15, wakeUp, FALLING);
// AUX ISR for radio idle/TX-complete (dual-core WOR logic)
attachInterrupt(digitalPinToInterrupt(AUX_PIN), auxISR, FALLING);
int value = digitalRead(KY002S_PIN); // KY002S, Vo pin
if (value == HIGH) {
digitalWrite(TRIGGER, HIGH); // Toggle MOSFET Bistable Switch
delay(pulseDuration);
digitalWrite(TRIGGER, LOW);
}
wifi_Start();
configTimeCustom();
// Web route: /relay with HTML from index7.h
server.on("/relay", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, PSTR("text/html"), HTML7, processor7);
triggerSwitchEvent(1); // switchState = 1 (battery ON / wake)
needAnotherCountdown = 1;
onceTick.once(60, countdownTrigger);
});
// Initialize E220
e220ttl.begin();
delay(delayTime);
server.begin();
// Create WOR task pinned to Core 1
xTaskCreatePinnedToCore(
WORTask,
"WORTask",
4096,
NULL,
1,
&worTaskHandle,
1 // Core 1
);
}
// ---------------------------
// Main loop (Core 0) — keep light for WiFi
// ---------------------------
void loop() {
// If something available from E220 (optional diagnostic RX on transmitter)
if (e220ttl.available() > 1) {
ResponseContainer rc = e220ttl.receiveMessage();
if (rc.status.code != 1) {
Serial.println(rc.status.getResponseDescription());
} else {
Serial.println(rc.status.getResponseDescription());
Serial.println(rc.data);
}
}
if (Serial.available()) {
String input = Serial.readString();
ResponseStatus rs = e220ttl.sendFixedMessage(0, DESTINATION_ADDL, CHANNEL, input);
Serial.println(rs.getResponseDescription());
}
// Countdown expiry logic: schedule switchState = 2 event
if (countdownExpired) {
countdownExpired = false;
triggerSwitchEvent(2); // switchState = 2 (battery OFF / sleep)
if (needAnotherCountdown == 1) {
onceTick.once(60, countdownTrigger);
needAnotherCountdown = 0;
}
}
delay(1); // yield to WiFi / system tasks
}
// ---------------------------
// Get formatted time string
// ---------------------------
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
}
// ---------------------------
// Custom configTime wrapper + NTP wait
// ---------------------------
void configTimeCustom() {
configTime(0, 0, udpAddress1, udpAddress2);
setenv("TZ", TZ, 1);
tzset();
if (connected) {
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);
}
// ---------------------------
// Get current date and time components
// ---------------------------
DateTime getCurrentDateTime() {
DateTime currentDateTime;
time_t now = time(nullptr);
struct tm *ti = localtime(&now);
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;
}
// ---------------------------
// Example web client to remote URL (not core to WOR)
// ---------------------------
void webInterface() {
String data = "http://192.123.12.15/relay";
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(data);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.print("HttpCode: ");
Serial.print(httpCode);
Serial.println("\n");
// Serial.println(payload);
http.end();
} else {
Serial.print("HttpCode: ");
Serial.print(httpCode);
Serial.println(" URL Request failed.");
http.end();
}
} else {
Serial.println("Error in WiFi connection");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment