esp32-timed-switch/esp32-timed-switch.ino

443 lines
17 KiB
Arduino
Raw Normal View History

2023-10-28 15:53:13 +00:00
#include "esp_system.h"
#include "sntp.h"
#include "time.h"
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Preferences.h>
#include "scheduler.h"
#include "scheduler_prefs.h"
2023-11-09 19:07:34 +00:00
// --------------------------------------------
2023-10-28 15:53:13 +00:00
// Init const and global objects
2023-11-09 19:07:34 +00:00
// --------------------------------------------
2023-10-28 15:53:13 +00:00
2024-02-03 15:07:29 +00:00
const char* html_app="<html><head><title>ESP32 timed Switch</title><script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js></script></head><body><h1>Scheduler</h1><script>class Schedule{constructor(scheduleArray){if(scheduleArray&&scheduleArray.length===24){this.schedule=scheduleArray.slice();}else{this.schedule=[24].fill(0x000)}}loadFromArrayBuffer=function(buffer){var view=new DataView(buffer);for(var i=0;i<24;i+=1){this.schedule[i]=view.getUint32(i*4)}};updateSegment(hour,segment,state){if(hour<0||hour>=24){throw new Error('Hour must be between 0 and 23.')}if(segment<0||segment>=12){throw new Error('Segment must be between 0 and 11.')}const bitPosition=11-segment;if(state){this.schedule[hour]|=(1<<bitPosition)}else{this.schedule[hour]&= ~(1<<bitPosition)}}serializeForDisplay(){return this.schedule.map(hour=>{if(hour===0xFFF){return{displayValue:' ',class:'on'}}else if(hour===0x000){return{displayValue:' ',class:'off'}}else{return{displayValue:' ',class:'partial'}}})}prepareEditMatrix(){const editMatrix=[];for(let hour=0;hour<24;hour+=1){const hourSegments=[];for(let segment=0;segment<12;segment+=1){hourSegments.push({value:(this.schedule[hour]&(1<<segment))!==0?1:0})}editMatrix.push(hourSegments.reverse())}return editMatrix}updateFromEditMatrix(editMatrix){for(let hour=0;hour<24;hour+=1){let hourValue=0;for(let segment=0;segment<12;segment+=1){if(editMatrix[hour][11-segment].value){hourValue|=(1<<segment)}}this.schedule[hour]=hourValue}}serializeToArrayBuffer=function(){var buffer=new ArrayBuffer(24*4);var view=new DataView(buffer);for(var i=0;i<this.schedule.length;i+=1){view.setUint32(i*4,this.schedule[i])}return buffer}}var app=angular.module('plugSchedulerApp',[]);app.controller('PlugScheduleController',['$http',function($http){var ctrl=this;ctrl.editMode=false;ctrl.$onInit=function(){ctrl.scheduleObj=new Schedule();ctrl.getSchedule()};ctrl.getSchedule=function(){if(ctrl.plugId===undefined){throw new Error('plugId is not defined')}var url=`/api/schedule/${ctrl.plugId }`;$http.get(url,{responseType:'arraybuffer'}).then(function(response){ctrl.scheduleObj.loadFromArrayBuffer(response.data);ctrl.displaySchedule=ctrl.scheduleObj.serializeForDisplay()}).catch(function(error){console.error('Error fetching schedule:',error)})};ctrl.modifySchedule=function(){ctrl.editMode=true;ctrl.editMatrix=ctrl.scheduleObj.prepareEditMatrix()};ctrl.saveSchedule=function(){ctrl.editMode=false;ctrl.scheduleObj.updateFromEditMatrix(ctrl.editMatrix);ctrl.updateSchedule()};ctrl.updateSchedule=function(){var url=`/api/schedule/${ctrl.plugId }`;var serializedSchedule=ctrl.scheduleObj.serializeToArrayBuffer();$http.post(url,serializedSchedule,{headers:{'Content-Type':'application/octet-stream'},responseType:'arraybuffer',transformRequest:[]}).then(function(response){console.log(\"successfully updated\");ctrl.displaySchedule=ctrl.scheduleObj.serializeForDisplay()},function(error){console.error('Error updating schedule:',error)})};ctrl.checkAll=function(hourIndex){ctrl.editMatrix[hourIndex].forEach(segment=>{segment.value=1})};ctrl.uncheckAll=function(hourIndex){ctrl.editMatrix[hourIndex].forEach(segment=>{segment.value=0})}}]);app.component('plugScheduleWidget',{bindings:{plugId:'<'},template:`<div class=plug-schedule-widget><h3>Schedule for Plug {{ $ctrl.plugId }}</h3><div ng-if=!$ctrl.editMode><div class=hour-cell ng-class=hour.class ng-repeat=\"hour in $ctrl.displaySchedule track by $index\">{{ hour.displayValue }}</div><button ng-click=$ctrl.modifySchedule()>Modify Schedule</button></div><div ng-if=$ctrl.editMode><table><tr ng-repeat=\"hourSegments in $ctrl.editMatrix track by $index\"><td ng-repeat=\"segment in hourSegments track by $index\"><input ng-false-value=0 ng-model=segment.value ng-true-value=1 type=checkbox><td><button ng-click=$ctrl.checkAll($index)>Check All</button> <button ng-click=$ctrl.uncheckAll($index)>Uncheck All</button></table><button ng-click=$ctrl.saveSchedule()>Save Schedule</button></div></div>`,controller:'PlugScheduleController'});</script><div ng-app=plugSchedulerApp><plug-schedule-widget
2023-12-07 18:52:59 +00:00
// PREFERENCES
2023-10-28 15:53:13 +00:00
Preferences preferences;
#define RW_MODE false
#define RO_MODE true
2023-11-02 22:27:48 +00:00
// WIFI
2024-01-18 18:32:12 +00:00
const char* ssid = "/tmp/lab";
const char* password = "bitte2cucung";
// const char* ssid = "Wokwi-GUEST";
// const char* password = "";
2023-10-28 17:06:10 +00:00
const uint8_t wifi_loop_max = 10;
2023-10-28 15:53:13 +00:00
// NTP
bool sntp_initialized = false;
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long gmtOffset_sec = 2 * 3600; // GMT + 2
const int daylightOffset_sec = 0;
2023-11-02 22:27:48 +00:00
// TIMER
2023-10-28 15:53:13 +00:00
hw_timer_t*timer_main = NULL;
uint32_t timer_interval = 3000000;
uint16_t timer_interval_in_secs = timer_interval / 1000000;
;
2023-11-02 22:27:48 +00:00
// SCHEDULER
bool brun_scheduler = true;
2023-10-28 15:53:13 +00:00
uint8_t current_hour = HOUR_DEFAULT;
uint8_t current_minute = MINUTE_DEFAULT;
uint8_t current_second = 0;
uint32_t current_ts = 0;
uint32_t next_event_ts = 0;
2023-11-02 22:27:48 +00:00
int scheduler_1[24] ; // = {A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60,A60};
int scheduler_2[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
2024-01-18 18:32:12 +00:00
int scheduler_3[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
int scheduler_4[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
int scheduler_5[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
int scheduler_6[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
int scheduler_7[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
int scheduler_8[24] ; // = {A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00,A00};
2023-11-02 22:27:48 +00:00
int* scheduler_list[] = {
2023-10-28 15:53:13 +00:00
scheduler_1,
2024-01-18 18:32:12 +00:00
scheduler_2,
scheduler_3,
scheduler_4,
scheduler_5,
scheduler_6,
scheduler_7,
scheduler_8,
2023-10-28 15:53:13 +00:00
};
2023-11-02 22:27:48 +00:00
// PINS
unsigned int PIN_1 = 12;
unsigned int PIN_2 = 14;
unsigned int PIN_3 = 27;
unsigned int PIN_4 = 26;
unsigned int PIN_5 = 20;
unsigned int PIN_6 = 33;
unsigned int PIN_7 = 32;
unsigned int PIN_8 = 35;
2024-01-27 18:15:53 +00:00
unsigned int pin_list[8] = {
2023-10-28 15:53:13 +00:00
PIN_1,
2024-01-18 18:32:12 +00:00
PIN_2,
PIN_3,
PIN_4,
PIN_5,
PIN_6,
PIN_7,
PIN_8
2023-10-28 15:53:13 +00:00
};
2023-11-02 22:27:48 +00:00
// HTTP
2023-12-07 18:52:59 +00:00
WiFiClient client;
2023-11-09 19:07:34 +00:00
WiFiServer http_server(3000);
2023-11-02 22:27:48 +00:00
String request;
2023-11-09 19:07:34 +00:00
// --------------------------------------------
2023-10-28 15:53:13 +00:00
// Local functions
2023-11-09 19:07:34 +00:00
// --------------------------------------------
2023-10-28 15:53:13 +00:00
// Time helper
uint32_t get_timestamp( uint8_t hour, uint8_t minute, uint8_t second){
return ( 3600 * hour + 60 * minute + second) % 86400;
}
2023-11-02 22:27:48 +00:00
bool event_keep_waiting( uint32_t current_ts, uint32_t event_ts ){
2023-10-28 15:53:13 +00:00
2023-11-02 22:27:48 +00:00
// basic case
// ts: 11h 41m 11s
// ev: 11h 44m 0s
// ts < ev => TRUE
2023-10-28 15:53:13 +00:00
if ( current_ts < event_ts ){
2023-11-02 22:27:48 +00:00
return true;
2023-10-28 15:53:13 +00:00
}
2023-11-02 22:27:48 +00:00
// if time looped at midnight
// ts: 23h 57m 21s
// ev: 0h 1m 0s
// ts > ev => TRUE (for high to very low values )
2023-10-28 15:53:13 +00:00
if( (current_ts - event_ts) > 600 && event_ts < 300 ) {
2023-11-02 22:27:48 +00:00
return true;
2023-10-28 15:53:13 +00:00
}
2023-11-02 22:27:48 +00:00
// ts: 11h 44m 11s
// ev: 11h 44m 0s
// ts > ev => FALSE
return false;
2023-10-28 15:53:13 +00:00
}
// NTP Callback
void timeavailable(struct timeval *t)
{
Serial.println("NTP::Got time adjustment from NTP!");
sntp_initialized = true;
return;
}
// TIMER Callback
void IRAM_ATTR onTimer(){
2023-11-02 22:27:48 +00:00
brun_scheduler = true;
2023-10-28 17:06:10 +00:00
}
2023-11-02 20:12:18 +00:00
2023-11-02 22:27:48 +00:00
void run_scheduler(){
// Serial.println("onTimer::run");
2023-10-28 15:53:13 +00:00
// Get the current time via NTP, or downgrade
if ( sntp_initialized ){
struct tm timeinfo;
time_t now;
time(&now);
localtime_r(&now, &timeinfo);
current_hour = timeinfo.tm_hour;
current_minute = timeinfo.tm_min;
current_second = timeinfo.tm_sec;
2023-10-28 17:06:10 +00:00
2023-10-28 15:53:13 +00:00
// If no NTP clock
}else{
2023-10-28 17:06:10 +00:00
// Serial.println("onTimer::NO NTP");
2023-10-28 15:53:13 +00:00
current_second += timer_interval_in_secs;
if(current_second >= 60) {
current_second = 0;
current_minute += 1;
if(current_minute >= 60) {
current_minute = 0;
current_hour += 1;
if(current_hour >= 24) {
current_hour = 0;
}
}
}
2023-11-02 22:27:48 +00:00
}
2023-10-28 15:53:13 +00:00
// If not expected to run exit
current_ts = get_timestamp( current_hour, current_minute, current_second );
2024-01-18 18:32:12 +00:00
// Serial.printf("onTimer::check event %d %d\n", current_ts, next_event_ts);
2023-11-02 22:27:48 +00:00
if( event_keep_waiting( current_ts, next_event_ts ) ){
2023-10-28 15:53:13 +00:00
return;
}
// Set the next target
next_event_ts = get_timestamp( current_hour, current_minute + EVENT_INC_MINUTE, current_second );
Serial.println("Reconfigure the Relays!");
// Run the relays reconfiguration
2024-02-01 21:55:41 +00:00
for ( int i = 0; i < 8; i++ ) {
2023-10-28 15:53:13 +00:00
// Get a pointer to the current array
2023-11-02 22:27:48 +00:00
int* scheduler = scheduler_list[i];
2023-10-28 15:53:13 +00:00
unsigned int pin = pin_list[i];
// Set the expected status
uint8_t minutes_by_5 = (current_minute / 5);
2023-11-02 22:27:48 +00:00
int hourly_12_values = scheduler[current_hour];
2023-10-28 15:53:13 +00:00
// Serial.printf("Check hour(%d) >> minutes(%d x 5)\n", hourly_12_values, minutes_by_5);
char buffer[40];
if (( hourly_12_values >> minutes_by_5 ) & 1) {
digitalWrite(pin, HIGH);
sprintf (buffer, "Pin %d is up ", pin);
} else {
digitalWrite(pin, LOW);
sprintf (buffer, "Pin %d is down ", pin);
}
Serial.println(buffer); // Get the expected status
}
Serial.println("Reconfiguration over");
return;
}
2023-11-09 19:07:34 +00:00
// HTTP
2024-02-01 21:55:41 +00:00
const char* getPreferenceName( int plug_id){
switch (plug_id) {
case 0:
return RELAY_1_SCHEDULE;
break;
case 1:
return RELAY_2_SCHEDULE;
break;
case 2:
return RELAY_3_SCHEDULE;
break;
case 3:
return RELAY_4_SCHEDULE;
break;
case 4:
return RELAY_5_SCHEDULE;
break;
case 5:
return RELAY_6_SCHEDULE;
break;
case 6:
return RELAY_7_SCHEDULE;
break;
case 7:
return RELAY_8_SCHEDULE;
break;
}
2023-11-09 19:07:34 +00:00
}
2024-02-03 15:07:29 +00:00
void saveSchedule( int plug_id, unsigned int data[24]){
2024-02-01 21:55:41 +00:00
const char* preference_name = getPreferenceName( plug_id );
2024-02-03 15:07:29 +00:00
Serial.print("saving to ");
Serial.println(preference_name);
preferences.putBytes(preference_name, data, sizeof(data));
2024-02-01 21:55:41 +00:00
}
2023-11-09 19:07:34 +00:00
2024-02-03 15:07:29 +00:00
void sendSchedule(WiFiClient &client, int plug_id) {
int buf[24];
const char* preference_name = getPreferenceName( plug_id );
preferences.getBytes(preference_name, buf, preferences.getBytesLength(preference_name));
client.write((const uint8_t*)buf, 24 * sizeof(int));
2023-11-09 19:07:34 +00:00
}
// --------------------------------------------
// ARDUINO Functions
// --------------------------------------------
2023-10-28 15:53:13 +00:00
void setup(){
// HARDWARE
Serial.begin(115200);
delay(1000);
Serial.println("Starting.");
pinMode(PIN_1, OUTPUT);
pinMode(PIN_2, OUTPUT);
// PREFERENCES
Serial.println("Setup::PREFERENCES");
preferences.begin(PREF_NAMESPACE, RW_MODE);
2023-11-02 22:27:48 +00:00
preferences.clear();
2023-11-09 19:07:34 +00:00
2023-11-02 22:27:48 +00:00
bool pref_init = preferences.isKey("test");
2023-10-28 15:53:13 +00:00
if (pref_init == false) {
2023-11-02 22:27:48 +00:00
Serial.println("preferences not exist");
preferences.putBool("test", true);
2024-02-03 15:07:29 +00:00
/*
saveSchedule(1,scheduler_default_deactivate);
saveSchedule(2,scheduler_default_deactivate);
saveSchedule(3,scheduler_default_deactivate);
saveSchedule(4,scheduler_default_deactivate);
saveSchedule(5,scheduler_default_deactivate);
saveSchedule(6,scheduler_default_deactivate);
saveSchedule(7,scheduler_default_deactivate);
saveSchedule(8,scheduler_default_deactivate);
*/
2023-10-28 15:53:13 +00:00
}
2024-02-03 15:07:29 +00:00
/*
2023-10-28 15:53:13 +00:00
preferences.getBytes(RELAY_1_SCHEDULE, scheduler_1, preferences.getBytesLength(RELAY_1_SCHEDULE));
preferences.getBytes(RELAY_2_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_2_SCHEDULE));
2024-01-27 18:15:53 +00:00
preferences.getBytes(RELAY_3_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_3_SCHEDULE));
preferences.getBytes(RELAY_4_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_4_SCHEDULE));
preferences.getBytes(RELAY_5_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_5_SCHEDULE));
preferences.getBytes(RELAY_6_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_6_SCHEDULE));
preferences.getBytes(RELAY_7_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_7_SCHEDULE));
preferences.getBytes(RELAY_8_SCHEDULE, scheduler_2, preferences.getBytesLength(RELAY_8_SCHEDULE));
2024-02-03 15:07:29 +00:00
*/
2023-10-28 15:53:13 +00:00
preferences.end();
2023-11-02 22:27:48 +00:00
Serial.println("Setup::PREFERENCES::end");
2023-10-28 15:53:13 +00:00
// WIFI
Serial.println("Setup::WIFI");
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
2023-10-28 17:06:10 +00:00
uint8_t wifi_loop_count = 0;
2023-11-02 22:27:48 +00:00
while (WiFi.status() != WL_CONNECTED or wifi_loop_count < wifi_loop_max ) {
2023-11-09 19:07:34 +00:00
delay(1000);
2023-10-28 17:06:10 +00:00
wifi_loop_count++;
2023-10-28 15:53:13 +00:00
Serial.print(".");
}
2023-11-09 19:07:34 +00:00
2023-10-28 15:53:13 +00:00
// TIMER
Serial.println("Setup::Timer");
timer_main = timerBegin(0, 80, true);
timerAttachInterrupt(timer_main, &onTimer, true);
timerAlarmWrite(timer_main, timer_interval, true);
timerAlarmEnable(timer_main);
// NTP
Serial.println("Setup::NTP");
sntp_set_time_sync_notification_cb(timeavailable);
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
delay(5000);
// SCHEDULER
Serial.println("Setup::SCHEDULER");
2023-10-28 17:06:10 +00:00
next_event_ts = get_timestamp( HOUR_DEFAULT, MINUTE_DEFAULT, 10 );
2023-10-28 15:53:13 +00:00
// HTTP
Serial.println("Setup::HTTP");
2023-11-09 19:07:34 +00:00
http_server.begin();
2023-11-02 22:27:48 +00:00
Serial.print("Connect to IP Address: ");
Serial.print("http://");
Serial.println(WiFi.localIP());
2023-10-28 15:53:13 +00:00
// Todo
2024-01-18 18:32:12 +00:00
2023-10-28 15:53:13 +00:00
}
2023-11-02 22:27:48 +00:00
2023-10-28 15:53:13 +00:00
void loop(){
2023-11-02 22:27:48 +00:00
if( brun_scheduler == true ){
brun_scheduler = false;
run_scheduler();
2023-10-28 17:06:10 +00:00
}
2023-10-28 15:53:13 +00:00
2023-12-07 18:52:59 +00:00
client = http_server.available();
if (!client) {
2024-01-18 18:32:12 +00:00
// Serial.println("Server not available... skip...");
2023-11-02 22:27:48 +00:00
return;
}
2023-11-09 19:07:34 +00:00
String request = client.readStringUntil('\r');
2024-01-27 18:15:53 +00:00
Serial.print("request: ");
Serial.println(request);
2023-11-02 22:27:48 +00:00
2023-11-09 19:07:34 +00:00
// GET request
if (request.indexOf("GET /api/schedule/") >= 0) {
2024-02-03 15:07:29 +00:00
int startIdIndex = 18;
int endIdIndex = 19;
2024-01-18 18:32:12 +00:00
2024-02-01 21:55:41 +00:00
String plug_idStr = request.substring(startIdIndex, endIdIndex);
int plug_id = plug_idStr.toInt() - 1; // Adjust for 0 index
Serial.print("plug_id " );
Serial.println(plug_id);
if (plug_id >= 0 && plug_id < 8) {
2023-11-09 19:07:34 +00:00
client.println("HTTP/1.1 200 OK");
2024-01-18 18:32:12 +00:00
client.println("Content-Type: text/html");
2023-11-09 19:07:34 +00:00
client.println("Connection: close");
client.println();
2024-02-03 15:07:29 +00:00
sendSchedule(client, plug_id);
2023-11-09 19:07:34 +00:00
} else {
client.println("HTTP/1.1 404 Not Found");
client.println("Connection: close");
client.println();
}
2023-12-07 18:52:59 +00:00
// GET homepage
} else if (request.indexOf("GET /") >= 0) {
client.println("HTTP/1.1 200 OK");
2024-01-18 18:32:12 +00:00
client.println("Content-Type: text/html");
2023-12-07 18:52:59 +00:00
client.println("Connection: close");
client.println();
client.println(html_app);
2024-01-18 18:32:12 +00:00
} else {
2024-01-27 18:15:53 +00:00
2024-01-18 18:32:12 +00:00
// POST request
int slashIndex = request.indexOf("POST /api/schedule/");
if (slashIndex != -1) {
2024-01-27 18:15:53 +00:00
Serial.print("slashIndex: ");
Serial.println(slashIndex);
int nextSlashIndex = slashIndex + 19;
2024-01-18 18:32:12 +00:00
// Extract the plug ID
2024-02-01 21:55:41 +00:00
String plug_idStr = request.substring(nextSlashIndex, nextSlashIndex + 1);
Serial.print("plug_idStr ");
Serial.println(plug_idStr);
int plug_id = plug_idStr.toInt() - 1;
// Force a new line
client.readStringUntil('\n');
2024-01-18 18:32:12 +00:00
// Read the next line which should have the POST body/content
2024-01-27 18:15:53 +00:00
String preBody ;
while ( preBody = client.readStringUntil('\n') ){
2024-02-01 21:55:41 +00:00
if( preBody.isEmpty() ){
2024-01-27 18:15:53 +00:00
break;
}
}
2024-02-01 21:55:41 +00:00
Serial.println("Done reading. Going for binary.");
2024-02-03 15:07:29 +00:00
char post_body[24];
client.readBytes(post_body, 24);
Serial.print("Read post_body: ");
Serial.println(post_body);
2024-01-18 18:32:12 +00:00
// Update the schedule for the specified plug
2024-02-01 21:55:41 +00:00
if (plug_id >= 0 && plug_id < 8) {
2024-02-03 15:07:29 +00:00
unsigned int byte_value[24];
for (int i=0; i < 24; i++) {
byte_value[i] = (int) post_body[i];
}
saveSchedule(plug_id, byte_value );
2024-02-01 21:55:41 +00:00
Serial.println("Schedule updated for plug " + plug_idStr);
2024-01-18 18:32:12 +00:00
} else {
Serial.println("Invalid plug ID");
}
2024-01-27 18:15:53 +00:00
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
2023-11-02 22:27:48 +00:00
}
}
2023-12-07 18:52:59 +00:00
client.stop();
2023-11-02 22:27:48 +00:00
2023-10-28 15:53:13 +00:00
2023-10-28 17:06:10 +00:00
}