This script runs on an ESP32-WROOM-DA Module

/*
This device will detect the connection of new clients on my home network
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 new client is detected, a message is sent to Telegram bot created for this purpose
*/
#include <EEPROM.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <WiFiClientSecure.h> // part of the esp32 framework V1.0
#include <PubSubClient.h> // for MQTT
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <AsyncUDP.h>
#include <esp_sntp.h>
#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";
const char* password = "15sotong15";
const char* MyHostName = "ESP32 - Network 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
// token for bot "Devices Presence" created on my phone
char TelegramBOTtoken[50] = "6604978268:AAEIcoq__dhu_iIOiq3E6Xw54olv3-47NHI";
char Chat_ID[15] = "739396707"; // pops telegram ID
char Country[30] = "Asia/Singapore";
char chatId[15] = "";
char telegram_message[200];
// Network Configuration (Needed if we do not use WiFiManager to connect to Access Point and select WiFiNetwork)
// IPAddress local_IP(192, 168, 86, 222);
// 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
UniversalTelegramBot bot(TelegramBOTtoken, clientTCP); // setup the Telegram bot
AsyncUDP udp;
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 printHex(char *data, int length)
{
int p = 0;
while(p < length)
{
char ascii[17];
int i = 0;
for(; i < 16; i++)
{
Serial.printf("%02X ", data[p]);
if(data[p] >= 32)// || data[p] < 128)
ascii[i] = data[p];
else
ascii[i] = '.';
p++;
if(p == length)
{ i++; break;}
}
ascii[i] = 0;
Serial.println(ascii);
}
}
void printIP(char *data)
{
for(int i = 0; i < 4; i++)
{
Serial.print((int)data[i]);
if(i < 3)
Serial.print('.');
}
}
const int DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET = 2;
const int DHCP_PACKET_CLIENT_ADDR_OFFSET = 28;
enum State
{
READY,
SNIFFING,
DETECTED
};
volatile State state = READY;
String newMAC;
String newIP;
String newName;
const char *hexDigits = "0123456789ABCDEF";
void printLocalTime()
{
struct tm timeinfo;
// it 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();
}
void parsePacket(char* data, int length)
{
String tempName;
String tempIP;
String tempMAC;
if(state == DETECTED) return;
Serial.println("DHCP Packet");
printHex(data, length);
Serial.print("MAC address: ");
for(int i = 0; i < data[DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET]; i++){
if(i < data[DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET] - 1)
{
Serial.printf("%02X:", (int)data[DHCP_PACKET_CLIENT_ADDR_OFFSET + i]);
}
else
{
Serial.printf("%02X", (int)data[DHCP_PACKET_CLIENT_ADDR_OFFSET + i]);
}
}
for(int i = 0; i < data[DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET]; i++)
{
tempMAC += hexDigits[(int)data[DHCP_PACKET_CLIENT_ADDR_OFFSET + i] >> 4];
tempMAC += hexDigits[(int)data[DHCP_PACKET_CLIENT_ADDR_OFFSET + i] & 15];
if(i < data[DHCP_PACKET_CLIENT_ADDR_LEN_OFFSET] - 1)
{
tempMAC += ":";
}
}
Serial.println();
//parse options
int opp = 240;
while(opp < length)
{
switch(data[opp])
{
case 0x0C:
{
Serial.print("Device name: ");
for(int i = 0; i < data[opp + 1]; i++)
{
Serial.print(data[opp + 2 + i]);
tempName += data[opp + 2 + i];
}
Serial.println();
break;
}
case 0x35:
{
Serial.print("Packet Type: ");
switch(data[opp + 2])
{
case 0x01:
Serial.println("Discover");
break;
case 0x02:
Serial.println("Offer");
break;
case 0x03:
Serial.println("Request");
if(state == READY)
state = SNIFFING;
break;
case 0x05:
Serial.println("ACK");
break;
default:
Serial.println("Unknown");
}
break;
}
case 0x32:
{
Serial.print("Device IP: ");
printIP(&data[opp + 2]);
Serial.println();
for(int i = 0; i < 4; i++)
{
tempIP += (int)data[opp + 2 + i];
if(i < 3) tempIP += '.';
}
break;
}
case 0x36:
{
Serial.print("Server IP: ");
printIP(&data[opp + 2]);
Serial.println();
break;
}
case 0x37:
{
Serial.println("Request list: ");
printHex(&data[opp + 2], data[opp + 1]);
break;
}
case 0x39:
{
Serial.print("Max DHCP message size: ");
Serial.println(((unsigned int)data[opp + 2] << 8) | (unsigned int)data[opp + 3]);
break;
}
case 0xff:
{
Serial.println("End of options.");
opp = length;
continue;
}
default:
{
Serial.print("Unknown option: ");
Serial.print((int)data[opp]);
Serial.print(" (length ");
Serial.print((int)data[opp + 1]);
Serial.println(")");
printHex(&data[opp + 2], data[opp + 1]);
}
}
opp += data[opp + 1] + 2;
}
if(state == SNIFFING)
{
newName = tempName;
newIP = tempIP;
newMAC = tempMAC;
Serial.println("Stored data.");
state = DETECTED;
}
Serial.println();
// String message = "New device detected!\n\nName: " + tempName + "\nIP Address: " + tempIP + "\nMAC Address: " + tempMAC + "\n";
if(state == DETECTED and newName != "" and newIP != "" and newMAC != ""){
String message = "New device detected!\n\nName: " + newName + "\nIP Address: " + newIP + "\nMAC Address: " + newMAC + "\n";
Serial.print("Telegram Message: ");
Serial.println(message);
}
}
// function to setup UDP protocol
void setupUDP()
{
if(udp.listen(67))
{
Serial.print("UDP Listening on IP: ");
Serial.println(WiFi.localIP());
// upon receipt of a packet on UDP
udp.onPacket([](AsyncUDPPacket packet)
{
char *data = (char *)packet.data();
int length = packet.length();
//parse the packet pointed by *data with a length of 'length'
parsePacket(data, length);
});
};
}
bool isDataStored()
{
return EEPROM.read(512) != 0; // was 96
}
String readString(int offset, int len = 32)
{
String s;
int p = offset;
for(int i = 0; i < len; i++)
{
char c = EEPROM.read(p++);
if(c)
s+=c;
}
return s;
}
void readData()
{
Serial.println("Reading the data reveiced on UDP");
newMAC = readString(0, 32);
newIP = readString(32, 32);
newName = readString(64, 32);
}
void clearData()
{
Serial.println("Erasing the EEPROM");
EEPROM.write(512, 0); // was 96
EEPROM.commit();
Serial.println("EEPROM have been erased.");
}
void writeString(String s, int offset, int len = 32)
{
int p = offset;
for(int i = 0; i < len; i++)
if(i < s.length())
EEPROM.write(p++, s.charAt(i));
else
EEPROM.write(p++, 0);
}
void writeData()
{
writeString(newMAC, 0, 32);
writeString(newIP, 32, 32);
writeString(newName, 64, 32);
EEPROM.write(512, 1); // was 96
EEPROM.commit();
}
//#########################//
// SET-UP //
//#########################//
void setup()
{
// for Pushover
ipClient.setCACert(pushoverCertificateRoot);
int timeout = 120; // seconds
// select the GPIO pin for connecting the button (default state: OPEN)
#define TRIGGER_PIN 0 // GPIO 0
WiFi.mode(WIFI_STA);
Serial.begin(115200);
Serial.println("\nStarting");
pinMode(TRIGGER_PIN,INPUT_PULLUP);
WiFi.setHostname(MyHostName);
// WiFi.begin(ssid, password);
Serial.print("Hostname is: ");
Serial.println(WiFi.getHostname());
// will start by activating an AP "ESP32_AP", with password "12345678"
WiFiManager wm;
// wm.resetSettings();
// set timeout for the Config Portal
wm.setConfigPortalTimeout(timeout);
WiFiManagerParameter custom_telegram_token("telegram_bot_token", "Telegram BOT Token", "Get it from Telegram App",sizeof(TelegramBOTtoken));
WiFiManagerParameter custom_chat_id("chat_id", "Telegram Chat ID", "739396707",sizeof(Chat_ID));
wm.addParameter(&custom_telegram_token);
wm.addParameter(&custom_chat_id);
bool res;
res = wm.autoConnect("ESP32_AP", "12345678");
if(!res){
Serial.println("Failed to connect");
}
else
{
Serial.println("Connected!");
}
delay(1000);
EEPROM.begin(512); //was 128
if(isDataStored())
{
readData();
clearData();
ESP.restart();
}
// erase the EEPROM of the ESP32
// for (int i = 0; i < 512; i++) {
// EEPROM.write(i, 0);
// }
// EEPROM.commit();
// delay(500);
// initialize Serial port with 115200 bauds trasmission speed
Serial.begin(115200);
Serial.println();
WiFi.setHostname(MyHostName);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Hostname is: ");
Serial.println(WiFi.getHostname());
// Configure static IP Address
// if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS))
// {
// Serial.println("STA Failed to configure");
// }
// else
// {
// Serial.println("Wifi configuration successful");
// }
// delay(1000);
clientTCP.setCACert(TELEGRAM_CERTIFICATE_ROOT);
// Lets deal with the user config values
// Copy the string value
strncpy(TelegramBOTtoken, custom_telegram_token.getValue(), sizeof(TelegramBOTtoken));
Serial.print("----> TelegramBOTtoken: ");
Serial.println(TelegramBOTtoken);
strncpy(Chat_ID, custom_chat_id.getValue(), sizeof(Chat_ID));
Serial.print("----> ChatID: ");
Serial.println(Chat_ID);
// 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 aquired via DHCP,
*
* NOTE: This call should be made BEFORE esp32 aquires 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 aquired 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 automagicaly.
*/
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1,ntpServer2);
/**
* A more convenient approach to handle TimeZones with daylightOffset
* would be to specify a environmnet 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();
// Initializes UDP
setupUDP();
Serial.println();
Serial.println("###########################");
Serial.println("# Wifi Network 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());
bot.sendMessage(Chat_ID, "ESP32 - Network Monitoring\nStarting now...");
// calls the sendPushover function
sendPushover(title, message);
}
//#########################//
// MAIN LOOP //
//#########################//
void loop()
{
// In case there is a button connected to GPIO 0 (defined in void setup)
int timeout = 120; // timeout in seconds
// Has the button been pressed (to change WiFi configuration)
if (digitalRead(TRIGGER_PIN) == LOW){
WiFiManager wm;
wm.resetSettings();
// set timeout for the Config Portal
wm.setConfigPortalTimeout(timeout);
if (!wm.startConfigPortal("ESP32_AP")) {
Serial.println("Failed to connect and hit timeout");
delay(3000);
// reset and restart
ESP.restart();
delay(5000);
}
else
{
Serial.println("Connected!");
}
} // end of button processing
// if a new detection has occured
if(state == DETECTED)
{
// String message = "New device detected!\nName: " + newName + "\nIP Address: " + newIP + "\nMAC Address: " + newMAC + "\n";
// Serial.println("Telegram Message:");
// Serial.println(message);
// sends the message to Telegram
bot.sendMessage(Chat_ID, "New device detected!\nName: " + newName + "\nIP Address: " + newIP + "\nMAC Address: " + newMAC + "\n");
// if (newName == ""){
// bot.sendMessage(Chat_ID,"The name has not been detected! You may want to run some network tools for security purpose...");
// }
//bot.sendMessage(Chat_ID, telegram_message);
// udp.close();
// WiFi.disconnect();
// WiFi.mode(WIFI_MODE_NULL);
// writeData();
// delay(1000);
// ESP.restart();
state = READY;
}
}