esp8266-remote-timer

ESP8266 based timer w/ remote control and NTP sync
Index Commits Files Refs README LICENSE
commit b40e44ec8c503f05eeb00b9c17900593e997ea57
parent d1acf00ac7f4c73244bd48b31ee7acea3405f67a
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Mon, 31 Mar 2025 13:14:28 -0300

represent timer set values with `struct timer_values_t`

Diffstat:
Msrc/main.cpp | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 155 insertions(+), 3 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
@@ -20,9 +20,16 @@
 
 #define MAIN_SWITCH_REPEAT_TIME 200
 
+typedef struct {
+    uint8_t from_hour, from_minute;
+    uint8_t to_hour, to_minute;
+} timer_values_t;
+
 ESP8266WebServer server(80);
 WebSocketsServer web_socket = WebSocketsServer(81);
-JSONVar data, system_time_data;
+JSONVar data, system_time_data, timer_data;
+timer_values_t timer_values;
+char from_time[8], to_time[8];
 
 time_t system_time, last_ntp_sync;
 tm tm;
@@ -39,8 +46,79 @@ void setup_websocket();
 void setup_webserver();
 void setup_mdns();
 void setup_fs();
+void update_timer_values(const timer_values_t);
+void update_timer_data();
 void update_all_clients_checkbox();
 
