ESP32 – CPU Temp Monitoring

This script runs on an ESP32 WROOM-DA Module

/*
This device will monitor the MQTT messages published by all Raspberry running at home and will detect which of these raspberry have a CPU Temperature running above a defined threshold

A Pushover notification will be sent at the end of the set-up module to publish the name of the device as well as its IP Address.

When a Raspberry is detected as too hot, a message is sent to Telegram bot created for this purpose

*/

#include <SPI.h>
#include <WiFi.h>
//#include <WiFiManager.h>
#include <WiFiClientSecure.h>                   // part of the esp32 framework V1.0
#include <esp_sntp.h>
#include <PubSubClient.h>  // for MQTT
#include <UniversalTelegramBot.h>               // https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot V1.3.0
#include <string.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>

const char* ssid = "Sotong_Purnama_ext";
const char* password = "15sotong15";
const char* MyHostName = "ESP32 - CPU Temp Monitoring";
const char* hostname;  // a variable to store the Hostname of ESP32
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
const long  gmtOffset_sec = 28800;  // For GMT+8
const int   daylightOffset_sec = 0;  // No daylight offset

// For Pushover
const char* title;
const char* message;
const char* pushoverUserKey = "u6ysovfgq1nhysszxzh91qnwadch2y";   // Set the user key generated in the Pushover account settings
const char* pushoverAPIToken = "amhb2rxrc2wa8gnpbpek99g2qrh4kx";  // Set the API token generated in the Pushover account settings
//Pushover API endpoint
const char* pushoverApiEndpoint = "https://api.pushover.net/1/messages.json";
const char* messagePrefix = "";  // Set a prefix for all messages


// // HTTPS root certificate for api.pushover.net: DigiCert Global Root CA, expires 2031.11.10
const char pushoverCertificateRoot[] = R"=EOF=(
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
)=EOF=";


WiFiClient espClient;
PubSubClient mqtt_client(espClient);
WiFiClientSecure clientTCP;  // for TELEGRAM

// Create a WiFiClientSecure object
WiFiClientSecure ipClient;

 bool wifiConnected = true;

// MQTT broker
const char* mqtt_server = "192.168.86.225";
const unsigned mqtt_port = 1883;
const char* mqtt_user = "homeassistant";
const char* mqtt_password = "raspberrypi";

// TELEGRAM
char TelegramBOTtoken[50] = "6426740052:AAE1WAIrfylu9694iEZlbfJRcDDE7nHMG9Y";  // token given by BotFather upon creation of the bot for cpu_temperature_monitoying
char Chat_ID[15] = "739396707";   // pops telegram ID
char Country[30] = "Asia/Singapore";
char chatId[15] = "";
float temp_threshold = 75.0;

// Set your static IP address
IPAddress local_IP(192, 168, 86, 30);
// Set your Gateway IP address
IPAddress gateway(192, 168, 86, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);   // optional
IPAddress secondaryDNS(8, 8, 4, 4); // optional

boolean mute = 0;

UniversalTelegramBot bot(TelegramBOTtoken, clientTCP); // setup the Telegram bot
bool flashState = LOW; //By Default it is off
int botRequestDelay = 1000;   // mean time between scan messages
long lastTimeBotRan;          // last time messages' scan has been done
void handleNewMessages(int numNewMessages);
const int DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET = 2;
const int DHCP_PACKET_CLIENT_ADDR_OFFSET = 28;

enum State
{
	READY,
	EVALUATING,
	TOO_HOT
};

volatile State state = READY;
String newMAC;
String newIP;
String newName;
String device = "";
const char *hexDigits = "0123456789ABCDEF";
String DetectorMessage;
float temperature;

