Utility Mains and Generator Monitor
Posted: Sat Nov 05, 2016 1:41 pm
So Iam in charge of this building which houses a lot of electrical equipment. It is a headend for the cable company that I work for. It has a nice big Generac 75KW generator. I needed a way to tell me when the Generator came on and when it shut off. As a bonus I wanted to be able to see if the Utility AC was on or off as well. I eneded up with a sketch that monitored the AC and the Generator and sent me an email when one of them was triggered on or off. It also runs a webserver that uses AJAX to update the status of the Generator or AC right on the page in real time.
The sketch is using the ArduinoOTA Library which lets you upload sketches to the esp8266 through the wifi. The best part is the httpwebserverupdate library which allows you to connect to the esp8266 via a webpage and upload a compiled bin of the sketch right to it from anywhere. The Bounce2 library is a treat to give you easy control over false triggers in a noisy environment by adding a timer that checks for the trigger, waits the allotted time and check again before it triggers. If you haven't used the Bounce2 library then by all means do yourself a favor and check it out. Gsender is a library that uses gmail to send an email via your gmail account using ssl.
Basically you power the esp8266 NodeMCU board with a 5V micro-usb phone charger type wall wart. Connect the positive wires of the DC end of the 3.3V wall warts to GPIO's 5 and 4 and the negative ends to the ground on the board. Get creative or do what I did and cut about a couple feet off the female end of an extension cord. Put insulated alligator clips on the cut-off end and connect them to the pre-determined spots in the Transfer switch cabinet for the generator. Solder one 4.5K ohm resistor from each of the INPUT pins to the ground of the board. Plug the 3.3V wall warts to the female end of the extension cords. Power the NodeMCU esp board with serial connected to see the ip address. Load that ip address in your broowser with :8080 as the port and the status page will appear. change port to 8081 and add /update to the end or the address to get to the update page. Monitor!!
BE VERY CAREFUL WORKING WITH LIVE MAINS, IT CAN KILL YOU. IF NOT CONFIDENT PLEASE HIRE A TRAINED PROFESSIONAL. I AM NOT RESPONSIBLE
Parts list:
1 - Small Project box
1 - Any NodeMCU esp-12e board
2 - 4.5K ohm resistors to pull down the inputs
1 - 5v micro-usb phone charger or equivalent
2 - 3.3v wall brick chargers (110v wall brick to 3.3VDC 1A)
Some wire to connect stuff up.
The sketch is using the ArduinoOTA Library which lets you upload sketches to the esp8266 through the wifi. The best part is the httpwebserverupdate library which allows you to connect to the esp8266 via a webpage and upload a compiled bin of the sketch right to it from anywhere. The Bounce2 library is a treat to give you easy control over false triggers in a noisy environment by adding a timer that checks for the trigger, waits the allotted time and check again before it triggers. If you haven't used the Bounce2 library then by all means do yourself a favor and check it out. Gsender is a library that uses gmail to send an email via your gmail account using ssl.
Basically you power the esp8266 NodeMCU board with a 5V micro-usb phone charger type wall wart. Connect the positive wires of the DC end of the 3.3V wall warts to GPIO's 5 and 4 and the negative ends to the ground on the board. Get creative or do what I did and cut about a couple feet off the female end of an extension cord. Put insulated alligator clips on the cut-off end and connect them to the pre-determined spots in the Transfer switch cabinet for the generator. Solder one 4.5K ohm resistor from each of the INPUT pins to the ground of the board. Plug the 3.3V wall warts to the female end of the extension cords. Power the NodeMCU esp board with serial connected to see the ip address. Load that ip address in your broowser with :8080 as the port and the status page will appear. change port to 8081 and add /update to the end or the address to get to the update page. Monitor!!
BE VERY CAREFUL WORKING WITH LIVE MAINS, IT CAN KILL YOU. IF NOT CONFIDENT PLEASE HIRE A TRAINED PROFESSIONAL. I AM NOT RESPONSIBLE
Parts list:
1 - Small Project box
1 - Any NodeMCU esp-12e board
2 - 4.5K ohm resistors to pull down the inputs
1 - 5v micro-usb phone charger or equivalent
2 - 3.3v wall brick chargers (110v wall brick to 3.3VDC 1A)
Some wire to connect stuff up.
Code: Select all
/* Generator Monitor
/ This is a sketch for the ESP8266, NodeMCU ESP-12E in my case, using the Arduino IDE for ESP8266. This sketch will allow you to see the current
/ state of the generator and the utility AC using 2 3.3V output AC/DC adapters. One +3.3V side (for generator) connected to pin 4 of the module and the - side
/ connected to ground. Pin 4 is pulled low by a 4.5K resistor. When pin 4 is pulled High (detecting +3.3V and after a debounce of 5 seconds) the
/ Generator is running. The other +3.3V is connected to pin 5 of the module and is also pulled low via a 4.5K resistor.
/ When pin 5 is pulled high by detecting the 3.3V and after debounce of 5 seconds it means the Utility AC is on. If pin 5 falls low the AC is off.
/ The webpage updates using Ajax in order to update the status of the Generator and Utility AC.
/ This script will also send an email and a text when the AC or generator changes state.
/ The Bounce2 Library takes care of any false triggers that may happen due to noise etc.
/ The ESP can be updated via the OTA Library through the IDE or using the update Server web page (upload a bin file).
/ WSW 11-05-2016
*/
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Bounce2.h>
#include "Gsender.h"
extern "C" {
#include "user_interface.h"
}
#define F
int genPin = 4;
int acPin = 5;
String HTTP_req;
ESP8266WebServer httpServer(8081);
ESP8266HTTPUpdateServer httpUpdater;
WiFiServer server(8080);
//*-- IoT Information
const char* host = "esp8266";
const char* SSID = "YOUR_SSID";
const char* PASS = "YOUR_WIFI_PASS";
// Instantiate Generator Bounce object
Bounce debouncerGen = Bounce();
// Instantiate AC Bounce object
Bounce debouncerAc = Bounce();
void setup() {
Serial.begin(115200);
pinMode(genPin, INPUT);
// After setting up the button, setup the Bounce instance :
debouncerGen.attach(genPin);
debouncerGen.interval(5000); // interval in ms
pinMode(acPin, INPUT);
// After setting up the button, setup the Bounce instance :
debouncerAc.attach(acPin);
debouncerAc.interval(5000); // interval in ms
digitalWrite(genPin, LOW);
delay(50);
Serial.print(F("Connecting to "));
Serial.println(F(SSID));
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(F("."));
}
Serial.println(F(""));
Serial.println(F("WiFi connected"));
delay(1000);
// BEGIN OTA PROGRAMMING
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// No authentication by default
// ArduinoOTA.setPassword((const char *)"1234");
ArduinoOTA.onStart([]() {
Serial.println("Start");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("OTA Ready");
// END OTA PROGRAMMING
//START WEBSERVER UPGRADE
MDNS.begin(host);
httpUpdater.setup(&httpServer);
httpServer.begin();
MDNS.addService("http", "tcp", 8081);
//Start the server
server.begin();
Serial.println(F("Server started"));
//Print the IP Address
Serial.print(F("Use this URL to connect: "));
Serial.print(F("http://"));
Serial.print(F(WiFi.localIP()));
Serial.println(F("/"));
delay(500);
//Send email to show that monitor booted up
Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject = "Monitor has Started!";
if(gsender->Subject(subject)->Send("YOUR_EMAIL", "Status Monitor at New HE has started, please check http://YOUR_IP:8080")) {
Serial.println("Message send. Monitor Up");
} else {
Serial.print("Error sending message: ");
Serial.println(gsender->getError());
}
}
void loop() {
ArduinoOTA.handle();
httpServer.handleClient();
// Check if a client has connected
WiFiClient client = server.available();
// Return the response
if (client) { // got client?
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) { // client data available to read
char c = client.read(); // read 1 byte (character) from client
HTTP_req += c; // save the HTTP request 1 char at a time
// last line of client request is blank and ends with \n
// respond to client only after last line received
if (c == '\n' && currentLineIsBlank) {
client.println(F("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: keep-alive"));
client.println();
if (HTTP_req.indexOf("ajax_switch") > -1) {
// read switch state and send appropriate paragraph text
GetAcState(client);
GetGenState(client);
}
else { // HTTP request for web page
// send web page - contains JavaScript with AJAX calls
client.print(F("<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<title>NEW Headend</title>\r\n<meta name='viewport' content='width=device-width', initial-scale='1'>"));
client.print(F("<script>\r\nfunction GetAcState() {\r\nnocache = \"&nocache=\" + Math.random() * 1000000;\r\nvar request = new XMLHttpRequest();\r\nrequest.onreadystatechange = function() {\r\nif (this.readyState == 4) {\r\nif (this.status == 200) {\r\nif (this.responseText != null) {\r\ndocument.getElementById(\"switch2_txt\").innerHTML = this.responseText;\r\n}}}}\r\nrequest.open(\"GET\", \"ajax_switch\" + nocache, true);\r\nrequest.send(null);\r\nsetTimeout('GetAcState()', 5000);\r\n}\r\n</script>\n"));
client.print(F("</head>\r\n<body onload=\"GetAcState()\">\r\n<center><h1>NEW Headend</h1><hr>\r\n<div id=\"switch2_txt\">\r\n</div>\r\n<br>\n"));
client.print(F("<script>\r\nfunction GetGenState() {\r\nnocache = \"&nocache=\" + Math.random() * 1000000;\r\nvar request = new XMLHttpRequest();\r\nrequest.onreadystatechange = function() {\r\nif (this.readyState == 4) {\r\nif (this.status == 200) {\r\nif (this.responseText != null) {\r\ndocument.getElementById(\"switch_txt\").innerHTML = this.responseText;\r\n}}}}\r\nrequest.open(\"GET\", \"ajax_switch\" + nocache, true);\r\nrequest.send(null);\r\nsetTimeout('GetGenState()', 5000);\r\n}\r\n</script>\n"));
client.print(F("\r\n<body onload=\"GetGenState()\">\r\n<center><hr>\r\n<div id=\"switch_txt\">\r\n</div>\r\n<br>\n"));
client.println(F(system_get_free_heap_size()));
client.println(F("</body>\r\n</html>"));
}
// display received HTTP request on serial port
Serial.println(F(HTTP_req));
HTTP_req = ""; // finished with request, empty string
break;
}
// every line of text received from the client ends with \r\n
if (c == '\n') {
// last character on line of received text
// starting new line with next character read
currentLineIsBlank = true;
}
else if (c != '\r') {
// a text character was received from client
currentLineIsBlank = false;
}
} // end if (client.available())
} // end while (client.connected())
delay(1); // give the web browser time to receive the data
client.stop(); // close the connection
} // end if (client)
debouncerGen.update();
debouncerAc.update();
int readGenState = debouncerGen.read();
if (debouncerGen.rose() ) {
delay(1000);
Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject = "New Headend Generator is Running!";
if(gsender->Subject(subject)->Send("YOUR_EMAIL", "Generator is running at new headend, please check http://YOUR_IP:8080")) {
Serial.println("Message send. Gen Running.");
} else {
Serial.print("Error sending message: ");
Serial.println(gsender->getError());
}
}
if (debouncerGen.fell() ) {
delay(1000);
Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject = "New Headend Generator has Shut Down!";
if(gsender->Subject(subject)->Send("YOUR_EMAIL", "Generator has shut down at new headend, please check http://YOUR_IP:8080")) {
Serial.println("Message send. Gen Down.");
} else {
Serial.print("Error sending message: ");
Serial.println(gsender->getError());
}
}
int readAcState = debouncerAc.read();
if (debouncerAc.rose() ) {
delay(1000);
Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject = "New Headend Utility AC is On!";
if(gsender->Subject(subject)->Send("YOUR_EMAIL", "Utility AC is on at new headend, please check http://YOUR_IP:8080")) {
Serial.println("Message send. AC On");
} else {
Serial.print("Error sending message: ");
Serial.println(gsender->getError());
}
}
if (debouncerAc.fell() ) {
delay(1000);
Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject = "New Headend Utility AC is Off!";
if(gsender->Subject(subject)->Send("YOUR_EMAIL", "Utility AC is off at new headend, please check http://YOUR_IP:8080")) {
Serial.println("Message send. AC Off");
} else {
Serial.print("Error sending message: ");
Serial.println(gsender->getError());
}
}
}
// send the state of the generator to the web browser
void GetGenState(WiFiClient cl) {
if (digitalRead(4)) {
//Serial.println(F("Running"));
cl.println(F("<p>Generator is currently: <span style='background-color:#FF0000; font-size:18pt'>Running</span></p>"));
}
else {
//Serial.println(F("Shut down"));
cl.println(F("<p>Generator is currently: <span style='background-color:#00FF00; font-size:18pt'>Shut down</span></p>"));
}
}
// send state of AC to the web browser
void GetAcState(WiFiClient cl) {
if (digitalRead(5)) {
//Serial.println(F("AC On"));
cl.println(F("<p>Utility AC is currently: <span style='background-color:#00FF00; font-size:18pt'>On</span></p>"));
}
else {
//Serial.println(F("AC Off"));
cl.println(F("<p>Utility AC is currently: <span style='background-color:#FF0000; font-size:18pt'>Off</span></p>"));
}
}