+void save_timer_values_to_file() {
+    const char* file_path = "timer_values.json";
+    File file = LittleFS.open(file_path, "w");
+
+    if (!file) {
+        Serial.printf("[LITTLEFS] Failed to open file `%s` for writing\n", file_path);
+        return;
+    }
+
+    update_timer_data();
+    if (file.print(JSON.stringify(timer_data).c_str())) {
+        // Serial.printf("[LITTLEFS] writing `%s` to file\n", JSON.stringify(timer_data).c_str());
+        Serial.println("[LITTLEFS] Timer values saved");
+    } else {
+        Serial.println("[LITTLEFS] Timer values write failed");
+    }
+
+    // delay(1000);  // Make sure the CREATE and LASTWRITE times are different
+    file.close();
+}
+
+void read_timer_values_from_file() {
+    const char* file_path = "timer_values.json";
+    File file = LittleFS.open(file_path, "r");
+
+    if (!file) {
+        Serial.printf("[LITTLEFS] Failed to open file `%s`. Setting default values..\n", file_path);
+        timer_values_t new_values = {18, 0, 0, 0};
+        update_timer_values(new_values);
+        return;
+    }
+
+    String timer_values_json = file.readString();
+    file.close();
+
+    Serial.printf("[LITTLEFS] Read `%s` from %s\n", timer_values_json.c_str(), file_path);
+
+    JSONVar timer_values = JSON.parse(timer_values_json.c_str());
+
+    if(JSON.typeof(timer_values) == "undefined") {
+        Serial.println("[SOCKET] Parsing file `timer-values.json` failed");
+        return;
+    }
+
+    // Serial.printf("[UPDATE_TIMER_VALUES] %02d:%02d to %02d:%02d\n",
+    //         new_values.from_hour, new_values.from_minute,
+    //         new_values.to_hour, new_values.to_minute);
+
+    // Serial.println(timer_values["from"]["hour"]);
+    // Serial.println(String(timer_values["from"]["hour"]).toInt());
+
+    timer_values_t new_values = {0};
+    /*
+    {
+        String(timer_values["from"]["hour"]).toInt(),
+        String(timer_values["from"]["minute"]).toInt(),
+        String(timer_values["to"]["hour"]).toInt(),
+        String(timer_values["to"]["minute"]).toInt()
+    };
+    */
+
+    Serial.printf("[UPDATE_TIMER_VALUES] %02d:%02d to %02d:%02d\n",
+            new_values.from_hour, new_values.from_minute,
+            new_values.to_hour, new_values.to_minute);
+
+    update_timer_values(new_values);
+    update_timer_data();
+}
+
 void show_time(bool from_sntp = false) {
     time(&system_time);              // read the current time
     localtime_r(&system_time, &tm);  // update the structure tm with the current time
@@ -96,9 +174,34 @@ void setup() {
     configTime(MY_TZ, MY_NTP_SERVER); // Here is the IMPORTANT ONE LINER needed in your sketch!
     settimeofday_cb(show_time);       // ntp update callback
     show_time();
+    read_timer_values_from_file();
+}
+
+uint32_t t = 0, timer_dt = 0, clock_dt = 0, main_output_dt = 0;
+
+void update_timer_output() {
+    // check timer output every minute
+    if((t - clock_dt) > 60*1000) {
+        localtime_r(&system_time, &tm);  // update the structure tm with the current time
+        if((timer_enabled) && (!main_output_enabled)) {
+            if((tm.tm_hour >= timer_values.from_hour)
+                && (tm.tm_min >= timer_values.from_minute)
+                && (tm.tm_min <= timer_values.to_hour)
+                && (tm.tm_min <= timer_values.to_minute)) {
+
+                digitalWrite(MAIN_OUTPUT_PIN, HIGH);
+                main_output_enabled = 1;
+                update_all_clients_checkbox();
+            } 
+            else {
+                digitalWrite(MAIN_OUTPUT_PIN, LOW);
+                main_output_enabled = 0;
+                update_all_clients_checkbox();
+            }
+        }
+    }
 }
 
-uint32_t t = 0, clock_dt = 0, main_output_dt = 0;
 void loop() {
     t = millis();
 
@@ -129,6 +232,8 @@ void loop() {
         clock_dt = millis();
         show_time();
     }
+
+    update_timer_output();
 }
 
 void setup_wifi() {
@@ -177,6 +282,10 @@ void update_data() {
     data["wifi-ssid"]           = WIFI_SSID;
     data["wifi-rssi"]           = WiFi.RSSI();
     data["timer-enabled"]       = String(timer_enabled);
+
+    update_timer_data();
+    data["from-time"] = from_time;
+    data["to-time"] = to_time;
 }
 
 void update_all_socket_clients() {
@@ -194,6 +303,28 @@ void update_all_clients_checkbox() {
     web_socket.broadcastTXT(output_data_as_json);
 }
 
+void update_timer_data() {
+    snprintf(from_time, 8, "%02d:%02d", timer_values.from_hour, timer_values.from_minute);
+    snprintf(to_time, 8, "%02d:%02d", timer_values.to_hour, timer_values.to_minute);
+
+    timer_data["from-time"] = from_time;
+    timer_data["to-time"] = to_time;
+    Serial.printf("[LOOP] from_time: %s; to_time: %s\n", from_time, to_time);
+}
+
+void update_all_clients_timer_values() {
+    // String output_data_as_json = build_timer_values_json();
+    timer_data["type"] = "timer";
+    update_timer_data();
+    web_socket.broadcastTXT(JSON.stringify(timer_data).c_str());
+}
+
+void update_timer_values(const timer_values_t new_values) {
+    timer_values = new_values;
+    Serial.printf("[UPDATE_TIMER_VALUES] %02d:%02d to %02d:%02d\n",
+            timer_values.from_hour, timer_values.from_minute,
+            timer_values.to_hour, timer_values.to_minute);
+}
 
 typedef enum {
     MAIN_OUTPUT_STATUS = 0,
@@ -229,7 +360,28 @@ void webp_socket_event(uint8_t num, WStype_t type, uint8_t *payload, size_t len)
                 timer_toggle();
                 update_all_clients_checkbox();
                 break;
-            case TIMER_SET_VALUES:
+            case TIMER_SET_VALUES: {
+                    payload++;
+                    Serial.printf("[SOCKET] %s\n", payload);
+                    JSONVar timer_values = JSON.parse((char *)payload);
+
+                    if(JSON.typeof(timer_values) == "undefined") {
+                        Serial.println("[SOCKET] Parsing payload failed!");
+                        break;
+                    }
+
+                    timer_values_t new_values = {
+                        (uint8_t)String(timer_values["from"]["hour"]).toInt(),
+                        (uint8_t)String(timer_values["from"]["minute"]).toInt(),
+                        (uint8_t)String(timer_values["to"]["hour"]).toInt(),
+                        (uint8_t)String(timer_values["to"]["minute"]).toInt()
+                    };
+
+                    update_timer_values(new_values);
+                    save_timer_values_to_file();
+                    update_all_clients_timer_values();
+                }
+                break;
             case MAIN_OUTPUT_STATUS:
             default:
                 update_data();