void sendPushover(const char* title, const char* message){
  const char* hostname;
  String IPAddress;
  //Make HTTP POST request to send notification
  if (WiFi.status() == WL_CONNECTED) {
    WiFi.setHostname(MyHostName);
    hostname = WiFi.getHostname();
    IPAddress = WiFi.localIP().toString().c_str();

    // Create a JSON object with notification details
    // Check the API parameters: https://pushover.net/api
    StaticJsonDocument<512> notification;
    notification["token"] = pushoverAPIToken;
    notification["user"] = pushoverUserKey;
    notification["message"] = IPAddress;
    notification["title"] = hostname;
    notification["url"] = "";
    notification["url_title"] = "";
    notification["html"] = "";
    notification["priority"] = "";
    notification["sound"] = "cosmic";
    notification["timestamp"] = "";

    // Serialize the JSON object to a string
    String jsonStringNotification;
    serializeJson(notification, jsonStringNotification);

 
    // Set the certificate
    ipClient.setCACert(pushoverCertificateRoot);

    // Create an HTTPClient object
    HTTPClient https;
    // Specify the target URL
    https.begin(ipClient, pushoverApiEndpoint);
    // Add headers
    https.addHeader("Content-Type", "application/json");

    // Send the POST request with the JSON data
    int httpResponseCode = https.POST(jsonStringNotification);

    // Check the response
    if (httpResponseCode > 0) {
      Serial.printf("HTTPS response code: %d\n", httpResponseCode);
      String response = https.getString();
      Serial.println("Response:");
      Serial.println(response);
    } else {
      Serial.printf("HTTPS response code: %d\n", httpResponseCode);
    }

    // Close the connection
    https.end();
  }
}

void printLocalTime()
{
  struct tm timeinfo;
  // if the function getLocalTime fails
  if(!getLocalTime(&timeinfo)){
    // print an error message
    Serial.println("No time available (yet)");
    // and return
    return;
  }
  //else print the Date and time as described below
  // Format "DayOfWeek, Month Date Year Hour:Minutes:Seconds"
  Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}

// Callback function (get's called when time adjusts via NTP)
void timeavailable(struct timeval *t)
{
  Serial.println("\nGot time adjustment from NTP!");
  printLocalTime();
  Serial.println();
}

