/** * _____ ____ ____ _______ _ * |_ _||_ \ / _||_ __ \ / \ * | | | \/ | | |__) | / _ \ * _ | | | |\ /| | | __ / / ___ \ * | |__' | _| |_\/_| |_ _| | \ \_ _/ / \ \_ * `.____.' |_____||_____||____| |___||____| |____| * * ESP Energy Monitor v0.6.6 by Jorge Assunção * * Based on a project by timseebeck @ https://community.home-assistant.io/t/power-monitoring-with-an-xtm18s-and-mqtt/16316 * Remote Debug over Telnet by JoaoLopesF @ https://github.com/JoaoLopesF/ESP8266-RemoteDebug-Telnet * * See Github for instructions on how to use this code: https://github.com/jorgeassuncao/ESP8266-Energy-Monitor * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License version 2 as published by the Free Software Foundation. * * You can change your personal user data (wifi access, MQTT server, etc) in the "config/userdata.h" file */ //************* INCLUDE LIBRARIES ************************************************************************ //******************************************************************************************************** #include <ESP8266WiFi.h> #include <PubSubClient.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> #include <ESP8266mDNS.h> //#include "RemoteDebug.h" #include "userdata.h" //************* PROJECT AND VERSION ********************************************************************** //******************************************************************************************************** const char* proj_ver = "MAID - Energy Monitor v0.6.6 (15/10/2017)"; // Project name and version //************* CONFIG DEBUG ***************************************************************************** //******************************************************************************************************** //RemoteDebug Debug; // Remote debug type //************* CONFIG WEBSERVER ************************************************************************* //******************************************************************************************************** ESP8266WebServer server(80); // Webserver port //************* CONFIG PINS ***************************************************************************** //******************************************************************************************************** #define DIGITAL_INPUT_SENSOR 14 // Digital input D6 int LED_pin = 13; // Internal LED in NodeMCU #define BRIGHT 150 // Maximum LED intensity (1-500) #define INHALE 1500 // Breathe-in time in milliseconds #define PULSE INHALE*1000/BRIGHT // Pulse #define REST 1000 // Pause between breathes //************* CONFIG PULSES PER KWH ******************************************************************** //******************************************************************************************************** #define PULSE_FACTOR 2000 // Number of pulses of the meter per kWh //************* CONFIG OTHER GLOBALS ********************************************************************* //******************************************************************************************************** unsigned long SEND_FREQUENCY = 15000; // Minimum time between send in milliseconds volatile unsigned long pulseCount = 0; // Variable - volatile unsigned long lastBlink = 0; // Variable - double kwh; // Variable - unsigned long lastSend; // Variable - WiFiClient espClient; // Initiate wifi PubSubClient client(espClient); // Initiate MQTT long lastMsg = 0; // Variable - char msg[50]; // Variable - char wattString[7]; // Variable - char kwhString[7]; // Variable - double kwhaccum; // Variable - char kwhaccumString[7]; // Variable - float kwhReading; // Variable - uint32_t mmLastTime = 0; // Variable - uint32_t mmTimeSeconds = 0; // Variable - int N = 1; // Variable - Number of times to loop int runXTimes = 0; // Variable - Count times looped byte mac[6]; // Variable - MAC address char myBuffer[15]; // Variable - MAC string buffer uint8_t MAC_array[6]; // Variable - char MAC_char[18]; // Variable - //************* CONFIG WEBSERVER ROOT PAGE *************************************************************** //******************************************************************************************************** String getPage(){ // Create webpage content String page = "<!DOCTYPE html>"; page += "<html lang='en'>"; page += "<head>"; page += "<meta charset='utf-8'>"; page += "<meta http-equiv='X-UA-Compatible' content='IE=edge'>"; page += "<meta name='viewport' content='width=device-width, initial-scale=1'>"; page += "<meta http-equiv='refresh' content='15'/>"; page += "<link rel='shortcut icon' href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAAyRJREFUaN7tmM1vDWEUxn/6QUhK4itxW6ErCQsSafEHSDStWAmxKhJKRCokysbdoHtCb22JpBo2SiyEv6FpoldYqK9ElbaKCnItZiamz7y9fWfuO23UfZJZnDnnPOc5M++8HwNllFHGf4Hqf7lmNdAHdM6i+CxwH6hyQZYDCv6VnSXxQb1rpZIdCJEF15EUxbcZ6u1LSrYS+Chkg/79tJAB3krNYWB5ErIuIZoANqYoPkAD8E1qX41LUgf8EJJ2Q1yjA8EmjtNSexKojUPaKQTPiM4Iu4HflPZhZ32OFrlfBeRFwyVb0krgjSS3SkwtME5ps1M2lD+GN/7DOCgahoAKG+IdkjgKLJaYG0Rf8foY4uuJDtGcxCyRh1TAcsiel6Sb4l8L/JKYtgRv4Lhw/CQ6zm9LTIeSmF7JVrGfir0Hb5gF6Ae6DU32+k9wHLgHbJCYHDAQsqt87jCezKDNiAHpukH8D8V/0iB+RGIKwCffF0a7xPSJf5v4+20a0OK6cOnsoGtDr0F8cPVI7CbxD4p/tfiHbRrQj2uh+PXDqpnBH77GJLZG/OPiXyT+SRVrNS05RME1oamBCbGXiv1ebB3Xj4vUeyT2OrHfib1M7C82DajAerFfir1T7A7gs4F3BG+LUCz3xQy1VZuxgbzYm8V+IPZh4cn7OXf4O432AFvwVvgAlX5uMW6trdqMOMfUD+eW+OvwFp1wzDEbYsEJ4bBZyM7aEG8nOnPoVqKb6OxQb0Puw7SV6JIY01aiwYa8Angtia0Sk/Ebc7WZGwXWiP+QaHhFjFnzMtEFRrfTLbjbTjfL/WrguWi4GIfYdKA5ZYgr9kp1EZsOJo4zRIdohpi4LiRf8ZZ+W9g2oGgEvkvulbjiAVYQPdTngVUpNlBL9FD/gYSHeoD9BiFHU2zA9Ftlb1LxAcJDKRsjL+kQyoZyYv+NMKESuAtciJmXtAHwFixnvxYh2Y/WUhqwrrnAVYfTNJB6LZfngWam/o4xNVTAW+Wb0mimVOj2o9g15Kqo6bU6PzU5xhTNs32kdI5yA/MJc/IRu0STZRNDwK65FltGGfMFfwB1P4cc2cai3gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMC0xM1QyMDo1OTo1MiswMDowMGf6eRAAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTAtMTNUMjA6NTk6NTIrMDA6MDAWp8GsAAAAKHRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy90bXAvbWFnaWNrLWZDN1pLU3MxVs1E8gAAAABJRU5ErkJggg=='>"; page += "<title>"; page += host_name; page += "</title>"; page += "<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' integrity='sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u' crossorigin='anonymous'>"; page += "<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css' integrity='sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp' crossorigin='anonymous'>"; page += "<script src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' integrity='sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa' crossorigin='anonymous'></script>"; page += "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js'></script>"; page += "</head>"; page += "<body>"; page += "<p></p>"; page += "<div class='container-fluid'>"; page += "<div class='row'>"; page += "<div class='col-md-12'>"; page += "<div class='jumbotron'>"; page += "<h2>"; page += proj_ver; page += "</h2>"; page += "<p>This project uses a MQTT topic to store the accumulated kWh value, so that when there is a reboot, reset or power off the value won't be lost. The subscribed topic to get the accumulated kWh value is <small><em style='color: #ababab;'>"; page += mqtt_topic_sub_1; page += "</em></small></p>"; page += "</div>"; page += "<div class='alert alert-dismissable alert-info'>"; page += "<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>"; page += "<strong>Attention!</strong> This page auto-refreshes every 15 seconds."; page += "</div>"; page += "</div>"; page += "</div>"; page += "<div class='row'>"; page += "<div class='col-md-4'>"; page += "<h3 class='text-primary'>Current Consumption</h3>"; page += "<p class='lead'>"; page += kwhString; page += " kWh</p>"; page += "</div>"; page += "<div class='col-md-4'>"; page += "<h3 class='text-primary'>Current Power</h3>"; page += "<p class='lead'>"; page += wattString; page += " Watt</p>"; page += "</div>"; page += "<div class='col-md-4'>"; page += "<h3 class='text-primary'>Accum Consumption</h3>"; page += "<p class='lead'>"; page += kwhaccumString; page += " kWh (total)</p>"; page += "</div>"; page += "</div>"; page += "<div class='row'>"; page += "<div class='col-md-12'>"; page += "<div class='alert alert-dismissable alert-danger'>"; page += "<button type='button' class='close' data-dismiss='alert' aria-hidden='true'>×</button>"; page += "<h4>Warning!</h4>Sometimes the board does not restart correctly. If you can't get back to this page after one minute, you need to reset on the board directly on the fisical reset button. "; page += "<button type='button' class='btn btn-warning btn-default'>RESET</button>"; page += "</div>"; page += "</div>"; page += "</div>"; page += "<div class='col-md-12'>"; page += "copyright <br/><br/>"; page += "</div>"; page += "</div>"; page += "</body>"; page += "</html>"; return page; } //************* SETUP WIFI SERVER ************************************************************************ //******************************************************************************************************** void handleRoot() { // Handle "root" page server.send ( 200, "text/html", getPage() ); } void handleNotFound(){ // Handle "not found" page String message = "Page or File Not Found\n\n"; message += "URI: "; message += server.uri(); message += "\nMethod: "; message += (server.method() == HTTP_GET)?"GET":"POST"; message += "\nArguments: "; message += server.args(); message += "\n"; for (uint8_t i=0; i<server.args(); i++){ message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; } server.send(404, "text/plain", message); } //************* SETUP WIFI ******************************************************************************* //******************************************************************************************************** void setup_wifi() { delay(20); /** TELNET **/ // Debug.begin("ESP_Energy_Monitor_01_t"); // Initiaze the telnet server // Debug.setResetCmdEnabled(true); // Enable/disable (true/false) the reset command (true/false) // Debug.showTime(false); // Enable/disable (true/false) timestamps // Debug.showProfiler(false); // Enable/disable (true/false) Profiler - time between messages of Debug // Debug.showDebugLevel(false); // Enable/disable (true/false) debug levels // Debug.showColors(true); // Enable/disable (true/false) colors Serial.println("- - - - - - - - - - - - - - - - - - - - - - - - - - -"); // Block separator to serial interface // Debug.println("- - - - - - - - - - - - - - - - - - - - - - - - - - -"); // Block separator to telnet debug interface Serial.println(proj_ver); // Send project name and version to serial interface // Debug.println(proj_ver); // Send project name and version to telnet debug interface Serial.println("- - - - - - - - - - - - - - - - - - - - - - - - - - -"); // Block separator to serial interface // Debug.println("- - - - - - - - - - - - - - - - - - - - - - - - - - -"); // Block separator to telnet debug interface Serial.println(); // Send space to serial interface // Debug.println(); // Send space to telnet debug interface Serial.println(); // Connecting to wifi network Serial.print("Connecting to "); Serial.println(ssid); // Send network name to serial interface // Debug.printf("Connecting to "); Debug.println(ssid); // Send network name to telnet debug interface WiFi.config(ip, dns, gateway, subnet); // Configure connection with IP, DNS, Gateway and subnet WiFi.mode(WIFI_STA); // Switch to STA mode WiFi.begin(ssid, password); // Start wifi connection with SSID and Password WiFi.macAddress(mac); // Get MAC address of the node while (WiFi.status() != WL_CONNECTED) { // Wait until connected to wifi delay(500); Serial.print("."); } Serial.println(); // Block space to serial interface // Debug.println(); // Block space to telnet debug interface Serial.println("WiFi connected"); // Send successful connection to serial interface // Debug.println("WiFi connected"); // Send successful connection to telnet debug interface Serial.print("IP address is "); Serial.println(WiFi.localIP()); // Send IP address to serial interface // Debug.printf("IP address is "); Debug.println(WiFi.localIP()); // Send IP address to telnet debug interface sprintf(myBuffer,"%02X:%02X:%02X:%02X:%02X:%02X",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); // Get MAC address Serial.print("MAC address is "); Serial.println(myBuffer); // Send MAC address to serial interface // Debug.printf("MAC address is "); Debug.println(myBuffer); // Send MAC address to telnet debug interface } //************* READ MQTT TOPIC ************************************************************************** //******************************************************************************************************** void callback(char* topic, byte* payload, unsigned int length) { if (runXTimes < N) { // Read the topic only N times Serial.println(); // Block space to serial interface // Debug.println(); // Block space to telnet debug interface Serial.print("Message arrived for topic ["); // Send text to serial interface // Debug.printf("Message arrived for topic ["); // Send text to telnet debug interface Serial.print(topic); Serial.print("] "); // Send MQTT topic to serial interface // Debug.printf(topic); Debug.printf("] "); // Send MQTT topic to telnet debug interface String value = ""; // Store MQTT topic inside string for (int i=0;i<length;i++) { value += (char)payload[i]; } kwhReading = value.toFloat(); // Convert kwhReading to float Serial.println(); // Block space to serial interface // Debug.println(); // Block space to telnet debug interface Serial.print("Payload: "); Serial.println(kwhReading); // Send MQTT payload to serial interface // Debug.printf("Payload: "); Debug.println(kwhReading); // Send MQTT payload to telnet debug interface Serial.println(); // Block space to serial interface // Debug.println(); // Block space to telnet debug interface runXTimes++; // Increase loop count } } //************* RECONNECT MQTT *************************************************************************** //******************************************************************************************************** void reconnect() { while (!client.connected()) { // Loop until reconnected Serial.print("Attempting connection to MQTT server... "); // Send text to serial interface // Debug.printf("Attempting connection to MQTT server... "); // Send text to telnet debug interface if (client.connect(host_name, mqtt_username, mqtt_password)) { // Connect to MQTT brocker Serial.println(" connected!"); // Send text to serial interface // Debug.println(" connected!"); // Send text to telnet debug interface client.subscribe(mqtt_topic_sub_1); // MQTT topic to subscribe } else { Serial.print("failed, rc="); // Send text to serial interface // Debug.printf("failed, rc="); // Send text to telnet debug interface Serial.print(client.state()); // Send failure state to serial interface //Debug.printf(client.state()); // Send failure state to telnet debug interface Serial.println(" try again in 5 seconds"); // Send text to serial interface // Debug.println(" try again in 5 seconds!"); // Send text to telnet debug interface delay(5000); // Wait 5 seconds before retrying } } } //************* ON PULSE ********************************************************************************* //******************************************************************************************************** void onPulse() { unsigned long newBlink = micros(); unsigned long interval = newBlink-lastBlink; if (interval<10000L) { // Sometimes we get interrupt on RISING return; } kwh += 1.0 / (double)PULSE_FACTOR; // Every time there is a pulse, the energy consumption is 1 [pulse] / PULSE_FACTOR [pulses/kWh] lastBlink = newBlink; pulseCount++; // Accumulate the energy (it will be initialized again once MQTT message is sent) } //************* SETUP ************************************************************************************ //******************************************************************************************************** void setup() { pinMode(LED_pin, OUTPUT); // Configure internal LED pin as output. WiFi.macAddress(MAC_array); // NEWNEWNEW for (int iii = 0; iii < sizeof(MAC_array); ++iii){ // NEWNEWNEW sprintf(MAC_char,"%s%02X:",MAC_char,MAC_array[iii]); // NEWNEWNEW } if (MDNS.begin("esp8266")) { // Serial.println("MDNS responder started"); // } // server.on("/", handleRoot); // Serve root page server.onNotFound(handleNotFound); // Serve page not found server.begin(); // Start Webserver Serial.println("HTTP server started"); // Send text to serial interface // Debug.println("HTTP server started"); // Send text to telnet debug interface Serial.begin(115200); // Start serial interface setup_wifi(); // Start wifi client.setServer(mqtt_server, mqtt_port); // client.setCallback(callback); // // Use the internal pullup to be able to hook up this sketch directly to an energy meter with S0 output // If no pullup is used, the reported usage will be too high because of the floating pin pinMode(DIGITAL_INPUT_SENSOR,INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(DIGITAL_INPUT_SENSOR), onPulse, RISING); // // Debug.begin(host_name); // Start Telnet server kwh = 0; lastSend=millis(); } //************* LOOP ************************************************************************************* //******************************************************************************************************** void loop() { server.handleClient(); // Handle http requests for (int ii=1;ii<BRIGHT;ii++){ // Breath in digitalWrite(LED_pin, LOW); // LED on delayMicroseconds(ii*10); // Wait digitalWrite(LED_pin, HIGH); // LED off delayMicroseconds(PULSE-ii*10); // Wait delay(0); // Prevent watchdog firing } for (int ii=BRIGHT-1;ii>0;ii--){ // Breath out digitalWrite(LED_pin, LOW); // LED on delayMicroseconds(ii*10); // Wait digitalWrite(LED_pin, HIGH); // LED off delayMicroseconds(PULSE-ii*10); // Wait ii--; delay(0); // Prevent watchdog firing } delay(REST); // Pause before repeat if (!client.connected()) { // If client disconnects... reconnect(); // ...connect again } client.loop(); unsigned long now = millis(); bool sendTime = now - lastSend > SEND_FREQUENCY; // Only send values at a maximum frequency if (sendTime) { Serial.println("- - - - - - - - - - - -"); // Block separator to serial interface// // Debug.println("- - - - - - - - - - - -"); // Block separator to telnet debug interface dtostrf(kwh, 2, 4, kwhString); // Convert Current kWh to string client.publish(mqtt_topic_kwh,kwhString); // Publish Current kWh to MQTT topic Serial.print("- Current kWh: "); Serial.println(kwhString); // Send Current kWh to serial interface // Debug.printf("- Current kWh: "); Debug.println(kwhString); // Send Current kWh to telnet debug interface lastSend = now; // Update the send time after publishing double watt = kwh * 1000.0 * 3600.0 / (double)SEND_FREQUENCY * 1000.0; // Calculate the power using the energy dtostrf(watt, 4, 2, wattString); // Convert Current Watt to string client.publish(mqtt_topic_watt,wattString); // Publish Current Watt to MQTT topic Serial.print("- Current Watt: "); Serial.println(wattString); // Send Current Watt to serial interface // Debug.printf("- Current Watt: "); Debug.println(wattString); // Send Current Watt to telnet debug interface kwhaccum = kwhReading + ((double)pulseCount/((double)PULSE_FACTOR)); // Calculate the accumulated energy since the begining dtostrf(kwhaccum, 5, 2, kwhaccumString); // Convert Accum kWh (pulses) to string client.publish(mqtt_topic_pulse,kwhaccumString, true); // Publish Accum kWh (pulses) to MQTT topic Serial.print("- Accum kWh: "); Serial.println(kwhaccumString); // Send Accum kWh (pulses) to serial interface // Debug.printf("- Accum kWh: "); Debug.println(kwhaccumString); // Send Accum kWh (pulses) to telnet debug interface kwh = 0; // Reset kWh count String ipaddress = WiFi.localIP().toString(); // Create IP address string char ipchar[ipaddress.length()+1]; ipaddress.toCharArray(ipchar,ipaddress.length()+1); client.publish(mqtt_topic_ip,ipchar); // Publish IP address to MQTT topic Serial.println(); // Block space to serial interface // Debug.println(); // Block space to telnet debug interface } // Debug.handle(); // Remote debug over telnet yield(); // Yielding } // END //********************************************************************************************************