/**********************************************************************************/
/* */
/* 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 <https://www.gnu.org/licenses/>. */
/* */
/**********************************************************************************/
# include <UIPEthernet.h>
// The connection_data struct needs to be defined in an external file.
# include <UIPServer.h>
# include <UIPClient.h>
//#include "utility/logging.h"
# include "PubSubClient.h"
# include <EEPROM.h> //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 "distributioncabinet / 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 = 1883 ;
IPAddress IP_MQTT_broker ;
# define PULSES_PER_KWH 1000 // 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 60000 // 60 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
# define LED 9
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. \n Last 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 ) ;
pinMode ( LED , OUTPUT ) ;
digitalWrite ( LED , HIGH ) ;
// 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 ) ;
//client.write("?\n",2);
//client.write(": <IP> sets ip of broker.\n",26);
//client.write("P <port> set port op broker.\n",29);
//client.write("I prints ip and port of broker.\n",32);
//client.write("S saves settings to EEPROM.,27");
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 ( ) ;
if ( digitalRead ( 2 ) )
digitalWrite ( LED , HIGH ) ;
else
digitalWrite ( LED , LOW ) ;
}
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 ;
}