// function to reconnect to the MQTT broker
void reconnect() {
  // Loop until we're reconnected
  while (!mqtt_client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (mqtt_client.connect("ESP32client",mqtt_user,mqtt_password)) {
      Serial.println("Connected to MQTT server");
      // Subscribe
      mqtt_client.subscribe("#");
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqtt_client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void extract_from_topic(String topic){
  device = "Device Name";
  // Extracting Device Name
  // topic is in the form of "cpu_temp/device_name"
  // so we are interested in all what is after character position 9
  device = topic.substring(9,topic.length());
}

void callback(char* topic, byte* message, unsigned int length) {
unsigned int MessageLength;
  String messageTemp;
  temperature = 0.0;
  Serial.print("\nMessage arrived on topic: ");
  Serial.println(topic);
  // extract the name of the device from the topic
  extract_from_topic(topic);
  // and print it
  Serial.print("Device Name: ");
  Serial.println(device);
  Serial.print("Device Temperature: ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)message[i]);
    messageTemp += (char)message[i];
  }
  MessageLength = messageTemp.length();
  Serial.println("");

  if (messageTemp.toFloat() > temp_threshold){
    temperature = messageTemp.toFloat();
  //  Serial.println("Device is too hot!");
    state = TOO_HOT;
  }

  // Feel free to add more if statements to check on the content of messages

}

//#########################//
//          SET-UP         //
//#########################//
void setup() {

  ipClient.setCACert(pushoverCertificateRoot);

  delay(1000);
  Serial.begin(115200);
  Serial.println();
  // Connect to the WiFi Network
  // Serial.print("Connecting to ");
  // Serial.println(ssid);
  // Serial.print("With password ");
  // Serial.println(password);
  // Configures static IP address
  WiFi.setHostname(MyHostName);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Hostname is: ");
  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
    Serial.println("STA Failed to configure");
  }
  else
  {
    Serial.println("Wifi configuration successful");
  }
  delay(1000);
    Serial.println(WiFi.getHostname());
    clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT);

    delay(2000);
    Serial.println("Attempting to connect to Wifi network...");
    // Wait until connection is established
    while(WiFi.status() != WL_CONNECTED){
      //  Serial.print(WiFi.status());
        Serial.print(".");
        delay(500);
        if (WiFi.status() == WL_CONNECT_FAILED){
          Serial.println("\nAttempt to connect has failed! Rebooting now...");
          ESP.restart();
        }
    }
    // Print ESP32's IP & HostName
    Serial.println("\nConnected to the WiFi network");
    Serial.print("Local ESP32 IP: ");
    Serial.println(WiFi.localIP());
    Serial.print("ESP32 HostName: ");
    Serial.println(WiFi.getHostname());

  // set notification call-back function
  sntp_set_time_sync_notification_cb( timeavailable );

  /**
   * NTP server address could be acquired via DHCP,
   *
   * NOTE: This call should be made BEFORE esp32 acquires IP address via DHCP,
   * otherwise SNTP option 42 would be rejected by default.
   * NOTE: configTime() function call if made AFTER DHCP-client run
   * will OVERRIDE acquired NTP server address
   */
  sntp_servermode_dhcp(1);    // (optional)

  /**
   * This will set configured ntp servers and constant TimeZone/daylightOffset
   * should be OK if your time zone does not need to adjust daylightOffset twice a year,
   * in such a case time adjustment won't be handled automatically.
   */
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1,ntpServer2);

  /**
   * A more convenient approach to handle TimeZones with daylightOffset 
   * would be to specify a environment variable with TimeZone definition including daylight adjustmnet rules.
   * A list of rules for your zone could be obtained from https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h
   */
  //configTzTime(time_zone, ntpServer1, ntpServer2);
  
  Serial.println();

  // Serial.println("##############################");
  // Serial.println("# Device CPU Temp Monitoring #");
  // Serial.println("##############################");
  // Print out Ready message as well as IP address given by DHCP
	Serial.print("Host Ready: ");
  Serial.println(WiFi.getHostname());
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println("Connecting to the MQTT Broker");
  // sets the MQTT server
  mqtt_client.setServer(mqtt_server,mqtt_port);
  mqtt_client.setCallback(callback);
  // attempting to connect to the MQTT server
  while (!mqtt_client.connected()) {
    Serial.print("Connecting to MQTT...");
    if (mqtt_client.connect("mqtt_client", mqtt_user, mqtt_password )) {
      Serial.println("connected");  
    } else {
      Serial.print("failed with state ");
      Serial.println(mqtt_client.state());
      delay(5000);
    }
  }
  // connected to the MQTT server, can subscribe to topics
  // subscribe to cpu_temp topic for all computers
  mqtt_client.subscribe("cpu_temp/#");
//  Serial.println("Hello World!");
  bot.sendMessage(Chat_ID, "ESP32 - CPU Temp Monitoring\nStarting now...");

// calls the sendPushover functions
   sendPushover(title, message);  
}

//#########################//
//        MAIN LOOP        //
//#########################//
void loop() {
char telegram_message [100];

  // check that the MQTT connection is established
  if (!mqtt_client.connected()) {
    Serial.println("MQTT server disconnected...");
    // if it is not, then reconnect
    reconnect();
  }
  mqtt_client.loop(); 
  
  // check if a device has too hot a temperature (state = TOO_HOT)
  delay(20);
	if(state == TOO_HOT)
	{	
    if (mute==0){
    // Send a message on Telegram
    //  Serial.print("Sending a message on Telegram: ");
    sprintf(telegram_message, "%s' temperature is %.1f deg. Have a look at it!", device, temperature);
    //  Serial.println(buffer);
    //  Serial.println(telegram_message);
    bot.sendMessage(Chat_ID, telegram_message);
    }
  state = READY;
	}
}