Created
June 21, 2024 06:01
-
-
Save ldijkman/f295d145e135600f832f06f9b7c7924d to your computer and use it in GitHub Desktop.
This file contains 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
// 4mb ESP32 Wroom, ESP32S2, ESP32S3 | |
// you do not need the shields to program/run the code | |
// added FastLED library for onboard LED ESP32S3 WS2812b Lep Pixel | |
// lots of things turned off // remarked | |
// but does some things | |
// https://t.me/Arduino_ESP8266_ESP32 | |
// maybe needs my modifeid version of | |
// https://github.com/ldijkman/Universal-Arduino-Telegram-Bot | |
// added webapp send web app data | |
// https://t.me/Luberth_Dijkman/84 | |
// Join My Telegram Channel? | |
// https://t.me/Luberth_Dijkman | |
// group | |
// https://t.me/Arduino_ESP8266_ESP32 | |
// 4mb wemos d1 mini ESP8266 12E / 12F | |
// https://t.me/Luberth_Dijkman/22 | |
// https://www.google.com/search?q=+wemos+d1+mini+ESP8266+12E+%2F+12F | |
// for test Flashed a 16mb version with 4mb settings | |
// https://t.me/Luberth_Dijkman/61 | |
// added buzzer shield on D5 GPIO14 if temp <10 or >40 buzzer | |
// sorry changed the pinout | |
// so it fits my wemos tripple base pinout https://t.me/Luberth_Dijkman/28 | |
// temperature from the shield DS18B20 needs offset | |
// Buzzer shield control port, default D5 = GPIO 14 ( i had to solder a connection on print for D5) | |
// Temp DS18B20 Shield D2 = GPIO 4 | |
// Relais shield default D1 = GPIO 5 | |
// onboard LED D4 = GPIO 2 (think no good for relais used for flashing, cannot flash program the board when relays is connected) | |
/* | |
const int oneWireBus = 4; // gpio4 yellow=data red=3.3v black/blue=GND | |
const int relayPin = 5; // gpio5 aliexpress d1 mini relais shield | |
const int LED_PIN = 2; // wemos D1 Mini onboard LED | |
chat gpt says | |
The pinout for a Wemos D1 Mini (an ESP8266-based development board) typically looks like this: | |
https://t.me/Luberth_Dijkman/28 | |
D0 -> GPIO16 | |
D1 -> GPIO5 | |
D2 -> GPIO4 | |
D3 -> GPIO0 | |
D4 -> GPIO2 | |
D5 -> GPIO14 | |
D6 -> GPIO12 | |
D7 -> GPIO13 | |
D8 -> GPIO15 | |
TX -> GPIO1 | |
RX -> GPIO3 | |
A0 -> Analog input | |
https://t.me/Luberth_Dijkman/28 | |
*/ | |
// wifi thermostat and off delay timed light switch | |
// lolin wemos v2 relais shield has solderpads for other pin config | |
// to add a second relais | |
/* | |
based on | |
https://github.com/Gianbacchio/ESP8266-TelegramBot | |
https://github.com/CasaJasmina/TelegramBot-Library | |
https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot | |
https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/tree/master/examples | |
channel https://t.me/arduino_telegram_library | |
https://randomnerdtutorials.com/telegram-control-esp32-esp8266-nodemcu-outputs/ | |
https://randomnerdtutorials.com/esp32-esp8266-thermostat-web-server/ | |
*/ | |
// should be possible to set setpoint from telegram buttons 15 and 20 celsius | |
// https://youtu.be/n8N7pnrjb2A | |
// https://t.me/s/Luberth_Dijkman | |
// video https://t.me/Luberth_Dijkman/15 | |
// added bottom menu scan mdns for other ESP devices in telegram | |
// https://t.me/Luberth_Dijkman/18 | |
// added Telegram fault notification message to phone | |
// telegram app for adroid phone | |
// https://play.google.com/store/apps/details?id=org.telegram.messenger&hl=nl&gl=US | |
// Telegram for PC Desktop | |
// https://desktop.telegram.org/ | |
// configure telegram | |
// playing with buttons in Telegram Message | |
// https://youtu.be/-IC-Z78aTOs | |
// https://github.com/witnessmenow/Simple-Home-Automation-With-Telegram/blob/master/LedControl/LedControl.ino | |
/* | |
____ _ _____ __ _ | |
| _ \ | | / ____| / _(_) | |
| |_) | ___ | |_ ______| | ___ _ __ | |_ _ __ _ | |
| _ < / _ \| __|______| | / _ \| '_ \| _| |/ _` | | |
| |_) | (_) | |_ | |___| (_) | | | | | | | (_| | | |
|____/ \___/ \__| \_____\___/|_| |_|_| |_|\__, | | |
__/ | | |
|___/ | |
*/ | |
// Configure your BOT | |
// Create a Bot on Telegram help message https://t.me/Luberth_Dijkman/55 | |
// | |
// go to | |
// https://t.me/botfather | |
// ask for /newbot | |
// answer the questions | |
// and you get Telegram BOT Token (Get from Botfather) | |
// watch this video https://youtu.be/6ehNbx6aFtk | |
// | |
#define BOT_TOKEN "xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
// help message https://t.me/Luberth_Dijkman/57 | |
// go to | |
// https://t.me/myidbot | |
// ask for /getid | |
// watch this video https://youtu.be/6ehNbx6aFtk | |
// | |
#define CHAT_ID "xxxxxxxxxx" | |
// Home image for your Home Bot profile (Manage Bot. (click on image to change)) | |
// https://raw.githubusercontent.com/ldijkman/async-esp-fs-webserver/master/docs/ino/thermostat_web_flash/Home_Bot.png | |
// if you cannot get telegram Bot to work | |
// maybe try and play first with | |
// https://randomnerdtutorials.com/telegram-control-esp32-esp8266-nodemcu-outputs/ | |
/* | |
__ ___ ______ _ _____ __ _ | |
\ \ / (_) ____(_) / ____| / _(_) | |
\ \ /\ / / _| |__ _ | | ___ _ __ | |_ _ __ _ | |
\ \/ \/ / | | __| | | | | / _ \| '_ \| _| |/ _` | | |
\ /\ / | | | | | | |___| (_) | | | | | | | (_| | | |
\/ \/ |_|_| |_| \_____\___/|_| |_|_| |_|\__, | | |
__/ | | |
|___/ | |
*/ | |
// Replace with your network WiFi Router credentials | |
const char *ssid = "Bangert_30_Andijk"; // wifi router name broadcasted in the air | |
const char *password = "ookikwilerin"; // your password | |
// https://github.com/ldijkman/async-esp-fs-webserver/tree/master/docs/ino/thermostat_web_flash | |
// for ESP8266 | |
// asked chatgpt for a on/off websocket thermostat | |
// maybe a bit based on https://randomnerdtutorials.com/esp32-esp8266-thermostat-web-server/ | |
// i loaded ChatGPT with above example | |
// and asked ChatGPT for changes on that code | |
// Start the mDNS responder for http://thermostat.local | |
// should play a sound when WARNING: WARNING Temperature < 10 or > 40 | |
// should turnoff heating if sensor lost, or maxtime heating on | |
// setpoint minimum input 10 maximum input 25 | |
// Android Phone / Tablet Bonjour mDNS Scanner | |
// Easy find your ESP devices on Android | |
// https://play.google.com/store/apps/details?id=de.wellenvogel.bonjourbrowser&pli=1 | |
// Include the Library: "LITTLEFS.h" | |
// The ESP32 environment requires a specific LittleFS library for ESP32, | |
// which is not bundled with the ESP32 core by default. | |
// You have to install it manually. The library can be found here: | |
// https://github.com/lorol/LITTLEFS. Follow the installation instructions provided in the repository. | |
// Correct Initialization: After installing the correct LittleFS library for ESP32, include it at the top of your sketch: | |
//#include "LITTLEFS.h" // Note the capitalization | |
#include "FS.h" | |
//LITTLEFS FS; // Create a LittleFS object | |
#include <time.h> | |
// For ESP8266, you might be using ESPAsyncTCP.h and ESPAsyncWebServer.h | |
// For ESP32, use: | |
#include <AsyncTCP.h> | |
#include <ESPAsyncWebServer.h> | |
#include <OneWire.h> | |
#include <DallasTemperature.h> | |
// For ESP8266: #include <ESP8266mDNS.h> | |
// Change to: | |
#include <ESPmDNS.h> | |
// For ESP8266: #include <ESP8266WiFi.h> | |
// Change to: | |
#include <WiFi.h> | |
#include <WiFiClientSecure.h> | |
#include "esp_heap_caps.h" | |
//#include <ESP8266HTTPClient.h> | |
#include <HTTPClient.h> | |
#include <UniversalTelegramBot.h> // manage libraries, | |
// search for Universal Telegram Bot Library | |
// https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot | |
// maybe needs my modifeid version of | |
// https://github.com/ldijkman/Universal-Arduino-Telegram-Bot | |
// added webapp send web app data | |
// https://t.me/Luberth_Dijkman/84 | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 // This code will only be compiled for ESP32S3 | |
#include <FastLED.h> // Daniel Garcia https://github.com/FastLED/FastLED | |
// Define the number of LEDs and the pin connected to the LED | |
#define NUM_LEDS 1 // Only one LED | |
#define DATA_PIN 47 // esp32s3 Change this to the pin you've connected the LED data line to | |
CRGB led; | |
#endif | |
#include <EEPROM.h> | |
#include <ArduinoJson.h> // Make sure you have the ArduinoJson library | |
WiFiClientSecure secured_client; | |
UniversalTelegramBot bot(BOT_TOKEN, secured_client); | |
// Function declaration | |
void buzzer(); | |
void turnOnLED(); | |
void turnOffLED(); | |
struct tm *timeinfo; | |
const char *mDNS_adress = "thermostat"; // .local is added by ESP | |
String externalIP = ""; // Global variable to store the external IP address | |
// Global variable to store the reset reason as a string | |
String resetReasonStr; | |
uint32_t freeStack; | |
uint32_t freeHeap; | |
// int timeoffset= 7200; // 2 hours in seconds time offset for Holland / The Nederlands | |
unsigned long lightTimerExpires; | |
boolean lightTimerActive = false; | |
int timeRequested; | |
bool RestartTriggered = false; | |
int numNewMessages = 0; | |
struct Task { | |
int taskNumber; | |
String time; | |
int duration; | |
}; | |
// Assuming a maximum of 4 tasks as per your requirement | |
const int maxTasks = 4; | |
Task tasks[maxTasks]; | |
bool manual = 0; | |
// Variables to keep track of millis() and rollovers uptime | |
unsigned long previousMillis = 0; | |
unsigned long rolloverCounter = 0; | |
const unsigned long maxMillis = ULONG_MAX; // Maximum value before rollover (4,294,967,295 for 32-bit unsigned long) | |
// Ensure you allocate enough space for the entire JSON string | |
// used next for url parameter test | |
// char bottomkeyboardJson[1024]; | |
// GPIO where the Wemos D1 Mini DS18B20 shield is connected | |
// const int oneWireBus = 4; // d1 mini ESP8266 D2=gpio4 yellow=data red=3.3v black/blue=GND | |
// const int oneWireBus = 33; // lolin mini ESP32s2 yellow=data red=3.3v black/blue=GND | |
const int oneWireBus = 21; // wemos d1 mini ESP32 yellow=data red=3.3v black/blue=GND | |
//const int oneWireBus = 35; // lolin mini ESP32S3 not working io yellow=data red=3.3v black/blue=GND | |
// needs a 4k7 resistor between data and 3.3v https://duckduckgo.com/?t=lm&q=DS18B20+resistor | |
// Setup a oneWire instance to communicate with any OneWire devices | |
OneWire oneWire(oneWireBus); | |
// Pass our oneWire reference to Dallas Temperature sensor | |
DallasTemperature sensors(&oneWire); | |
// GPIO where the relay is connected | |
// const int relayPin = 16; // gpio16 gpio2=LED gives error on tx on my board | |
// const int relayPin = 5; // wemos D1 mini relais shield has D1(GPIO5) i think | |
const int relayPin = 5; // gpio5 aliexpress d1 mini relais shield | |
//const int LED_PIN = 2; // ESP8266 wemos D1 Mini onboard LED | |
const int LED_PIN = 2; // 2 ESP32 wemos mini onboard LED | |
//const int LED_PIN = 15; // 15 ESP32-S2 purple Wemos S2 Mini onboard LED | |
//const int LED_PIN = 47; // ws2812b needs | |
#define BUZZER_PIN 14 // D5 = GPIO14 | |
// Hysteresis margin, prevent pinball machine effect | |
// deadband around the setpoint, prevent rapid toggling. | |
const float hysteresisMargin = 0.1; // switchpoint +0.1 and -0.1 | |
static bool relayState = false; // Keeps track of the current relay state | |
// Create AsyncWebServer object on port 80 | |
AsyncWebServer server(80); | |
AsyncWebSocket ws("/ws"); | |
// Temperature setpoint, initialized with a default value | |
float temperatureSetpoint = 20.0; | |
// Make sure your keyboardJson string is correctly formatted. | |
// Note: The correction from "callback_path" to "callback_data" for the "Settings" button. | |
String keyboardJson = | |
"[" | |
"[{\"text\":\"ON\",\"callback_data\":\"ON\"},{\"text\":\"OFF\",\"callback_data\":\"OFF\"}]," | |
"[{\"text\":\"1 Min\",\"callback_data\":\"TIME1\"}," | |
"{\"text\":\"5 Min\",\"callback_data\":\"TIME5\"}," | |
"{\"text\":\"10 Min\",\"callback_data\":\"TIME10\"}," | |
"{\"text\":\"15 Min\",\"callback_data\":\"TIME15\"}," | |
"{\"text\":\"30 Min\",\"callback_data\":\"TIME30\"}," | |
"{\"text\":\"1 Hr\",\"callback_data\":\"TIME60\"}]," | |
"[{\"text\":\"10 °C\",\"callback_data\":\"TEMP10\"}," | |
"{\"text\":\"15 °C\",\"callback_data\":\"TEMP15\"}," | |
"{\"text\":\"18 °C\",\"callback_data\":\"TEMP18\"}," | |
"{\"text\":\"20 °C\",\"callback_data\":\"TEMP20\"}," | |
"{\"text\":\"21 °C\",\"callback_data\":\"TEMP21\"}]" | |
"]"; | |
// make it better readable with less \ https://www.blackbox.ai/ | |
const char commands[] PROGMEM = R"rawliteral( | |
[ | |
{"command":"menu", "description":"Thermostat Control Menu"}, | |
{"command":"options", "description":"Thermostat Control Menu"}, | |
{"command":"buzzer", "description":"Test buzzer"}, | |
{"command":"start", "description":"Start"}, | |
{"command":"scan", "description":"Scan network for other ESP mDNS"}, | |
{"command":"set_10", "description":"Temp Setpoint 10 °C"}, | |
{"command":"set_15", "description":"Temp Setpoint 15 °C"}, | |
{"command":"set_18", "description":"Temp Setpoint 18 °C"}, | |
{"command":"set_20", "description":"Temp Setpoint 20 °C"}, | |
{"command":"set_21", "description":"Temp Setpoint 21 °C"}, | |
{"command":"reboot", "description":"Reboot / Restart"}, | |
{"command":"status","description":"Answer device current status"} | |
] | |
)rawliteral"; | |
// String bottomkeyboardJson = "[[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]]"; | |
// Define the JSON keyboard layout as a constant character array in program memory | |
// const char bottomkeyboardJson[] PROGMEM = R"rawliteral(JSON([[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]])JSON")rawliteral";; | |
const char bottomkeyboardJson[] PROGMEM = R"RAW( | |
[ | |
["Menu", "ON", "OFF", "Task"], | |
["1Min", "5Min", "10Min", "15Min", "30Min", "60Min"], | |
["10°", "15°", "16°", "17°", "18°", "19°", "20°", "21°", "22°"], | |
[ | |
"Reboot", "Time", "Buzzer", "Info", | |
{"text": "web_app", "web_app": {"url": "https://ldijkman.github.io/async-esp-fs-webserver/ino/thermostat_web_flash/Telegram_WebApp/Telegram_WebApp.html"}}, | |
{"text": "web_app", "web_app": {"url": "https://ldijkman.github.io/async-esp-fs-webserver/ino/thermostat_web_flash/Telegram_WebApp/schedule.html"}} | |
] | |
] | |
)RAW"; | |
// maybe needs my modifeid version of | |
// https://github.com/ldijkman/Universal-Arduino-Telegram-Bot | |
// added webapp send web app data | |
// https://t.me/Luberth_Dijkman/84 | |
// HTML content with JavaScript for WebSocket communication | |
const char index_html[] PROGMEM = R"rawliteral( | |
<!DOCTYPE HTML><html> | |
<head> | |
<!-- | |
// for ESP8266 | |
// asked chatgpt for a on/off websocket thermostat | |
// maybe a bit based on https://randomnerdtutorials.com/esp32-esp8266-thermostat-web-server/ | |
// maybe a start | |
// Start the mDNS responder for http://thermostat.local | |
--> | |
<title>Thermostat, ESP8266 WiFi Thermostat DS18B20 Temperature Sensor</title> | |
<link rel="icon" type="image/png" href="https://raw.githubusercontent.com/ldijkman/async-esp-fs-webserver/master/docs/ino/thermostat_web_flash/thermo_icon.png"> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
background-color: #303030; /* Dark grey background */ | |
border: 5px solid black; /* Default border color */ | |
/* Ensure the border doesn't affect layout size (optional) */ | |
box-sizing: border-box; | |
} | |
#temperature{ | |
color: yellow; | |
} | |
#setpoint{ | |
color: orange; | |
} | |
#setpointInput { | |
width: 120px; /* Adjust input width as necessary */ | |
border: 2px solid #007bff; /* Blue border for input */ | |
text-align: center; /* Ensure text is centered */ | |
font-size: 2em; /* Increase font size for better visibility */ | |
border-radius: 5px; /* Adds rounded corners to the buttons */ | |
} | |
#currentTime{ | |
padding: 4px; | |
font-weight: bold; | |
font-size: 1.5em; | |
color: lightgray; | |
} | |
.button { | |
width: 50px; /* Width to match the input height for aesthetic consistency */ | |
background-color: #9DA1A5; /* Blue background for buttons */ | |
cursor: pointer; /* Change cursor to pointer to indicate clickable */ | |
font-size: 1.7em; /* Ensure buttons are also easily readable */ | |
border-radius: 5px; /* Adds rounded corners to the buttons */ | |
} | |
#wsMessages { | |
max-height: 200px; | |
width: 300px; | |
text-align: left; /* Aligns text to the left */ | |
padding-left: 10px; /* Adds some space on the left for better readability */ | |
overflow-y: auto; | |
border-radius: 5px; /* Adds rounded corners to the buttons */ | |
} | |
</style> | |
<script> | |
var ws; | |
function initWebSocket() { | |
ws = new WebSocket('ws://' + window.location.hostname + '/ws'); | |
ws.onopen = function(event) { | |
console.log('WebSocket connected'); | |
prependMessageWithTimestamp("<font style='color:green;'>WebSocket connected</font>"); | |
document.body.style.borderColor = 'green'; // Connected: green body border background | |
}; | |
ws.onclose = function(event) { | |
console.log('WebSocket disconnected'); | |
prependMessageWithTimestamp("<font style='color:red;'>WebSocket disconnected</font>"); | |
document.body.style.borderColor = 'red'; // Disconnected: red body border background | |
// Attempt to reconnect after a timeout | |
setTimeout(initWebSocket, 2000); | |
}; | |
ws.onmessage = function(event) { | |
console.log('Message Received:', event.data); | |
var data = event.data.split(':'); | |
if (data[0] === "temperature") { | |
var temperature = parseFloat(data[1]); | |
document.getElementById("temperature").innerHTML = temperature + " <span id='relayStatus'></span>"; | |
// Check if the temperature is < 10 or > 40 and play the alert sound | |
if (temperature < 10 || temperature > 40) { | |
var alertSound = document.getElementById("alertSound"); | |
alertSound.play(); | |
console.warn("WARNING temperature < 10 || temperature > 40 "); | |
prependMessageWithTimestamp('<font style="color:red;">WARNING temperature < 10 || temperature > 40</font>'); | |
} | |
} else if (data[0] === "setpoint") { | |
document.getElementById("setpoint").innerHTML = data[1] ; | |
document.getElementById("setpointInput").value = data[1]; | |
} else if (data[0] === "relays") { | |
// Assume relay state is sent as "1" for on and "0" for off | |
var relayIsOn = data[1] === "1"; | |
updateRelayStatus(relayIsOn); | |
} else if (data[0] === "IP") { | |
var ipString = data[1]+':'+data[2]; ; // also splits at http: Use the IP address as a string | |
//console.log('data[1]', data[1]); | |
// Create a clickable link that opens the IP address in a new tab | |
var message = "<a href='" + ipString + "' target='_blank'>" + ipString + "</a>"; | |
prependMessageWithTimestamp(message); | |
}else if (data[0] === "mDNS") { | |
var mDNSString = data[1]+':'+data[2]; // also splits at http: | |
//console.log('data[1]', data[1]); | |
// Create a clickable link that opens in a new tab | |
var message = "<a href='" + mDNSString + "' target='_blank'>" + mDNSString + "</a>"; | |
prependMessageWithTimestamp(message); | |
} | |
prependMessageWithTimestamp(event.data); | |
// Re-check the relay state each time temperature is updated, in case the relay status span was overwritten | |
// updateRelayStatus(relayState); // Ensure relayState is kept up to date elsewhere in your code or retrieved from this function | |
}; | |
} | |
function prependMessageWithTimestamp(message) { | |
// Create a timestamp for the message | |
var now = new Date(); | |
var timestamp = ('0' + now.getHours()).slice(-2) + ':' + ('0' + now.getMinutes()).slice(-2) + ':' + ('0' + now.getSeconds()).slice(-2); | |
// Display the message with timestamp in the wsMessages div | |
var wsMessages = document.getElementById("wsMessages"); | |
// Prepend new messages with timestamp to the top | |
var newMessage = "[" + timestamp + "] " + message + "<br>"; | |
wsMessages.innerHTML = newMessage + wsMessages.innerHTML; | |
// Limit the number of lines (messages) to 360 | |
var lines = wsMessages.innerHTML.split("<br>"); | |
if (lines.length > 360) { | |
lines = lines.slice(0, 360); // Keep only the newest 360 lines | |
wsMessages.innerHTML = lines.join("<br>"); | |
} | |
} | |
function updateRelayStatus(isOn) { | |
var statusElement = document.getElementById("relayStatus"); | |
if (isOn) { | |
statusElement.innerHTML = "<span style='color: red;'>●</span>"; // Red circle when relay is on | |
} else { | |
// Use a span with a border to create an outline circle | |
statusElement.innerHTML = "<span style='display: inline-block; width: 10px; height: 10px; border: 2px solid blue; border-radius: 50%;'></span>"; | |
} | |
} | |
function sendSetpoint(value) { | |
var minValue = 10; // Define the minimum setpoint value | |
var maxValue = 25; // Define the maximum setpoint value | |
var validatedValue = parseFloat(value); // Parse the input value to a float | |
// Round the value to one decimal place | |
validatedValue = Math.round(validatedValue * 10) / 10; | |
// Check if the parsed value is less than the minimum or greater than the maximum | |
if(validatedValue < minValue) { | |
validatedValue = minValue; // Set to minimum if below the allowed range | |
} else if(validatedValue > maxValue) { | |
validatedValue = maxValue; // Set to maximum if above the allowed range | |
} | |
// Update the input field to reflect the corrected value | |
document.getElementById('setpointInput').value = validatedValue; | |
// Send the validated setpoint value to the server via WebSocket | |
if(ws.readyState === WebSocket.OPEN) { | |
ws.send('setpoint:' + validatedValue); // Send the validated setpoint | |
console.log('ws.send setpoint:', validatedValue); // Log for debugging | |
} else { | |
console.log('WebSocket is not open.'); // Log if the WebSocket connection is not open | |
prependMessageWithTimestamp('WebSocket is not open.'); | |
} | |
} | |
function adjustSetpoint(delta) { | |
var inputField = document.getElementById('setpointInput'); | |
var currentValue = parseFloat(inputField.value); | |
var newValue = currentValue + delta; | |
inputField.value = newValue.toFixed(1); | |
sendSetpoint(newValue); | |
} | |
window.onload = initWebSocket; | |
</script> | |
</head> | |
<body> | |
<center> | |
<h2><a href="http://thermostat.local" style="color:lightgray; text-decoration:none;">ESP8266 WiFi Thermostat DS18B20</a></h2> | |
<h1><span id="temperature">--</span> °C <span id="relayStatus"></span></h1> | |
<h1>Setpoint: <span id="setpoint">--</span> °C</h1> | |
<input type="button" class="button" value="-" onclick="adjustSetpoint(-0.1)" /> | |
<input type="number" id="setpointInput" min="10" max="25" step="0.1" onchange="sendSetpoint(this.value)" placeholder="Set Temperature" value="20"/> <input type="button" class="button" value="+" onclick="adjustSetpoint(0.1)" /> | |
<br> | |
<br><br><br> | |
<!-- Preset temperature setpoint buttons --> | |
<input type="button" class="button preset" value="14°" onclick="sendSetpoint(14)" />  | |
<input type="button" class="button preset" value="15°" onclick="sendSetpoint(15)" />  | |
<input type="button" class="button preset" value="16°" onclick="sendSetpoint(16)" />  | |
<input type="button" class="button preset" value="17°" onclick="sendSetpoint(17)" />  | |
<input type="button" class="button preset" value="18°" onclick="sendSetpoint(18)" /><br> | |
<br> | |
<input type="button" class="button preset" value="19°" onclick="sendSetpoint(19)" />  | |
<input type="button" class="button preset" value="20°" onclick="sendSetpoint(20)" />  | |
<input type="button" class="button preset" value="21°" onclick="sendSetpoint(21)" />  | |
<input type="button" class="button preset" value="22°" onclick="sendSetpoint(22)" /> | |
<br><br> | |
<div id="currentTime"></div> | |
<div id="wsMessages" style="margin-top:20px;padding:10px;background:#f9f9f9;border:1px solid #ddd;"></div> | |
<br><br><br><br><br><br> | |
</center> | |
<script src="https://ldijkman.github.io/async-esp-fs-webserver/foother.js"></script> | |
<script src="https://ldijkman.github.io/Ace_Seventh_Heaven/console.js"></script> | |
<audio id="alertSound" src="https://github.com/ldijkman/async-esp-fs-webserver/raw/master/docs/ino/thermostat_web_flash/sound.mp3" type="audio/mp3"></audio> | |
<script> | |
function updateCurrentTime() { | |
var now = new Date(); | |
var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | |
var months = ["January", "February", "March", "April", "May", "June", | |
"July", "August", "September", "October", "November", "December"]; | |
var dayName = days[now.getDay()]; | |
var monthName = months[now.getMonth()]; | |
var dayNumber = now.getDate(); | |
var currentTimeFormatted = dayName + ' ' + dayNumber + ' ' + monthName + ' ' + | |
('0' + now.getHours()).slice(-2) + ':' + | |
('0' + now.getMinutes()).slice(-2) + ':' + | |
('0' + now.getSeconds()).slice(-2); | |
document.getElementById("currentTime").innerHTML = "" + currentTimeFormatted; | |
} | |
// Update the time immediately and every second thereafter | |
updateCurrentTime(); | |
setInterval(updateCurrentTime, 1000); | |
</script> | |
</body> | |
</html>)rawliteral"; | |
// WebSocket event handler | |
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, | |
AwsEventType type, void *arg, uint8_t *data, size_t len) { | |
if (type == WS_EVT_DATA) { | |
data[len] = 0; // Ensure the incoming data is null-terminated | |
String message = String((char *)data); | |
if (message.startsWith(F("setpoint:"))) { | |
String setpointStr = message.substring(strlen("setpoint:")); | |
float newSetpoint = setpointStr.toFloat(); | |
// Validate the new setpoint | |
if (newSetpoint >= 10.0 && newSetpoint <= 25.0) { | |
temperatureSetpoint = newSetpoint; | |
Serial.print(F("New temperature setpoint received and accepted: ")); | |
Serial.println(temperatureSetpoint); | |
// Broadcast the new setpoint to all connected clients | |
String confirmationMessage = "setpoint:" + String(temperatureSetpoint); | |
server->textAll(confirmationMessage.c_str()); | |
} else { | |
Serial.print(F("Received setpoint out of range: ")); | |
Serial.println(newSetpoint); | |
// Optionally, send a message back to the client indicating the rejection | |
} | |
} | |
} | |
} | |
void getExternalIP() { | |
WiFiClient client; // Create a WiFiClient object | |
if (WiFi.status() == WL_CONNECTED) { // Check WiFi connection status | |
HTTPClient http; // Declare an object of class HTTPClient | |
http.begin(client, "http://api.ipify.org"); // Specify request destination with WiFiClient | |
int httpCode = http.GET(); // Send the request | |
if (httpCode > 0) { // Check the returning code | |
externalIP = http.getString(); // Update the global variable with the external IP | |
Serial.print(F("externalIP ")); | |
Serial.println(externalIP); | |
} else { | |
Serial.printf("Failed to retrieve IP, error: %s\n", http.errorToString(httpCode).c_str()); | |
Serial.println(F("Failed to retrieve IP")); | |
} | |
http.end(); //Close connection | |
} else { | |
Serial.println(F("Error in WiFi connection")); | |
} | |
} | |
// Change the return type from 'void' to 'String' | |
String UpTime() { | |
unsigned long currentMillis = millis(); | |
previousMillis = currentMillis; | |
// Calculate the total uptime in milliseconds, accounting for rollovers | |
unsigned long long totalUptimeMillis = currentMillis + (rolloverCounter * (maxMillis + 1ULL)); | |
// Convert total uptime from milliseconds to seconds | |
unsigned long long totalUptimeSeconds = totalUptimeMillis / 1000; | |
// Calculate days, hours, minutes, and seconds from total uptime | |
unsigned long days = totalUptimeSeconds / 86400; | |
unsigned long hours = (totalUptimeSeconds % 86400) / 3600; | |
unsigned long minutes = (totalUptimeSeconds % 3600) / 60; | |
unsigned long seconds = totalUptimeSeconds % 60; | |
Serial.printf("Uptime: %lu days, %lu hours, %lu minutes, %lu seconds\n", days, hours, minutes, seconds); | |
String message; | |
message.reserve(100); // Reserve memory to prevent fragments | |
message += F("Uptime: "); | |
message += String(days) + " days, " + String(hours) + " hours, " + String(minutes) + " minutes, " + String(seconds) + " seconds"; | |
return message; // Return the constructed message | |
} | |
#include "esp_system.h" // Include ESP32 system header | |
void printResetReason() { | |
esp_reset_reason_t reset_reason = esp_reset_reason(); // Get the reset reason | |
Serial.print(F("Reset reason: ")); | |
switch (reset_reason) { | |
case ESP_RST_POWERON: // Power on reset | |
resetReasonStr = F("Power on reset"); | |
break; | |
case ESP_RST_EXT: // Reset by external pin | |
resetReasonStr = F("External system reset"); | |
break; | |
case ESP_RST_SW: // Software reset via esp_restart | |
resetReasonStr = F("Software restart"); | |
break; | |
case ESP_RST_PANIC: // Software reset due to exception/panic | |
resetReasonStr = F("Exception reset"); | |
break; | |
case ESP_RST_INT_WDT: // Reset (software or hardware) due to interrupt watchdog | |
resetReasonStr = F("Hardware watch dog reset"); | |
break; | |
case ESP_RST_TASK_WDT: // Reset due to task watchdog | |
resetReasonStr = F("Software watch dog reset"); | |
break; | |
case ESP_RST_WDT: // Reset due to other watchdogs | |
resetReasonStr = F("Other watchdog reset"); | |
break; | |
case ESP_RST_DEEPSLEEP: // Wake up from deep-sleep | |
resetReasonStr = F("Wake up from deep-sleep"); | |
break; | |
case ESP_RST_BROWNOUT: // Brownout reset (software or hardware) | |
resetReasonStr = F("Brownout reset"); | |
break; | |
case ESP_RST_SDIO: // Reset over SDIO | |
resetReasonStr = F("Reset over SDIO"); | |
break; | |
default: | |
resetReasonStr = F("Unknown reset reason"); | |
break; | |
} | |
Serial.println(resetReasonStr); | |
} | |
void readTasksFromEEPROM() { | |
int addr = 0; // Start reading at the beginning of EEPROM | |
for (int i = 0; i < maxTasks; i++) { | |
EEPROM.get(addr, tasks[i].taskNumber); | |
addr += sizeof(int); | |
int hour, minute; | |
hour = EEPROM.read(addr); | |
addr++; | |
minute = EEPROM.read(addr); | |
addr++; | |
// Convert hour and minute back to String HH:MM | |
tasks[i].time = String(hour / 10) + String(hour % 10) + ":" + String(minute / 10) + String(minute % 10); | |
EEPROM.get(addr, tasks[i].duration); | |
addr += sizeof(int); | |
} | |
} | |
void writeTasksToEEPROM() { | |
int addr = 0; // Start writing at the beginning of EEPROM | |
for (int i = 0; i < maxTasks; i++) { | |
EEPROM.put(addr, tasks[i].taskNumber); | |
addr += sizeof(int); // Move to the next address | |
// Assuming time is stored as a String HH:MM, convert and store as two bytes | |
int hour = tasks[i].time.substring(0, 2).toInt(); | |
int minute = tasks[i].time.substring(3, 5).toInt(); | |
EEPROM.write(addr, hour); | |
addr++; // Next address | |
EEPROM.write(addr, minute); | |
addr++; // Next address | |
EEPROM.put(addr, tasks[i].duration); | |
addr += sizeof(int); // Move to the next address | |
} | |
EEPROM.commit(); // Make sure to commit changes to EEPROM | |
} | |
void deleteTask(int taskNumber) { | |
bool found = false; | |
for (int i = 0; i < maxTasks; i++) { | |
// If the task is found, mark as found and start shifting remaining tasks | |
if (tasks[i].taskNumber == taskNumber) { | |
found = true; | |
} | |
// Shift tasks to "delete" the task from the array | |
if (found && i < maxTasks - 1) { | |
tasks[i] = tasks[i + 1]; | |
} | |
} | |
// If the last task was deleted or a shift was made, clear the last task | |
if (found) { | |
tasks[maxTasks - 1].taskNumber = 0; // Indicate an empty slot | |
tasks[maxTasks - 1].time = ""; | |
tasks[maxTasks - 1].duration = 0; | |
writeTasksToEEPROM(); // Update EEPROM storage | |
} | |
} | |
void setup() { | |
Serial.begin(115200); | |
EEPROM.begin(512); // Allocate 512 bytes for EEPROM, adjust size as needed | |
readTasksFromEEPROM(); | |
//if (!LITTLEFS.begin(true)) { | |
// Serial.println("An error has occurred while mounting LITTLEFS"); | |
// return; | |
//} | |
pinMode(relayPin, OUTPUT); // Initialize the relay pin as an output | |
digitalWrite(relayPin, LOW); // Start with the relay off | |
pinMode(LED_PIN, OUTPUT); // Initialize the relay pin as an output | |
digitalWrite(LED_PIN, LOW); // Start with the LED high is off | |
pinMode(BUZZER_PIN, OUTPUT); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(&led, NUM_LEDS); // This code will only be compiled for ESP32S3 | |
turnOnLED(); | |
delay(1000); // Keep the LED on for 1 second | |
turnOffLED(); | |
#endif | |
/* | |
* next send bot token and chat id as url parameter to webapp | |
* it works but would not be smart to put this in url paramaters | |
* | |
snprintf(bottomkeyboardJson, sizeof(bottomkeyboardJson), R"RAW( | |
[ | |
["Menu", "ON", "OFF", "Task"], | |
["1Min", "5Min", "10Min", "15Min", "30Min", "60Min"], | |
["10°", "15°", "16°", "17°", "18°", "19°", "20°", "21°", "22°"], | |
[ | |
"Reboot", "Time", "Buzzer", "Info", | |
{"text": "web_app", "web_app": {"url": "https://ldijkman.github.io/async-esp-fs-webserver/ino/thermostat_web_flash/Telegram_WebApp/Telegram_WebApp.html?bot_token=%s&chat_id=%s"}}, | |
{"text": "web_app", "web_app": {"url": "https://ldijkman.github.io/async-esp-fs-webserver/ino/thermostat_web_flash/Telegram_WebApp/schedule.html?bot_token=%s&chat_id=%s"}} | |
] | |
] | |
)RAW", BOT_TOKEN, CHAT_ID, BOT_TOKEN, CHAT_ID); | |
// Debug print to serial | |
Serial.println(bottomkeyboardJson); | |
*/ | |
WiFi.begin(ssid, password); | |
// secured_client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org | |
// Only required on 2.5 Beta | |
// client.setInsecure(); | |
// X509List cert(TELEGRAM_CERTIFICATE_ROOT); | |
// WiFiClientSecure secured_client; | |
// UniversalTelegramBot bot(BOT_TOKEN, secured_client); | |
// WiFiClientSecure secured_client; | |
// Make sure to set up the secure client for ESP32, might require setting up certificates or bypassing them: | |
// secured_client.setInsecure(); // Not recommended for production, but can be used for testing | |
secured_client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org | |
// longPoll keeps the request to Telegram open for the given amount of seconds if there are no messages | |
// This hugely improves response time of the bot, but is only really suitable for projects | |
// where the the initial interaction comes from Telegram as the requests will block the loop for | |
// the length of the long poll | |
bot.longPoll = 2; // 60 looks like blocks my loop | |
while (WiFi.status() != WL_CONNECTED) { | |
delay(1000); | |
Serial.println(F("Connecting to WiFi...")); | |
} | |
Serial.println(F("Connected to WiFi")); | |
// Print the IP address | |
Serial.print(F("IP Address: ")); | |
Serial.println(WiFi.localIP()); | |
getExternalIP(); | |
// Start the DS18B20 sensor | |
sensors.begin(); | |
// Initialize mDNS | |
if (!MDNS.begin(mDNS_adress)) { // Start the mDNS responder for http://thermostat.local | |
Serial.println(F("Error setting up MDNS responder!")); | |
} else { | |
Serial.println(F("mDNS responder started")); | |
// Print the mDNS address | |
Serial.print(F("mDNS Address: http://")); | |
Serial.print(mDNS_adress); | |
Serial.println(F(".local")); | |
// Add service to mDNS-SD | |
MDNS.addService("http", "tcp", 80); | |
} | |
// Initialize WebSocket | |
ws.onEvent(onWsEvent); | |
server.addHandler(&ws); | |
// Serve the HTML page | |
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { | |
request->send_P(200, "text/html", index_html); | |
}); | |
// Serve the HTML page for mdns overview page from mdns scan | |
server.on("/bulb.html", HTTP_GET, [](AsyncWebServerRequest *request) { | |
request->send_P(200, "text/html", index_html); | |
}); | |
// Handle Not Found | |
server.onNotFound([](AsyncWebServerRequest *request) { | |
request->send(404, "text/html", F("<h2>Page Not Found!</h2><p>Go back to the <a href='/'>homepage</a>.</p>")); | |
}); | |
////////////////////////////////////////////////////////////// | |
// looks like next time is needed for Telegram notifications | |
Serial.print(F("Retrieving time: ")); | |
configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // Improved NTP server configuration with a 2-hour time offset | |
time_t now = time(nullptr); | |
while (now < 24 * 3600) { | |
Serial.print("."); | |
delay(1000); | |
now = time(nullptr); | |
} | |
////////////////////////////////////////// | |
// Set the timezone and DST rules | |
////////////////////////////////////////// | |
// Set the timezone and DST rules for Central European Time (CET) with DST | |
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); | |
// Set the timezone and DST rules for Eastern Standard Time (EST) with DST | |
//setenv("TZ", "EST5EDT,M3.2.0,M11.1.0", 1); | |
// Set the timezone and DST rules for Australian Eastern Standard Time (AEST) with DST | |
//setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1); | |
// Set the timezone and DST rules for Japan Standard Time (JST) with no DST | |
//setenv("TZ", "JST-9", 1); | |
// Set the timezone and DST rules for Indian Standard Time (IST) with no DST | |
//setenv("TZ", "IST-5:30", 1); | |
// Set the timezone and DST rules for Brasilia Time (BRT) with no DST | |
//setenv("TZ", "BRT3", 1); | |
// Set the timezone and DST rules for Central Standard Time (CST) with DST | |
//setenv("TZ", "CST6CDT,M3.2.0,M11.1.0", 1); | |
tzset(); // Update the timezone settings | |
// Convert the time to a struct tm | |
// struct tm *timeinfo; | |
timeinfo = localtime(&now); | |
// Print the time in human-readable format | |
Serial.println(); | |
Serial.print(F("Current time: ")); | |
Serial.print(asctime(timeinfo)); // asctime() converts the time to a string in the format: Day Mon Date Hours:Minutes:Seconds Year\n | |
// For more control over formatting, use strftime() instead: | |
char buffer[80]; | |
strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); | |
Serial.print(F("Formatted time: ")); | |
Serial.println(buffer); | |
////////////////////////////////////////////////////////////// | |
// Print the last reset reason | |
printResetReason(); | |
// Menu button in send area | |
Serial.println(F("send bot bottom menu button")); | |
bot.setMyCommands(commands); | |
// Menu button in send area | |
// static persistent menu at bottom ? | |
// String bottomkeyboardJson = "[[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]]"; | |
// Define the JSON keyboard layout as a constant character array in program memory | |
// const char bottomkeyboardJson[] PROGMEM = R"JSON([[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]])JSON"; | |
// bot.sendMessageWithReplyKeyboard(CHAT_ID, "Create Static Menu", "", bottomkeyboardJson, true); | |
bool resizeKeyboard = true; | |
bool oneTimeKeyboard = false; | |
bool forceReply = false; | |
bot.sendMessageWithReplyKeyboard(CHAT_ID, "Create Static Menu", "", bottomkeyboardJson, resizeKeyboard, oneTimeKeyboard, forceReply); | |
Serial.println(F("send bottom bot menu ")); | |
Serial.println(F("send bot start info")); | |
String message = (F("Thermostat started \n")); | |
message += F("WiFi Network: "); | |
message += String(ssid); | |
message += F("\n"); | |
message += F("Local URL: http://"); | |
message += String(mDNS_adress); | |
message += F(".local\n"); | |
message += F("Local IP: http://"); | |
message += WiFi.localIP().toString(); | |
message += F("\n"); | |
message += F("External IP: "); | |
message += externalIP; | |
message += F("\nReset reason "); | |
message += resetReasonStr; | |
message += F(" "); | |
message += asctime(timeinfo); | |
message += F("\n"); | |
bot.sendMessage(CHAT_ID, message.c_str(), ""); | |
Serial.println(F("send message area bot menu")); | |
bot.sendMessageWithInlineKeyboard(CHAT_ID, F("Thermostat Control\nhttps://t.me/s/Luberth_Dijkman or https://t.me/Arduino_ESP8266_ESP32"), "", keyboardJson); | |
Serial.println(F("send bot temp info")); | |
// Assuming temperatureSetpoint is a float | |
// Assuming currentTemperature holds the current temperature | |
bot.sendMessage(CHAT_ID, asctime(timeinfo), ""); | |
int freeContStack = 0; //ESP.getFreeContStack(); | |
int freeHeap = 0; //ESP.getFreeHeap(); | |
message = F("Setpoint: "); | |
message += String(temperatureSetpoint, 1); | |
message += F("°C, Current Temp: "); | |
message += String(sensors.getTempCByIndex(0), 1); | |
message += F("°C\n Stack: "); | |
message += String(freeContStack); | |
message += F(" bytes, Heap: "); | |
message += String(freeHeap); | |
message += F(" bytes"); | |
bot.sendMessage(CHAT_ID, message.c_str(), ""); | |
printResetReason(); // Print the last reset reason | |
buzzer(); // make some noise | |
Serial.println(F("server begin")); | |
server.begin(); | |
} //end setup | |
void buzzer() { | |
tone(BUZZER_PIN, 1000, 250); // Play the first tone (1000 Hz for 250 milliseconds) | |
delay(250 + 10); // Wait for the 250 of the first tone plus a little extra to ensure it's fully played | |
tone(BUZZER_PIN, 2000, 250); // Play the second tone (2000 Hz for 250 milliseconds) | |
delay(250 + 10); // Wait for the 250 of the second tone plus a little extra | |
} | |
// Scan local network for other ESP mDNS devices | |
void browseService(const char *service, const char *proto) { | |
Serial.println(""); | |
//Serial.printf("Scan _%s._%s.local. ... ", service, proto); | |
Serial.printf("Scan mDNS... "); //_%s._%s.local. ... ", service, proto); | |
int n = MDNS.queryService(service, proto); // Query mDNS service | |
if (n == 0) { | |
Serial.print(F("\033[31m")); //red | |
Serial.println(F("\nDamn, no services found\n Flash more Devices\n And give each a Unique mDNS name in Setup tab Custom")); | |
Serial.print(F("\033[0m")); // Reset color | |
// Now send 'telegramMessage' via your Telegram bot | |
bot.sendMessage(CHAT_ID, F("Damn, no other ESP mDNS devices found\n Flash more ESP Devices\nhttps://ldijkman.github.io/async-esp-fs-webserver/"), ""); | |
} else { | |
Serial.print(n); | |
Serial.println(F(" service(s) found")); | |
/* | |
// Create a JSON array to hold service details | |
DynamicJsonDocument doc(1024); | |
JsonArray services = doc.to<JsonArray>(); | |
*/ | |
String telegramMessage = ""; | |
for (int i = 0; i < n; ++i) { | |
// Print details for each service found | |
// added http so that it is clickable in webserial monitor https://ldijkman.github.io/async-esp-fs-webserver/WebSerialMonitor.html | |
// Obtain the hostname and convert it to lowercase uppercase no clickable link in webserial monitor | |
// https://github.com/xtermjs/xterm.js/issues/4964 | |
// Create a String object from the MDNS hostname and convert it to lowercase | |
String hostnameLower = MDNS.hostname(i); // Obtain the hostname as a String | |
hostnameLower.toLowerCase(); // Convert the hostname to lowercase | |
// Now print the details, using the lowercase hostname | |
//Serial.printf(" %d: http://%s - http://%s port:%d\n", i + 1, hostnameLower.c_str(), MDNS.IP(i).toString().c_str(), MDNS.port(i)); | |
Serial.print("\033[32m"); // Set green color (other color codes available) | |
Serial.printf(" %d: http://%s - http://%s\n", i + 1, hostnameLower.c_str(), MDNS.IP(i).toString().c_str()); | |
Serial.print("\033[0m"); // Reset color | |
// Serial.printf(" %d: http://%s - http://%s port:%d\n", i + 1, (MDNS.hostname(i).c_str()).toLowerCase(), MDNS.IP(i).toString().c_str(), MDNS.port(i)); | |
// make ip clickable weblink addon doenst do :port | |
// http://Living.local uppercase L does not work | |
// https://github.com/xtermjs/xterm.js/issues/4964 | |
// Assuming you have a function to send a message via Telegram | |
// and a String variable 'telegramMessage' to accumulate the message content | |
telegramMessage += String(i + 1) + ": http://" + hostnameLower + " - http://" + MDNS.IP(i).toString() + "\n"; | |
} | |
// Now send 'telegramMessage' via your Telegram bot | |
if (telegramMessage != ""){ | |
bot.sendMessage(CHAT_ID, telegramMessage, ""); | |
} | |
telegramMessage = ""; | |
/* | |
// Add service details to the JSON array | |
JsonObject serviceObj = services.createNestedObject(); | |
serviceObj["mdnsname"] = MDNS.hostname(i); | |
serviceObj["ip"] = MDNS.IP(i).toString(); | |
serviceObj["port"] = MDNS.port(i); | |
} | |
// Serialize the JSON array to a string | |
String message; | |
serializeJson(doc, message); | |
// Send the JSON string to all connected WebSocket clients | |
ws.textAll(message.c_str()); | |
*/ | |
} | |
String message = "Scan Done"; //F("Scan Done Stack: ") + String(ESP.getFreeContStack()) + F(" bytes, Heap: ") + String(ESP.getFreeHeap()) + F(" bytes"); | |
bot.sendMessage(CHAT_ID, message, ""); | |
message = ""; | |
Serial.println(); | |
} | |
void scanForWiFiNetworks() { | |
Serial.println("Scanning for WiFi networks..."); | |
int n = WiFi.scanNetworks(); | |
Serial.println("Scan done."); | |
if (n == 0) { | |
Serial.println("No networks found."); | |
} else { | |
Serial.print(n); | |
Serial.println(" networks found"); | |
String message = "WiFi Networks Scan Result:\n"; | |
for (int i = 0; i < n; ++i) { | |
// Append WiFi SSID and RSSI to the message | |
message += String(i + 1) + ": "; | |
message += WiFi.SSID(i); | |
message += " ("; | |
message += WiFi.RSSI(i); | |
message += " dBm)"; | |
message += (WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*"; | |
message += "\n"; | |
} | |
// Optionally, add more information or another message to be sent after the list | |
message += "\nScan complete!"; | |
// Send the message via your Telegram bot | |
bot.sendMessage(CHAT_ID, message, ""); | |
} | |
} | |
void handleNewMessages(int numNewMessages) { | |
for (int i = 0; i < numNewMessages; i++) { | |
String from_name = bot.messages[i].from_name; | |
if (from_name == "") | |
from_name = "Guest"; | |
Serial.print("\nhi "); | |
Serial.println(from_name); | |
Serial.print("Call back button pressed by: "); | |
Serial.println(bot.messages[i].from_id); | |
Serial.println(bot.messages[i].from_name); | |
Serial.print("Data on the button: "); | |
Serial.println(bot.messages[i].text); | |
Serial.println(""); | |
Serial.print(F("bot.messages[i].text ")); | |
Serial.println(bot.messages[i].text); | |
Serial.print(F("last_sent_message_id ")); | |
Serial.println(bot.last_sent_message_id); | |
Serial.print(F("last_message recieved ")); | |
Serial.println(bot.last_message_received); | |
///* | |
if (bot.messages[i].web_app_data != "") { | |
if (bot.messages[i].web_app_data != "") { | |
// Process the web_app_data | |
Serial.println(F("XXXXXXXXXXXXXXXXXXXXX Web App Data: ")); | |
// Parse the JSON data | |
DynamicJsonDocument doc(2048); // Adjust the size according to your needs | |
//deserializeJson(doc, bot.messages[i].web_app_data); | |
DeserializationError error = deserializeJson(doc, bot.messages[i].web_app_data); | |
if (error) { | |
Serial.print(F("deserializeJson() failed: ")); | |
Serial.println(error.c_str()); | |
return; // Stop execution if parsing fails | |
} | |
JsonArray arr = doc.as<JsonArray>(); | |
/* | |
* | |
* if json string is not correct next brings wdt reset boot loop | |
* moved sort to html javascript schedule html just before web app send data | |
* | |
// Simple Bubble Sort for "on" times | |
bool swapped; | |
do { | |
swapped = false; | |
for (int j = 0; j < arr.size() - 1; j++) { | |
if (arr[j]["on"] > arr[j + 1]["on"]) { | |
// Swap | |
JsonObject temp = arr.createNestedObject(); | |
temp.set(arr[j]); | |
arr[j].set(arr[j + 1]); | |
arr[j + 1].set(temp); | |
// Remove temporary object at the end | |
arr.remove(arr.size() - 1); | |
swapped = true; | |
} | |
} | |
} while (swapped); | |
*/ | |
// Initialize a String to build the message | |
String message = "Schedule:\n"; | |
// Process and build message from sorted JSON array | |
for (JsonVariant v : arr) { | |
int onTime = v["on"]; | |
int offTime = v["off"]; | |
// Convert minutes into hours and minutes format | |
String onHours = String(onTime / 60); | |
String onMinutes = String(onTime % 60); | |
if (onMinutes.length() < 2) onMinutes = "0" + onMinutes; // Add leading zero if needed | |
String offHours = String(offTime / 60); | |
String offMinutes = String(offTime % 60); | |
if (offMinutes.length() < 2) offMinutes = "0" + offMinutes; // Add leading zero if needed | |
// Append the current on/off pair to the message | |
message += "On: " + onHours + ":" + onMinutes + ", Off: " + offHours + ":" + offMinutes + "\n"; | |
Serial.print(F("On Time: ")); | |
Serial.print(onHours + ":" + onMinutes); | |
Serial.print(F(", Off Time: ")); | |
Serial.println(offHours + ":" + offMinutes); | |
} | |
// Send messages back to the chat | |
String chat_id = bot.messages[i].chat_id; | |
bot.sendMessage(chat_id, bot.messages[i].web_app_data, ""); // json | |
bot.sendMessage(chat_id, "Received data from the web app!", ""); // Confirmation message | |
bot.sendMessage(chat_id, message, ""); // Send the organized schedule message | |
Serial.println(F("XXXXXXXXXXXXXXXXXXXXX Data processed.")); | |
} | |
} | |
// */ | |
// If the type is a "callback_query", a inline keyboard button was pressed | |
if (bot.messages[i].type == F("callback_query")) { | |
String text = bot.messages[i].text; | |
// Extract callback_query_id from the callback query | |
String callbackQueryId = bot.messages[i].query_id; | |
// Optionally, extract the message text sent with the callback query | |
// Note: This is the data associated with the button that was pressed, not a text message from the user | |
// String callbackData = bot.messages[i].query_data; | |
// 'struct telegramMessage' has no member named 'query_data'; did you mean 'query_id'? | |
String callbackData = bot.messages[i].text; | |
Serial.print(F("Callback Query ID: ")); | |
Serial.println(callbackQueryId); | |
Serial.print(F("Callback Query Data: ")); | |
Serial.println(callbackData); | |
// stop the loading spinner on the clicked button in chat message area | |
// Here you can process the callback query | |
// For example, answer the callback query to stop the loading spinner on the button | |
String notificationText = F("Button pressed!"); // Notification text to send to user (optional) | |
//bool showAlert = true; // Choose whether to show an alert box or a toast notification | |
bool showAlert = false; // Choose whether to show an alert box or a toast notification | |
// this stops the inline keyboard button click spinner animation | |
bot.answerCallbackQuery(callbackQueryId, notificationText, showAlert); | |
Serial.print("Call back button pressed by: "); | |
Serial.println(bot.messages[i].from_id); | |
Serial.println(bot.messages[i].from_name); | |
Serial.print("Data on the button: "); | |
Serial.println(bot.messages[i].text); | |
bot.sendMessage(bot.messages[i].from_id, bot.messages[i].text, ""); | |
Serial.print(F("Call back button pressed with text: ")); | |
Serial.println(text); | |
// ws.textAll(F("Telegram button Recieved ") + text); | |
if (text.startsWith("/schedule ")) { | |
// Extract time and duration from the command | |
text.remove(0, 10); // Remove the command part | |
int splitIndex = text.indexOf(' '); | |
String time = text.substring(0, splitIndex); | |
String duration = text.substring(splitIndex + 1); | |
// Here you can handle the scheduling logic based on extracted 'time' and 'duration' | |
// For demonstration, just echo back the scheduled time and duration | |
String reply = "Task scheduled at " + time + " for " + duration + " minutes."; | |
bot.sendMessage(CHAT_ID, reply, ""); | |
} | |
if (text == F("ON")) { | |
manual = 1; | |
digitalWrite(LED_PIN, HIGH); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
turnOnLED(); | |
//turnOffLED(); | |
#endif | |
bot.sendMessage(CHAT_ID, F("LED ON"), ""); | |
} else if (text == F("OFF")) { | |
digitalWrite(LED_PIN, LOW); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
//turnOnLED(); | |
turnOffLED(); | |
#endif | |
manual = 0; | |
bot.sendMessage(CHAT_ID, F("LED OFF"), ""); | |
} else if (text.startsWith("TIME")) { | |
manual = 1; | |
text.replace("TIME", ""); | |
timeRequested = text.toInt(); | |
digitalWrite(LED_PIN, HIGH); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
turnOnLED(); | |
//turnOffLED(); | |
#endif | |
String message = F("LED ON, off delay "); | |
message += String(timeRequested); | |
message += F(" Minutes"); | |
bot.sendMessage(CHAT_ID, message, ""); | |
lightTimerActive = true; | |
lightTimerExpires = millis() + (timeRequested * 1000 * 60); | |
} else if (text.startsWith("TEMP")) { | |
text.replace("TEMP", ""); | |
temperatureSetpoint = text.toInt(); | |
String message = F("Setpoint: "); | |
message += String(temperatureSetpoint, 1); | |
message += F("°C, Current Temp: "); | |
message += String(sensors.getTempCByIndex(0), 1); | |
message += F("°C\n Stack: "); | |
// message += String(ESP.getFreeContStack()); | |
message += F(" bytes, Heap: "); | |
// message += String(ESP.getFreeHeap()); | |
message += F(" bytes"); | |
bot.sendMessage(CHAT_ID, message.c_str(), ""); | |
} | |
if (text == F("/scan")) { | |
bot.sendMessage(CHAT_ID, F("Scan local network for other ESP mDNS device"), ""); | |
browseService("http", "tcp"); // find other mdns devices in network | |
} | |
if (text == F("/buzzer")) { | |
bot.sendMessage(CHAT_ID, F("Buzzer Test"), ""); | |
buzzer(); | |
} | |
if (text == F("reboot")) { | |
// /* keeps rebooting | |
if (millis() > 30000) { // Check if 30 seconds have passed otherwise do not reboot | |
// old messages keeps it rebooting | |
bot.sendMessage(CHAT_ID, F("Rebooting now...\n Please Wait for a new Menu"), ""); | |
delay(1000); // Short delay to ensure message delivery | |
RestartTriggered = true; // flag used in loop to restart ESP | |
} else { | |
bot.sendMessage(CHAT_ID, F("Reboot blocked for 30sec after boot"), ""); | |
} | |
//*/ | |
} | |
} else { | |
String chat_id = String(bot.messages[i].chat_id); | |
String text = bot.messages[i].text; | |
text.toLowerCase(); | |
if (text == F("/options") || text == F("/menu") || text == F("menu")) { | |
// static persistent menu at bottom ? | |
// String bottomkeyboardJson = "[[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]]"; | |
// Define the JSON keyboard layout as a constant character array in program memory | |
// const char bottomkeyboardJson[] PROGMEM = R"JSON([[\"Menu\", \"On\",\"OFF\",\"Buzzer\"],[\"Reboot\"]])JSON"; | |
// bot.sendMessageWithReplyKeyboard(CHAT_ID, "Create Static Menu", "", bottomkeyboardJson, true); | |
bool resizeKeyboard = true; | |
bool oneTimeKeyboard = false; | |
bool forceReply = false; | |
bot.sendMessageWithReplyKeyboard(CHAT_ID, "Create Static Menu", "", bottomkeyboardJson, resizeKeyboard, oneTimeKeyboard, forceReply); | |
Serial.println(F("send bottom bot menu ")); | |
// Keyboard Json is an array of arrays. | |
// The size of the main array is how many row options the keyboard has | |
// The size of the sub arrays is how many coloums that row has | |
// "The Text" property is what shows up in the keyboard | |
// The "callback_data" property is the text that gets sent when pressed | |
String message = "Thermostat \n"; | |
message += "WiFi Network: " + String(ssid) + "\n"; | |
message += "Local URL: http://" + String(mDNS_adress) + ".local\n"; | |
message += "Local IP: http://" + WiFi.localIP().toString() + "\n"; | |
message += "External IP: " + externalIP + "\nReset reason " + resetReasonStr + "\n"; | |
bot.sendMessage(chat_id, message.c_str(), ""); | |
bot.sendMessageWithInlineKeyboard(chat_id, "hi "+bot.messages[i].from_name, "Markdown", keyboardJson); | |
// Assuming temperatureSetpoint is a float | |
// Assuming currentTemperature holds the current temperature | |
message = "Setpoint: " + String(temperatureSetpoint, 1) + "°C, Current Temp: " + String(sensors.getTempCByIndex(0), 1) + "°C"; // 1 decimal place for float | |
bot.sendMessage(chat_id, message.c_str(), ""); | |
} | |
// When a user first uses a bot they will send a "/start" command | |
// So this is a good place to let the users know what commands are available | |
if (text == F("/start")) { | |
bot.sendMessage(chat_id, F("/options or /menu : returns the inline keyboard\n"), "Markdown"); | |
} | |
if (text.startsWith("/set_")) { // set temp from menu button sendarea | |
text.replace("/set_", ""); | |
temperatureSetpoint = text.toInt(); | |
// Assuming temperatureSetpoint is a float | |
// Assuming currentTemperature holds the current temperature | |
String message = "Setpoint: " + String(temperatureSetpoint, 1) + "°C, Current Temp: " + String(sensors.getTempCByIndex(0), 1) + "°C"; // 1 decimal place for float | |
bot.sendMessage(chat_id, message.c_str(), ""); | |
} | |
if (text == F("/reboot") || text == F("reboot")) { | |
// /* keeps rebooting | |
if (millis() > 30000) { // Check if 30 seconds have passed otherwise do not reboot | |
// old messages keeps it rebooting | |
bot.sendMessage(CHAT_ID, "Rebooting now...\n Please Wait for a new Menu", ""); | |
delay(1000); // Short delay to ensure message delivery | |
RestartTriggered = true; // flag used in loop to restart ESP | |
} else { | |
bot.sendMessage(CHAT_ID, "Reboot blocked for 30sec after boot", ""); | |
} | |
//*/ | |
} | |
if (text == "/scan") { | |
bot.sendMessage(chat_id, F("Scan local network for other ESP mDNS device"), ""); | |
browseService("http", "tcp"); // find other mdns devices in network | |
} | |
if (text == "/buzzer" || text == "buzzer") { | |
bot.sendMessage(chat_id, F("Buzzer test D5"), ""); | |
buzzer(); | |
} | |
if (text == F("on")) { | |
manual = 1; | |
digitalWrite(LED_PIN, HIGH); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
turnOnLED(); | |
//turnOffLED(); | |
#endif | |
bot.sendMessage(CHAT_ID, F("LED ON"), ""); | |
} else if (text == F("off")) { | |
manual = 0; | |
digitalWrite(LED_PIN, LOW); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
//turnOnLED(); | |
turnOffLED(); | |
#endif | |
bot.sendMessage(CHAT_ID, F("LED OFF"), ""); | |
} | |
// used text tolower earlier so Min is min | |
if (text == F("1min") || text == F("5min") || text == F("10min") || text == F("15min") || text == F("30min") || text == F("60min")) { | |
manual = 1; | |
text.replace("min", ""); // Remove the "Min" suffix to isolate the number | |
timeRequested = text.toInt(); // Convert the remaining text to an integer | |
digitalWrite(LED_PIN, HIGH); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
turnOnLED(); | |
//turnOffLED(); | |
#endif | |
String message = F("LED ON, off delay "); | |
message += String(timeRequested); | |
message += F(" Minutes"); | |
bot.sendMessage(CHAT_ID, message, ""); | |
lightTimerActive = true; | |
lightTimerExpires = millis() + (timeRequested * 1000 * 60); | |
} | |
if (text == F("10°") || text == F("15°") || text == F("16°") || text == F("17°") || text == F("18°") || text == F("19°") || text == F("20°") || text == F("21°") || text == F("22°")) { | |
text.replace("°", ""); // Replace the degree symbol with an empty string | |
int tempSetpoint = text.toInt(); // Now convert the text to an integer | |
temperatureSetpoint = tempSetpoint; // Update the temperature setpoint | |
// Construct the message to send back to the user | |
String message = F("Setpoint: "); | |
message += String(temperatureSetpoint, 1); | |
message += F("°C, Current Temp: "); | |
message += String(sensors.getTempCByIndex(0), 1); | |
message += F("°C\n Stack: "); | |
//message += String(ESP.getFreeContStack()); | |
message += F(" bytes, Heap: "); | |
//message += String(ESP.getFreeHeap()); | |
message += F(" bytes"); | |
bot.sendMessage(CHAT_ID, message.c_str(), ""); | |
} | |
if (text == F("time")) { | |
bot.sendMessage(CHAT_ID, asctime(timeinfo), ""); | |
} | |
// used text tolower earlier so Min is min | |
if (text == F("task")) { | |
bot.sendMessage(CHAT_ID, F("Schedule 1 to 4 Daily Repeat\ntask tasknumber_1_to_4 HH:MM duration_in_minutes\nlike\ntask 1 06:30 30\n/list_tasks\n/delete_task"), ""); | |
} | |
if (text.startsWith(F("task "))) { | |
String taskDetails = text.substring(5); | |
taskDetails.trim(); | |
int firstSpaceIndex = taskDetails.indexOf(" "); | |
if (firstSpaceIndex != -1) { | |
String taskNumberStr = taskDetails.substring(0, firstSpaceIndex); | |
int taskNumber = taskNumberStr.toInt(); | |
if (taskNumber >= 1 && taskNumber <= 4) { | |
String timeAndDuration = taskDetails.substring(firstSpaceIndex + 1); | |
timeAndDuration.trim(); | |
int lastSpaceIndex = timeAndDuration.lastIndexOf(" "); | |
if (lastSpaceIndex != -1) { | |
String timePart = timeAndDuration.substring(0, lastSpaceIndex); | |
String durationPart = timeAndDuration.substring(lastSpaceIndex + 1); | |
if (timePart.length() == 5 && durationPart.toInt() > 0) { | |
// Store task details in the corresponding array position | |
tasks[taskNumber - 1].taskNumber = taskNumber; // Task numbers start from 1, array indices from 0 | |
tasks[taskNumber - 1].time = timePart; | |
tasks[taskNumber - 1].duration = durationPart.toInt(); | |
Serial.print(F("Task Number: ")); | |
Serial.println(taskNumber); | |
Serial.print(F("Task Time: ")); | |
Serial.println(timePart); | |
Serial.print(F("Task Duration: ")); | |
Serial.println(durationPart); | |
writeTasksToEEPROM(); | |
// bot.sendMessage(CHAT_ID, F("Task #") + String(taskNumber) + F(" scheduled for ") + timePart + F(" with a duration of ") + durationPart + F(" minutes."), ""); | |
} else { | |
bot.sendMessage(CHAT_ID, F("Invalid time or duration format. Please use 'task # HH:MM duration' format."), ""); | |
} | |
} else { | |
bot.sendMessage(CHAT_ID, F("Invalid format. Please use 'task # HH:MM duration' format."), ""); | |
} | |
} else { | |
bot.sendMessage(CHAT_ID, F("Invalid task number. Please use a number from 1 to 4."), ""); | |
} | |
} else { | |
bot.sendMessage(CHAT_ID, F("Invalid task format. Please use 'task # HH:MM duration' format."), ""); | |
} | |
} | |
// Inside your message handling loop | |
if (text.startsWith(F("/delete_task"))) { | |
int taskNumber = text.substring(12).toInt(); // Extract task number from command | |
if (taskNumber > 0) { | |
deleteTask(taskNumber); | |
// bot.sendMessage(CHAT_ID, F("Task #") + String(taskNumber) + F(" deleted."), ""); | |
} else { | |
bot.sendMessage(CHAT_ID, F("Invalid task number.\n/delete_task1\n/delete_task2\n/delete_task3\n/delete_task4\n/list_tasks"), ""); | |
} | |
} | |
if (text == F("/list_tasks")) { | |
String message = F("Scheduled Tasks:\n"); | |
bool hasTasks = false; | |
for (int j = 0; j < maxTasks; j++) { | |
if (tasks[j].taskNumber > 0) { // Assuming taskNumber > 0 as indicator of a used slot | |
hasTasks = true; | |
// message += F("Task #") + String(tasks[j].taskNumber) + F(": "); | |
// message += F("Time: ") + tasks[j].time + F(", "); | |
// message += F("Duration: ") + String(tasks[j].duration) + F(" minutes\n"); | |
} | |
} | |
if (!hasTasks) { | |
message += F("No tasks scheduled."); | |
} | |
// bot.sendMessage(CHAT_ID, message, ""); | |
} | |
// used text tolower earlier so Min is min | |
if (text == F("info")) { | |
uint32_t chipId = 0; //ESP.getChipId(); | |
uint32_t flashChipId = 0; //ESP.getFlashChipId(); | |
uint32_t flashChipSize = 0; //ESP.getFlashChipSize(); | |
uint32_t flashChipRealSize = 0; //ESP.getFlashChipRealSize(); | |
uint32_t freeSketchSpace = 0; //ESP.getFreeSketchSpace(); | |
uint32_t sketchSize = 0; //ESP.getSketchSize(); | |
uint8_t cpuFreqMHz = 0; //ESP.getCpuFreqMHz(); | |
String sdkVersion = ""; //ESP.getSdkVersion(); | |
uint8_t bootVersion = 0; //ESP.getBootVersion(); | |
// Network Information | |
String ipAddress = WiFi.localIP().toString(); | |
String macAddress = WiFi.macAddress(); | |
String ssid = WiFi.SSID(); | |
int32_t rssi = WiFi.RSSI(); | |
/* | |
FSInfo fs_info; | |
if (FS.info(fs_info)) { | |
uint32_t totalBytes = fs_info.totalBytes; | |
uint32_t usedBytes = fs_info.usedBytes; | |
uint32_t freeBytes = totalBytes - usedBytes; | |
String message = ""; | |
message += "LittleFS Information:\n"; | |
message += "Total Bytes: " + String(totalBytes) + "\n"; | |
message += "Used Bytes: " + String(usedBytes) + "\n"; | |
message += "Free Bytes: " + String(freeBytes) + "\n"; | |
Serial.println(message); | |
} else { | |
Serial.println("Failed to retrieve LittleFS Information."); | |
} | |
*/ | |
// mDNS Information | |
String mdnsInfo = F("http://"); | |
mdnsInfo += String(mDNS_adress); | |
mdnsInfo += F(".local"); | |
// Router IP Address | |
IPAddress gatewayIP = WiFi.gatewayIP(); | |
String routerIP = gatewayIP.toString(); | |
String message = F("http://paypal.me/LDijkman\n\n"); | |
message += "Chip Information:\nModel: "; | |
esp_chip_info_t chip_info; | |
esp_chip_info(&chip_info); | |
switch(chip_info.model) { | |
case CHIP_ESP32: | |
message += "ESP32"; | |
break; | |
case CHIP_ESP32S2: | |
message += "ESP32-S2"; | |
break; | |
case CHIP_ESP32S3: | |
message += "ESP32-S3"; | |
break; | |
case CHIP_ESP32C3: | |
message += "ESP32-C3"; | |
break; | |
default: | |
message += "Unknown"; | |
} | |
// Add additional chip information | |
message += "\nCores: " + String(chip_info.cores); | |
message += "\nRevision: " + String(chip_info.revision); | |
// Check for PSRAM availability and add its information to the message | |
if (psramFound()) { | |
message += F("\n\nPSRAM is found.\n"); | |
message += F("PSRAM size: "); | |
message += String(ESP.getPsramSize()); | |
message += F(" bytes\n"); | |
} else { | |
message += F("\n\nPSRAM is not available.\n"); | |
} | |
message += UpTime(); // Append uptime info to your message string, message return from function String uptime, not global | |
float temperature = temperatureRead(); | |
message += F("\n\nSP Internal Temperature: "); | |
message += String(temperature); | |
message += F(" °C\n"); | |
message += "free heap: " + String(esp_get_free_heap_size()) + "\n"; | |
heap_caps_print_heap_info(MALLOC_CAP_DEFAULT); | |
multi_heap_info_t info; | |
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL); | |
message += F("Internal Heap Size: "); | |
message += String(info.total_free_bytes + info.total_allocated_bytes); | |
message += F(" bytes\n"); | |
message += F("Internal Free Heap: "); | |
message += String(info.total_free_bytes); | |
message += F(" bytes\n"); | |
// message += F("Chip ID: ") + String(chipId) + F("\n"); | |
// message += F("Flash Chip ID: ") + String(flashChipId) + F("\n"); | |
// message += F("Flash Chip Size: ") + String(flashChipSize) + F(" bytes\n"); | |
// message += F("Flash Chip Real Size: ") + String(flashChipRealSize) + F(" bytes\n"); | |
// message += F("Free Sketch Space: ") + String(freeSketchSpace) + F(" bytes\n"); | |
// message += F("Sketch Size: ") + String(sketchSize) + F(" bytes\n"); | |
// message += F("CPU Frequency: ") + String(cpuFreqMHz) + F(" MHz\n"); | |
// message += F("SDK Version: ") + sdkVersion + F("\n"); | |
// message += F("Boot Version: ") + String(bootVersion) + F("\n\n"); | |
// Chip ID (MAC address in this case as a stand-in for a unique chip identifier) | |
uint8_t baseMac[6]; | |
esp_read_mac(baseMac, ESP_MAC_WIFI_STA); | |
char baseMacChr[18] = { 0 }; | |
sprintf(baseMacChr, "%02X:%02X:%02X:%02X:%02X:%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]); | |
message += F("Chip ID: "); | |
message += String(baseMacChr) + F("\n"); | |
// Flash Chip Size | |
flashChipSize = ESP.getFlashChipSize(); | |
message += F("Flash Chip Size: "); | |
message += String(flashChipSize); | |
message += F(" bytes\n"); | |
// Flash Chip Real Size | |
flashChipRealSize = ESP.getFlashChipSize(); | |
message += F("Flash Chip Real Size: "); | |
message += String(flashChipRealSize); | |
message += F(" bytes\n"); | |
// Free Sketch Space | |
freeSketchSpace = ESP.getFreeSketchSpace(); | |
message += F("Free Sketch Space: "); | |
message += String(freeSketchSpace); | |
message += F(" bytes\n"); | |
// Sketch Size | |
sketchSize = ESP.getSketchSize(); | |
message += F("Sketch Size: "); | |
message += String(sketchSize); | |
message += F(" bytes\n"); | |
// CPU Frequency | |
cpuFreqMHz = ESP.getCpuFreqMHz(); | |
message += F("CPU Frequency: "); | |
message += String(cpuFreqMHz); | |
message += F(" MHz\n"); | |
// SDK Version | |
sdkVersion = esp_get_idf_version(); | |
message += F("SDK Version: "); | |
message += String(sdkVersion); | |
message += F("\n\n"); | |
message += F("Wi-Fi Mode: "); | |
switch (WiFi.getMode()) { | |
case WIFI_OFF: message += F("OFF"); break; | |
case WIFI_STA: message += F("STA"); break; | |
case WIFI_AP: message += F("AP"); break; | |
case WIFI_AP_STA: message += F("AP+STA"); break; | |
default: message += F("UNKNOWN"); break; | |
} | |
message += F("\n"); | |
message += F("Wi-Fi Channel: "); | |
message += String(WiFi.channel()); | |
message += F("\n"); | |
message += F("IP Address: http://"); | |
message += ipAddress; | |
message += F("\n"); | |
message += F("MAC Address: "); | |
message += macAddress; | |
message += ("\n"); | |
message += F("mDNS: "); | |
message += mdnsInfo; | |
message += F("\n"); | |
message += F("SSID: "); | |
message += ssid; | |
message += F("\n"); | |
message += F("RSSI: "); | |
message += String(rssi); | |
message += F(" dBm\n"); | |
message += F("External IP: http://"); | |
message += externalIP; | |
message += F("\n"); | |
message += F("Router IP: http://"); | |
message += routerIP; | |
message += F("\n"); | |
message += F("Gateway IP: http://"); // Directly appending a string literal | |
message += String(WiFi.gatewayIP().toString()); // Appending the IP address as a String | |
message += F("\n"); // Appending a newline character | |
message += F("DNS Server 1: "); | |
message += WiFi.dnsIP(0).toString(); | |
message += F("\n"); | |
message += F("DNS Server 2: "); | |
message += WiFi.dnsIP(1).toString(); | |
message += F("\n\n"); | |
// message += F("LittleFS Total Bytes: ") + String(totalBytes) + F("\n"); | |
// message += F("LittleFS Used Bytes: ") + String(usedBytes) + F("\n"); | |
// message += F("LittleFS Free Bytes: ") + String(freeBytes) + F("\n\n"); | |
message += F("a Penny for Sharing my Thoughts?!"); | |
bot.sendMessage(CHAT_ID, message); | |
bot.sendMessage(CHAT_ID, F("/list_tasks")); | |
bot.sendMessage(CHAT_ID, F("Start Scan for other mDNS in local network")); | |
browseService("http", "tcp"); // find other mdns devices in network | |
bot.sendMessage(CHAT_ID, F("https://t.me/Arduino_ESP8266_ESP32")); | |
bot.sendMessage(CHAT_ID, F("https://t.me/Luberth_Dijkman")); | |
bot.sendMessage(CHAT_ID, F("scanForWiFiNetworks();")); | |
scanForWiFiNetworks(); | |
bot.sendMessage(CHAT_ID, F("https://t.me/Arduino_ESP8266_ESP32/1/47")); | |
} | |
} | |
} | |
} | |
void checkAndActivateTasks() { | |
bool isAnyTaskActive = false; // Flag to track if any task is currently active | |
time_t now = time(nullptr); | |
struct tm *timeinfo = localtime(&now); | |
unsigned long currentTime = timeinfo->tm_hour * 60 + timeinfo->tm_min; // Convert current time to minutes from midnight | |
for (int i = 0; i < 4; i++) { // Correct loop condition to iterate correctly through tasks | |
if (tasks[i].taskNumber != 0) { // Skip empty tasks | |
int taskHour = tasks[i].time.substring(0, 2).toInt(); | |
int taskMinute = tasks[i].time.substring(3, 5).toInt(); | |
int duration = tasks[i].duration; | |
// Calculate the task's start and end time | |
unsigned long taskStartTime = taskHour * 60 + taskMinute; | |
unsigned long taskEndTime = taskStartTime + duration; | |
// Check if current time is within the task's active period | |
if (currentTime >= taskStartTime && currentTime < taskEndTime) { | |
isAnyTaskActive = true; // Mark that at least one task is active | |
break; // Exit the loop as we found an active task | |
} | |
} | |
} | |
bool currentLedState = isAnyTaskActive ? LOW : HIGH; | |
if (manual == 0) { // manual on switch or off delay time on override tasks | |
// Set the LED (or relay) state based on whether any task is active | |
if (isAnyTaskActive) { | |
digitalWrite(LED_PIN, HIGH); // Activate relay | |
} else { | |
digitalWrite(LED_PIN, LOW); // Deactivate relay | |
} | |
} | |
// Send a message through the bot indicating the state change | |
// if (currentLedState == LOW) { | |
// bot.sendMessage(CHAT_ID, F("LED_PIN LOW: Task Active"), ""); | |
// } else { | |
// bot.sendMessage(CHAT_ID, F("LED_PIN HIGH: No Active Task"), ""); | |
// } | |
} | |
int delayBetweenChecks = 1000; | |
unsigned long lastTimeChecked; //last time messages' scan has been done | |
static unsigned long lastMillis = 0; | |
void loop() { | |
sensors.requestTemperatures(); | |
float temperature = sensors.getTempCByIndex(0); | |
if (temperature < 10 || temperature > 40) { | |
buzzer(); | |
} | |
if (millis() > lastTimeChecked + delayBetweenChecks) { | |
// checks all scheduled tasks against the current time | |
// and activates a relay if the current time is within a task's active period | |
checkAndActivateTasks(); | |
ws.cleanupClients(); | |
// getUpdates returns 1 if there is a new message from Telegram | |
int numNewMessages = bot.getUpdates(bot.last_message_received + 1); | |
//Serial.println(bot.getUpdates(bot.last_message_received)); | |
//Serial.println(bot.getUpdates(bot.last_sent_message_id)); | |
//String message=F("numNewMessages "); | |
//message += String(numNewMessages); | |
//Serial.println(message); | |
while (numNewMessages) { | |
// Serial.println(F("got response")); | |
handleNewMessages(numNewMessages); | |
numNewMessages = bot.getUpdates(bot.last_message_received + 1); | |
} | |
lastTimeChecked = millis(); | |
if (lightTimerActive && millis() > lightTimerExpires) { | |
manual = 0; | |
lightTimerActive = false; | |
digitalWrite(LED_PIN, LOW); | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 | |
//turnOnLED(); | |
turnOffLED(); | |
#endif | |
// Create a string to hold the message, including the time in seconds | |
String message = F("LED OFF delay Time Expired. \nTime was set for: "); | |
message += String(timeRequested); // Add the time to the message | |
message += F(" Minutes."); | |
bot.sendMessage(CHAT_ID, message, ""); // Send the message | |
} | |
} | |
if (RestartTriggered == true) { | |
buzzer(); | |
ESP.restart(); | |
} | |
if (millis() - lastMillis > 5000) { // delay without Delay(), do it every 5 seconds | |
lastMillis = millis(); // | |
// Get free stack and heap memory | |
freeStack = 0; //ESP.getFreeContStack(); | |
freeHeap = 0; //ESP.getFreeHeap(); | |
// Print memory info | |
// Serial.print(F("Free stack: ")); | |
// Serial.print(freeStack); | |
// Serial.println(" bytes"); | |
// Serial.print(F("Free heap: ")); | |
// Serial.print(freeHeap); | |
// Serial.println(F(" bytes")); | |
sensors.requestTemperatures(); | |
float temperature = sensors.getTempCByIndex(0); | |
String tempString = String(temperature, 1); | |
// Print the IP address | |
/* Serial.print(F("IP Address: ")); | |
Serial.println(WiFi.localIP()); | |
Serial.print(F("mDNS Address: ")); | |
Serial.print(F("http://")); | |
Serial.print(mDNS_adress); | |
Serial.println(F(".local")); | |
*/ | |
// Create a string containing both the local IP address and the mDNS URL, separated by a comma for clarity. | |
// String ip = F("IP: http://") + WiFi.localIP().toString(); | |
// ws.textAll(ip.c_str()); // Send the string to all connected WebSocket clients. | |
// Create a string containing both the local IP address and the mDNS URL, separated by a comma for clarity. | |
// String mDNS = F("mDNS: http://") + String(mDNS_adress) + F(".local"); // Corrected to use 'mDNS_adress' | |
// ws.textAll(mDNS.c_str()); // Send the string to all connected WebSocket clients. | |
// Serial.print(F("Current temperature: ")); | |
// Serial.print(tempString); | |
// Serial.println(" °C"); | |
// Serial.print(F("Current setpoint: ")); | |
// Serial.print(temperatureSetpoint); | |
// Serial.println(" °C\n"); | |
// Implement hysteresis control | |
if (!relayState && temperature < (temperatureSetpoint - hysteresisMargin)) { | |
digitalWrite(relayPin, HIGH); // Activate the relay if temperature goes below the lower threshold | |
relayState = true; // Update relay state | |
} else if (relayState && temperature > (temperatureSetpoint + hysteresisMargin)) { | |
digitalWrite(relayPin, LOW); // Deactivate the relay if temperature goes above the upper threshold | |
relayState = false; // Update relay state | |
} | |
// No change if the temperature is within the hysteresis band | |
// Send the temperature to all connected clients | |
String message = ""; //F("temperature:") + tempString; | |
ws.textAll(message.c_str()); | |
// Inside your loop(), replace the relay state message construction and sending part with: | |
// String relayStateMessage = F("relays:") + String(relayState ? "1" : "0"); // Converts boolean to "1" or "0" | |
// String relayStateMessage = "relays:" + String(relayState ? "ON" : "OFF"); // Converts boolean to "1" or "0" | |
// ws.textAll(relayStateMessage.c_str()); | |
// Construct the setpoint message | |
// String setpointMessage = F("setpoint:") + String(temperatureSetpoint); | |
// ws.textAll(setpointMessage.c_str()); | |
} | |
// MDNS.update(); // Keep the mDNS responder updated only needed for ESP8266 Version | |
unsigned long currentMillis = millis(); | |
if (currentMillis < previousMillis) { // Check for rollover uptime longer as max. millis() = +/-49 days | |
rolloverCounter++; // Increment the rollover counter | |
} | |
} // end loop | |
#ifdef CONFIG_IDF_TARGET_ESP32S3 // This code will only be compiled for ESP32S3 | |
void turnOnLED() { | |
led = CRGB::White; // Turn on the LED (white color) | |
FastLED.show(); | |
} | |
void turnOffLED() { | |
led = CRGB::Black; // Turn off the LED | |
FastLED.show(); | |
} | |
#endif | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment