/*********
Rui Santos
Complete instructions at https://RandomNerdTutorials.com/esp32-websocket-server-sensor/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*********/
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "SPIFFS.h"
#include <Arduino_JSON.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SD.h> // Include the SD card library
#include <NTPClient.h>
#include <WiFiUdp.h>
/**
* @brief Replace with your network credentials
*/
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// NTP
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 7200; // 2 hours offset for GMT+2 timezone
const int daylightOffset_sec = 0;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, gmtOffset_sec, daylightOffset_sec);
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// Create a WebSocket object
AsyncWebSocket ws("/ws");
// Json Variable to Hold Sensor Readings
JSONVar readings;
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 30000;
// Data wire is connected to GPIO 4
#define ONE_WIRE_BUS 21
// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature sensor
DallasTemperature sensors(&oneWire);
// SD card pin configuration
#define SD_CS_PIN 5
// File to store sensor readings
File dataFile;
/**
* @brief Initialize DS18B20 temperature sensor
*/
void initDS18B20(){
sensors.begin();
}
/**
* @brief Initialize SD card
*/
void initSDCard() {
if (!SD.begin(SD_CS_PIN)) {
Serial.println("Failed to initialize SD card");
return;
}
Serial.println("SD card initialized successfully");
}
/**
* @brief Get sensor readings and return JSON object
*/
String getSensorReadings(){
sensors.requestTemperatures(); // Send the command to get temperatures
float temperature = sensors.getTempCByIndex(0); // First DS18B20 sensor found
readings["temperature"] = String(temperature);
// Adjust time to GMT+2 timezone
timeClient.update();
readings["time"] = timeClient.getFormattedTime();
String jsonString = JSON.stringify(readings);
return jsonString;
}
/**
* @brief Save sensor readings to SD card
*/
void saveSensorReadingsToSD(String sensorReadings) {
dataFile = SD.open("/sensor_readings.txt", FILE_WRITE);
if (dataFile) {
dataFile.println(sensorReadings);
dataFile.println(timeClient.getFormattedTime());
dataFile.close();
Serial.println("Sensor readings saved to SD card");
} else {
Serial.println("Error opening file to save sensor readings");
}
}
/**
* @brief Read sensor readings from SD card
*/
String readSensorReadingsFromSD() {
String sensorReadings = "";
dataFile = SD.open("/sensor_readings.txt");
if (dataFile) {
while (dataFile.available()) {
sensorReadings += (char)dataFile.read();
}
dataFile.close();
Serial.println("Sensor readings read from SD card");
} else {
Serial.println("Error opening file to read sensor readings");
}
return sensorReadings;
}
/**
* @brief Send sensor readings to WebSocket clients
*/
void sendSensorReadingsToWebSocket(String sensorReadings) {
ws.textAll(sensorReadings);
}
/**
* @brief Initialize SPIFFS (SPI Flash File System)
*/
void initSPIFFS() {
if (!SPIFFS.begin(true)) {
Serial.println("An error has occurred while mounting SPIFFS");
}
Serial.println("SPIFFS mounted successfully");
}
/**
* @brief Initialize WiFi connection
*/
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println(WiFi.localIP());
}
/**
* @brief Notify WebSocket clients of sensor readings
*/
void notifyClients(String sensorReadings) {
ws.textAll(sensorReadings);
}
/**
* @brief Handle WebSocket messages
*/
void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
AwsFrameInfo *info = (AwsFrameInfo*)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
String sensorReadings = getSensorReadings();
Serial.print(sensorReadings);
notifyClients(sensorReadings);
saveSensorReadingsToSD(sensorReadings);
}
}
/**
* @brief Handle WebSocket events
*/
void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("WebSocket client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
handleWebSocketMessage(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}
/**
* @brief Initialize WebSocket server
*/
void initWebSocket() {
ws.onEvent(onEvent);
server.addHandler(&ws);
}
/**
* @brief Setup function, called once at startup
*/
void setup() {
Serial.begin(115200);
initDS18B20(); // Initialize DS18B20
initWiFi();
initSPIFFS();
initSDCard(); // Initialize SD card
initWebSocket();
timeClient.begin(); // Initialize NTP client
// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html", "text/html");
});
server.serveStatic("/", SPIFFS, "/");
// Start server
server.begin();
}
/**
* @brief Main loop function
*/
void loop() {
if ((millis() - lastTime) > timerDelay) {
String sensorReadings = getSensorReadings();
Serial.print(sensorReadings);
notifyClients(sensorReadings);
saveSensorReadingsToSD(sensorReadings);
lastTime = millis();
}
// Check for new WebSocket messages and handle them
ws.cleanupClients();
// Read sensor readings from SD card and send them to WebSocket clients
String sensorReadingsFromSD = readSensorReadingsFromSD();
if (sensorReadingsFromSD != "") {
sendSensorReadingsToWebSocket(sensorReadingsFromSD);
}
// Update NTP client
timeClient.update();
delay(1000); // Add a small delay to avoid busy loop
}