/**********************************************************************************/ /* */ /* MQTT_energymeter counts light pulses from an energy meter, converts it to Wh */ /* and publishes it to an MQTT broker via ethernet. Every 1000Wh the counter is */ /* saved to EEPROM. Every power up this value is read from EERPOM as the starting */ /* value for the counter, preventing data loss after a power outage. */ /* */ /* (C)2022 M.T. Konstapel https://meezenest.nl/mees */ /* */ /* This file is part of MQTT_energymeter. */ /* */ /* MQTT_energymeter is free software: you can redistribute it and/or modify */ /* it under the terms of the GNU General Public License as published by */ /* the Free Software Foundation, either version 3 of the License, or */ /* (at your option) any later version. */ /* */ /* MQTT_energymeter is distributed in the hope that it will be useful, */ /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License */ /* along with MQTT_energymeter. If not, see . */ /* */ /**********************************************************************************/ #include // The connection_data struct needs to be defined in an external file. #include #include //#include "utility/logging.h" #include "PubSubClient.h" #include //Internal EEPROM #define MACADDRESS 0x12,0x7C,0x54,0x33,0xB4,0xA2 #define MYIPADDR 192,168,88,120 #define MYIPMASK 255,255,255,0 #define MYDNS 192,168,88,1 #define MYGW 192,168,88,1 #define LISTENPORT 1000 #define UARTBAUD 115200 /* CHANGE THESE VALUES TO CUSTOMIZE DATA LOGGER */ #define CLIENT_ID "MeesElectronics" // Our MQTT client ID #define CLIENT_TOPIC "energy/solar" // Name of the topic we publish //IPAddress IP_MQTT_broker(192, 168, 89, 10); // IP address of the MQTT broker //String string_ip_mqtt_broker = "192.168.88.10"; char IP_string_MQTT_broker[20] = "192.168.88.11"; uint16_t MQTT_port = 1884; IPAddress IP_MQTT_broker; #define PULSES_PER_KWH 500 // 500 pulses from the energy meter is one kWh /* END USER DEFINABLE VALUES */ uint8_t mac[6] = {MACADDRESS}; uint8_t myIP[4] = {MYIPADDR}; uint8_t myMASK[4] = {MYIPMASK}; uint8_t myDNS[4] = {MYDNS}; uint8_t myGW[4] = {MYGW}; EthernetServer server = EthernetServer(LISTENPORT); EthernetClient ethClient; PubSubClient mqttClient; #define PULSES_PER_WH 1000/PULSES_PER_KWH // Calculates how many pulses corresponds to a Wh #define INTERVAL 10000 // 10 sec delay between MQTT publishings long previousMillis; char numberArray[20]; uint32_t PulseCount=0; // This variable holds the pulse count from the energy meter uint32_t EnergyReading=0; // This variable holds the number of Watt-hours uint32_t stored_kwh_count=0; // Counted whole kWh value sored in EEPROM void onDetectInterrupt() { // The Arduino calls this function when it detects a falling edge on pin 2. // Received a pulse from the energy meter: add one to the counter PulseCount++; } void setup() { // setup serial communication Serial.begin(UARTBAUD); Serial.println(F("Energy meter with MQTT client.")); // Read EEPROM address 0. If it is 0xFF the EEPROM is probaly empty and we set EEPROM address 0 to 0x5A if (EEPROM.read(0) == 0xFF) { Serial.println(F("Powered up for the first time. Saving the default settings to the EEPROM.")); EEPROM.write(0, 0x5A); EEPROM.put(1, stored_kwh_count); // Save zero to EEPROM EEPROM.put(5, MQTT_port); // Save MQTT broker port EEPROM.put(9,IP_string_MQTT_broker); //Save default IP address of MQTT broker } // Now we test if the first address of the EEPROM is 0x5A. If so, te EEPROM is correctly initialized. We can assume that the stored // stored_kwh_count at address 1-4 is valid. if (EEPROM.read(0) == 0x5A) { Serial.print(F("Valid configuration found in EEPROM.\nLast stored kWh value: ")); EEPROM.get(1,stored_kwh_count); Serial.println(stored_kwh_count); EEPROM.get(9,IP_string_MQTT_broker); //Save default IP address of MQTT broker Serial.print(F("IP of MQTT broker: ")); Serial.println(IP_string_MQTT_broker); EEPROM.get(5, MQTT_port); Serial.print(F("Port of MQTT broker: ")); Serial.println(MQTT_port); } else { // There was a problem reading the EEPROM. Try resetting the EEPROM settings and halt. A reboot of the device might solve the issue. Serial.println(F("Error reading the EEPROM. Try rebooting the device.")); EEPROM.write(0, 0x5A); EEPROM.put(1, stored_kwh_count); // Save zero to EEPROM EEPROM.put(5, MQTT_port); // Save MQTT broker port EEPROM.put(9,IP_string_MQTT_broker); //Save default IP address of MQTT broker while(1); } // Attach interrupt to input pin connected to pulse input pinMode(2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(2), onDetectInterrupt, FALLING); // initialize the ethernet device //Ethernet.begin(mac,myIP,myDNS,myGW,myMASK); Serial.print(F("Configuring ethernet...")); //Ethernet.begin(mac,myIP,myDNS,myGW,myMASK); // setup ethernet communication using DHCP if (Ethernet.begin(mac) == 0) { Serial.println("[FAIL]"); while(1); }; Serial.println(F("[OK]")); // start listening for clients server.begin(); // setup mqtt client IP_MQTT_broker.fromString(IP_string_MQTT_broker); mqttClient.setClient(ethClient); mqttClient.setServer(IP_MQTT_broker,MQTT_port); Serial.println(F("MQTT client configured")); previousMillis = millis(); } void loop() { size_t size; uint16_t cnt; char tmp_string[6]; // Telnet loop if (EthernetClient client = server.available()) { if (client) { while((size = client.available()) > 0) { uint8_t* msg = (uint8_t*)malloc(size+1); //make memory allocation one byte larger to accomodate for the NULL string terminator. size = client.read(msg,size); msg[size-1] = 0; //Add NULL to terminate the string // Set IP of MQTT broker if (msg[0] == ':' && size>9 && size<20) { // Extract IP address from command for (cnt=2; cnt < (size-2); cnt++) { IP_string_MQTT_broker[cnt-2] = msg[cnt]; } IP_string_MQTT_broker[cnt-2] = 0; // Add NULL to string if (IP_MQTT_broker.fromString((char*)IP_string_MQTT_broker)) { client.write("MQTT server set to ",19); Serial.print(F("MQTT server set to : ")); Serial.println(IP_string_MQTT_broker); client.write(msg,size); } else { client.write("?",1); Serial.println(F("?")); } } // Set port of MQTT broker else if (msg[0] == 'P' && size>3 && size<10) { // Clear temporary string for (cnt=0; cnt < sizeof(tmp_string); cnt++) tmp_string[cnt]=0; // Extract port number from command uint16_t port=0; for (cnt=2; cnt < (size-2); cnt++) { if (msg[cnt]>=48 && msg[cnt]<=57) { port = 10*port + (msg[cnt]-48); tmp_string[cnt-2] = msg[cnt]; } } tmp_string[cnt-2] = 0; // Add NULL to string if (port>0 && port<=0xFFFF) { MQTT_port=port; client.write("MQTT port set to ",17); Serial.print(F("Set port : ")); Serial.println(MQTT_port); client.write(tmp_string,cnt-2); } else { client.write("?",1); Serial.println(F("?")); } } // Print IP and port of MQTT broker else if (msg[0] == 'I') { client.write("MQTT server set to : ",21); Serial.print(F("MQTT server set to : ")); Serial.print(IP_string_MQTT_broker); Serial.print(F(" port : ")); Serial.println(MQTT_port); client.write(IP_string_MQTT_broker,sizeof(IP_string_MQTT_broker)); client.write(" port : ",8); itoa(MQTT_port,tmp_string,10); //(integer, yourBuffer, base) client.write(tmp_string,sizeof(tmp_string)); } // Save IP of MQTT broker to EEPROM else if (msg[0] == 'S') { EEPROM.put(5, MQTT_port); //Save port address of MQTT broker EEPROM.put(9,IP_string_MQTT_broker); //Save IP address of MQTT broker client.write("SAVED",5); Serial.println(F("SAVED")); } // Unknown command else { client.write("?",1); Serial.println(F("?")); } client.write("\n",1); free(msg); } } } // check interval if(millis() - previousMillis > INTERVAL) { sendData(); Serial.print(F(CLIENT_TOPIC));Serial.print(F(" : "));Serial.println(EnergyReading); previousMillis = millis(); } mqttClient.loop(); readEnergymeter(); } void sendData() { if(mqttClient.connect(CLIENT_ID)) { //Convert unsigned long to string (base 10) and send it to our MQTT broker mqttClient.publish(CLIENT_TOPIC, ultoa(EnergyReading, numberArray, 10)); } } // Counts pulses from energy meter. Every pulse is equivalent to a certain amount of energy in Wh. This is almost always stated on te front of the meter. // With variable PULSES_PER_KWH you can define this value. // Every PULSES_PER_KWH pulses is saved to EEPROM. After a powercycle this value is read from EEPROM. void readEnergymeter(){ // Every kWh (PULSES_PER_KWH) we increment and save kWh value to EEPROM if (PulseCount >= PULSES_PER_KWH) { PulseCount=0; //Serial.println(F("Store kWh counter to EEPROM.")); stored_kwh_count++; EEPROM.put(1,stored_kwh_count); } // Convert pulses to Wh for sending to the MQTT broker EnergyReading=1000*stored_kwh_count + PulseCount*PULSES_PER_WH; }