commit c9661b2095c126785674de0ca5a40c295be73ed5
parent d1acf00ac7f4c73244bd48b31ee7acea3405f67a
Author: Martin Klöckner <mjkloeckner@gmail.com>
Date: Thu, 3 Apr 2025 00:39:45 -0300
Merge `timer_values_t`
Implement all he timer logic
Diffstat:
| M | data/main.js | | | 10 | +++++----- |
| M | src/main.cpp | | | 380 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
2 files changed, 267 insertions(+), 123 deletions(-)
diff --git a/data/main.js b/data/main.js
@@ -36,7 +36,7 @@ function rssi_to_percentage(rssi) {
function update_checkbox() {
main_checkbox.setAttribute("checked", received_data["main-output-enabled"]);
- timer_checkbox.setAttribute("checked", received_data["timer-enabled"]);
+ timer_checkbox.setAttribute("checked", received_data["timer"]["enabled"]);
}
function update_time() {
@@ -47,9 +47,10 @@ function update_time() {
}
function update_timer() {
- console.log(received_data);
- from_time.value = received_data["from-time"];
- to_time.value = received_data["to-time"];
+ from_time.value = received_data["timer"]["from"]["hour"] + ":"
+ + received_data["timer"]["from"]["minute"];
+ to_time.value = received_data["timer"]["to"]["hour"] + ":"
+ + received_data["timer"]["to"]["minute"];
}
function update_all() {
@@ -124,7 +125,6 @@ function timer_set_time() {
query_json.to = new Object();
query_json.to.hour = to_time.value.slice(0, 2);
query_json.to.minute = to_time.value.slice(3, 5);
- console.log(query_json);
socket.send(query_types["TIMER_SET_VALUES"] + JSON.stringify(query_json));
}
diff --git a/src/main.cpp b/src/main.cpp
@@ -7,8 +7,8 @@
#include <time.h>
#include <coredecls.h> // settimeofday_cb() callback
-#define REMOTE_LED_PIN LED_BUILTIN
-#define MAIN_OUTPUT_PIN 5
+#define REMOTE_LED_PIN LED_BUILTIN
+#define MAIN_OUTPUT_PIN 5
#define MAIN_SWITCH_INPUT_PIN 4
#define WIFI_SSID "hello-world"
@@ -18,30 +18,102 @@
#define MY_NTP_SERVER "pool.ntp.org"
#define MY_TZ "<-03>3"
-#define MAIN_SWITCH_REPEAT_TIME 200
+#define MAIN_SWITCH_REPEAT_TIME 500
+
+typedef enum {
+ MAIN_OUTPUT_STATUS = 0,
+ MAIN_OUTPUT_TOGGLE,
+ TIMER_TOGGLE,
+ TIMER_SET_VALUES
+} query_t;
+
+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, timer_data;
+timer_values_t timer_values;
time_t system_time, last_ntp_sync;
-tm tm;
uint8_t remote_devices = 0, timer_enabled = 0, main_output_enabled = 0;
-
-void webp_socket_event(uint8_t, WStype_t, uint8_t *, size_t);
-void webp_handler();
-
-void main_output_toggle();
-void update_all_socket_clients();
+uint32_t t = 0, system_time_dt = 0, main_output_dt = 0, timer_dt = 0;
void setup_wifi();
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 update_all_socket_clients();
+
+void websocket_event_handler(uint8_t, WStype_t, uint8_t *, size_t);
+
+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.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();
+
+ 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;
+ }
+
+ 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()
+ };
+
+ timer_enabled = String(timer_values["enabled"]).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) {
+ tm tm;
+
time(&system_time); // read the current time
localtime_r(&system_time, &tm); // update the structure tm with the current time
@@ -64,108 +136,52 @@ uint32_t sntp_startup_delay_MS_rfc_not_less_than_60000 () {
// ntp polling interval
uint32_t sntp_update_delay_MS_rfc_not_less_than_15000 () {
- return 8 * 60 * 60 * 1000UL; // 60 mins
+ return 8 * 60 * 60 * 1000UL; // 8*60 mins
}
-void setup() {
- pinMode(REMOTE_LED_PIN, OUTPUT);
- pinMode(MAIN_OUTPUT_PIN, OUTPUT);
- pinMode(MAIN_SWITCH_INPUT_PIN, INPUT);
-
- digitalWrite(REMOTE_LED_PIN, HIGH); // led builtin uses inverted logic
- digitalWrite(MAIN_OUTPUT_PIN, LOW);
-
- Serial.begin(115200);
- Serial.setDebugOutput(false);
- Serial.printf("\n\n\n");
- delay(1000);
-
- Serial.printf("[SETUP] Booting");
- for(uint8_t t = 10; t > 0; t--) {
- Serial.print(".");
- Serial.flush();
- delay(150);
- }
- Serial.print("\n");
-
- setup_wifi();
- setup_fs();
- setup_websocket();
- setup_mdns();
- setup_webserver();
- 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();
-}
-
-uint32_t t = 0, clock_dt = 0, main_output_dt = 0;
-void loop() {
- t = millis();
-
- if((t - main_output_dt) > MAIN_SWITCH_REPEAT_TIME) {
- if(digitalRead(MAIN_SWITCH_INPUT_PIN)) {
- main_output_dt = millis();
- main_output_toggle();
- update_all_clients_checkbox();
- }
- }
-
- // if((t - dt) > MAIN_OUTPUT_PERIOD) {
- // dt = millis();
- // digitalWrite(MAIN_OUTPUT_PIN, HIGH);
- // main_output_dt = millis();
- // }
-
- // if((t - main_output_dt) > MAIN_OUTPUT_ON_TIME) {
- // digitalWrite(MAIN_OUTPUT_PIN, LOW);
- // }
-
- MDNS.update();
- web_socket.loop();
- server.handleClient();
-
- // print system time every minute
- if((t - clock_dt) > 60000){
- clock_dt = millis();
- show_time();
+void main_output_set_to_if_not_already(bool state) {
+ if((bool)main_output_enabled != state) {
+ digitalWrite(MAIN_OUTPUT_PIN, main_output_enabled ^= 1);
+ update_all_clients_checkbox();
}
}
-void setup_wifi() {
- WiFi.begin(WIFI_SSID, WIFI_PASSWD);
+void update_timer_output() {
+ tm now_tm;
+ uint16_t timer_from_24, timer_to_24, now_24;
- Serial.printf("[SETUP] Connecting to WiFi");
- while(WiFi.status() != WL_CONNECTED) {
- delay(100);
- Serial.print(".");
- }
- Serial.printf("\n[SETUP] Connected to '%s' IP address ", WIFI_SSID);
- Serial.println(WiFi.localIP());
-}
+ if(!timer_enabled) return;
-void setup_mdns() {
- if (!MDNS.begin(MDNS_DOMAIN)) {
- Serial.println("[SETUP] Error setting up MDNS responder!");
- while(1) { delay(100); }
- }
+ localtime_r(&system_time, &now_tm);
+ timer_from_24 = (timer_values.from_hour*100)+timer_values.from_minute;
+ timer_to_24 = (timer_values.to_hour*100)+timer_values.to_minute;
+ now_24 = (now_tm.tm_hour*100)+now_tm.tm_min;
- MDNS.addService("http", "tcp", 80);
- Serial.printf("[SETUP] mDNS started domain '%s.local'\n", MDNS_DOMAIN);
-}
+ Serial.printf("[TIMER] from: %04d; to: %04d; now: %04d\n",
+ timer_from_24, timer_to_24, now_24);
-void main_output_toggle() {
- if(main_output_enabled) {
- // GPOC = (1 << MAIN_OUTPUT_PIN); // low
- digitalWrite(MAIN_OUTPUT_PIN, LOW);
+ if(timer_to_24 > timer_from_24) {
+ if((now_24 >= timer_from_24) && (now_24 < timer_to_24)) {
+ main_output_set_to_if_not_already(true);
+ }
+ else {
+ main_output_set_to_if_not_already(false);
+ }
} else {
- // GPOS = (1 << MAIN_OUTPUT_PIN); // high
- digitalWrite(MAIN_OUTPUT_PIN, HIGH);
+ if((now_24 < timer_to_24) || now_24 >= timer_from_24) {
+ main_output_set_to_if_not_already(true);
+ }
+ else {
+ main_output_set_to_if_not_already(false);
+ }
}
- main_output_enabled = !main_output_enabled;
}
-void timer_toggle() {
- timer_enabled = !timer_enabled;
+void clear_data() {
+ JSONVar keys = data.keys();
+ for (int i = 0; i < keys.length(); i++) {
+ data[keys[i]] = undefined;
+ }
}
void update_data() {
@@ -176,7 +192,9 @@ void update_data() {
data["system-time"] = (long)system_time;
data["wifi-ssid"] = WIFI_SSID;
data["wifi-rssi"] = WiFi.RSSI();
- data["timer-enabled"] = String(timer_enabled);
+
+ update_timer_data();
+ data["timer"] = timer_data;
}
void update_all_socket_clients() {
@@ -189,20 +207,39 @@ void update_all_clients_checkbox() {
JSONVar output_data;
output_data["type"] = "cb";
output_data["main-output-enabled"] = String(main_output_enabled);
- output_data["timer-enabled"] = String(timer_enabled);
+ output_data["timer"]["enabled"] = String(timer_enabled);
String output_data_as_json = JSON.stringify(output_data);
web_socket.broadcastTXT(output_data_as_json);
}
+String left_pad(uint8_t n) {
+ return n < 10 ? "0" + String(n) : String(n);
+}
-typedef enum {
- MAIN_OUTPUT_STATUS = 0,
- MAIN_OUTPUT_TOGGLE,
- TIMER_TOGGLE,
- TIMER_SET_VALUES
-} query_t;
+void update_timer_data() {
+ timer_data["enabled"] = String(timer_enabled);
+ timer_data["from"]["hour"] = left_pad(timer_values.from_hour);
+ timer_data["from"]["minute"] = left_pad(timer_values.from_minute);
+ timer_data["to"]["hour"] = left_pad(timer_values.to_hour);
+ timer_data["to"]["minute"] = left_pad(timer_values.to_minute);
+}
+
+void update_all_clients_timer_values() {
+ clear_data();
+ update_timer_data();
+ data["type"] = "timer";
+ data["timer"] = timer_data;
+ web_socket.broadcastTXT(JSON.stringify(data).c_str());
+}
-void webp_socket_event(uint8_t num, WStype_t type, uint8_t *payload, size_t len) {
+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);
+}
+
+void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_t len) {
switch(type) {
case WStype_DISCONNECTED:
Serial.printf("[SOCKET] %d: Disconnected\n", num);
@@ -222,14 +259,40 @@ void webp_socket_event(uint8_t num, WStype_t type, uint8_t *payload, size_t len)
switch(query_type) {
case MAIN_OUTPUT_TOGGLE:
- main_output_toggle();
+ digitalWrite(MAIN_OUTPUT_PIN, main_output_enabled ^= 1);
+ if(timer_enabled) {
+ timer_enabled = 0;
+ }
update_all_clients_checkbox();
break;
case TIMER_TOGGLE:
- timer_toggle();
+ timer_enabled = !timer_enabled;
+ update_timer_output();
update_all_clients_checkbox();
break;
- case TIMER_SET_VALUES:
+ case TIMER_SET_VALUES: {
+ payload++; // skip query type
+ Serial.printf("[SOCKET] %s\n", payload);
+ timer_data = JSON.parse((char *)payload);
+
+ if(JSON.typeof(timer_data) == "undefined") {
+ Serial.println("[SOCKET] Parsing payload failed!");
+ break;
+ }
+
+ timer_values_t new_values = {
+ (uint8_t)String(timer_data["from"]["hour"]).toInt(),
+ (uint8_t)String(timer_data["from"]["minute"]).toInt(),
+ (uint8_t)String(timer_data["to"]["hour"]).toInt(),
+ (uint8_t)String(timer_data["to"]["minute"]).toInt()
+ };
+
+ update_timer_values(new_values);
+ update_timer_output();
+ save_timer_values_to_file();
+ update_all_clients_timer_values();
+ }
+ break;
case MAIN_OUTPUT_STATUS:
default:
update_data();
@@ -243,16 +306,10 @@ void webp_socket_event(uint8_t num, WStype_t type, uint8_t *payload, size_t len)
case WStype_BIN:
Serial.printf("[SOCKET][%u] get binary length: %u\n", num, len);
hexdump(payload, len);
- // web_socket.sendBIN(num, payload, len);
break;
}
}
-void setup_websocket() {
- web_socket.begin();
- web_socket.onEvent(webp_socket_event);
-}
-
int webserver_get_file(String path, String &return_page) {
if(LittleFS.exists(path)) {
Serial.printf("[SERVER] Serving file '%s'\n", path.c_str());
@@ -305,6 +362,33 @@ void webserver_handle_root() {
server.send(response_code, "text/html", index_page.c_str());
}
+void setup_wifi() {
+ WiFi.begin(WIFI_SSID, WIFI_PASSWD);
+
+ Serial.printf("[SETUP] Connecting to WiFi");
+ while(WiFi.status() != WL_CONNECTED) {
+ delay(200);
+ Serial.print(".");
+ }
+ Serial.printf("\n[SETUP] Connected to '%s' IP address ", WIFI_SSID);
+ Serial.println(WiFi.localIP());
+}
+
+void setup_mdns() {
+ if (!MDNS.begin(MDNS_DOMAIN)) {
+ Serial.println("[SETUP] Error setting up MDNS responder!");
+ while(1) { delay(100); }
+ }
+
+ MDNS.addService("http", "tcp", 80);
+ Serial.printf("[SETUP] mDNS started domain '%s.local'\n", MDNS_DOMAIN);
+}
+
+void setup_websocket() {
+ web_socket.begin();
+ web_socket.onEvent(websocket_event_handler);
+}
+
void setup_webserver() {
Serial.println("[SETUP] loading server response from file 'index.html'");
@@ -325,3 +409,63 @@ void setup_fs() {
uint8_t percentage_usad = (fs_info.usedBytes/fs_info.totalBytes)*100;
Serial.printf("[SETUP] LittleFS started: spaced used %d%%\n", percentage_usad);
}
+
+void setup() {
+ pinMode(REMOTE_LED_PIN, OUTPUT);
+ pinMode(MAIN_OUTPUT_PIN, OUTPUT);
+ pinMode(MAIN_SWITCH_INPUT_PIN, INPUT);
+
+ digitalWrite(REMOTE_LED_PIN, HIGH); // led builtin uses inverted logic
+ digitalWrite(MAIN_OUTPUT_PIN, LOW);
+
+ Serial.begin(115200);
+ Serial.setDebugOutput(false);
+ Serial.printf("\n\n\n");
+ delay(1000);
+
+ Serial.printf("[SETUP] Booting");
+ for(uint8_t i = 10; i > 0; i--) {
+ Serial.print(".");
+ Serial.flush();
+ delay(150);
+ }
+ Serial.print("\n");
+
+ setup_wifi();
+ setup_fs();
+ setup_websocket();
+ setup_mdns();
+ setup_webserver();
+
+ configTime(MY_TZ, MY_NTP_SERVER); // configure builtin ntp!
+ settimeofday_cb(show_time); // ntp update callback
+
+ show_time();
+ read_timer_values_from_file();
+}
+
+void loop() {
+ t = millis();
+
+ if((t - main_output_dt) > MAIN_SWITCH_REPEAT_TIME) {
+ if(digitalRead(MAIN_SWITCH_INPUT_PIN)) {
+ main_output_dt = millis();
+ digitalWrite(MAIN_OUTPUT_PIN, main_output_enabled ^= 1);
+ if(timer_enabled) {
+ timer_enabled = 0;
+ }
+ update_all_clients_checkbox();
+ }
+ }
+
+ // update `system_time` every 5s
+ if((t - system_time_dt) > 5000){
+ system_time_dt = millis();
+ time(&system_time); // read the current time
+ update_timer_output();
+ }
+
+ MDNS.update();
+ web_socket.loop();
+ server.handleClient();
+}