Introduction: Your Energy Bill Monitor

ABOUT THIS PROJECT

If you really want to make your home smarter, you'll probably want to start from your monthly bills (i.e. energy, gas, etc...). As some say, Good for Planet, The Wallet and The Bottom Line. Open-source hardware is our way to reach sustainability in the home environment! This idea brought us to build a simple and secure solution, easy to integrate with any home automation software since it exposes data over MQTT (in our case we will show you how to integrate it into Home Assistant).

Overview

In order to measure the electrical energy consumption, we chose to use the Finder Energy Meter, since it is designed for DIN rail use and fits perfectly in the main cabinet of our house. The cool thing about this product is that it has an RS485 Modbus interface, an industrial standard communication protocol which makes talking to an Arduino really easy. In fact, Arduino has released an official shield, the MKR485 and two libraries to decode the protocol. As the mainboard, we chose the Arduino MKR WiFi 1010, since it shares the MKR form factor and has WiFi connectivity.

Setup
Warning! Check your country regulations about dealing with your house electrical system and be extremely careful because it can be deadly! If you don't know-how, call an electrician. The first step is to install the meter in your electrical cabinet. To ensure you are working in a safe environment, turn off the power from the electrical terminal ahead of your system and double-check with the multimeter that there is no voltage between the terminals. Then place the energy meter inside your cabinet and connect live and neutral wires from the main breaker to the input of the meter, remember to use the colour convention (blue for neutral and brown/black/grey for live in EU). The output has to be connected to the rest of the system.

Main voltage connections. Wires above are input, wires beyond are outputs.

Step 1: Parts Needed

Step 2: Software Needes

Software

Start your computer and open your IDE. You can use the Arduino IDE or Arduino Create Editor. The code is meeting the following requests: Modbus communication, WiFi managementMQTT protocol Modbus is and open-source protocol for industrial sensors and machines. To make Arduino talk Modbus, we are going to use the Arduino Modbus library. This library packs all the handlers and makes hooking up any Modbus device really fast. Since we are going to read registers, following the datasheet of the meter, we can find all the information we need like function codes, address of the register and size of the register in words. But to make it clearer, let us explain how Modbus works: Modbus messages follow a simple structure: 01 03 04 00 16 00 02 25 C7 0x01is the Device Address 0x03 is the Function Code that tells the device if we want to read or write data *, in this case, read holding registers 0x04 for Byte Count00 16 - We send 4 bytes of register address (00 16) that tells the device what we want to read 00 02- then the size of the register (00 02) in words (every word is 2 bytes long) Last 4 bytes are CRC code. This code is generated from a math function over previous bytes, this ensures the message has been received correctly.

Home Assistant Integration
Adding the meter to Home Assistant is pretty straightforward. Assuming you have a MQTT broker configured (Here is the guide), all you need to do is to add new definitions under the configuration.yaml file. sensor: - platform: mqtt name: "Main Voltage" state_topic: "energy/main/voltage" unit_of_measurement: "V" Here you have to put the name of the measurement, the MQTT topic to read and the measurement unit of the quantity. Save the file, check the configuration and reload Home Assistant, now the measurements will appear on the main page.

Home Assistant consumption panel showing current readings

Home Assistant will take care of creating graphs and automate processes triggered by your readings. This tutorial has finished, now it's up to you to add features and customize it for your own purposes!

Step 3: Assemble

Done? It is time to screw in the RS485 connection! We will use twisted single pair cable with the ground, typically used for phone lines. With this cable, you can transmit over a long distance (1.2 km). However, we just use a cable long enough to exit the cabinet and place the Arduino in an accessible place.

Finder RS485 connection

The RS485 interface names its terminals A, B and COM. A common de-facto standard is the use of TX+/RX+ or D+ as an alternative for B (high for MARK i.e. idle), TX-/RX- or D- as an alternative for A (low for MARK i.e. idle)Since the MKR shield supports also Full Duplex, you'll see two other terminals, Y and Z. Here we are going to screw the other end of the cable since we know from the datasheet that half-duplex communication happens only on Y and Z terminals. The COM terminal has to be connected to ISOGND. Since we use a half-duplex connection and since the cabling is peer-to-peer, we have to set up the switches on the MKR485 shield to match our setup: we set HALF (2 to off) and termination on Y-Z (3 to ON); the first one does not matter. The termination is a resistance connecting the two data terminals, for dampening interferences.

This is it. Now you can close the cabinet and focus on the software side!

Step 4: Code

#include

#include #include #include //your wifi credentials const char ssid[] = "**********"; const char pass[] = "**********";

WiFiClient net; MQTTClient client; unsigned long rate = 60000; // default refresh rate in ms unsigned long lastMillis = 0;

//connect function void connect() { Serial.print("checking wifi..."); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(1000); } Serial.print("\nconnecting..."); while (!client.connect("device_name", "user_name", "user_pw")) { //CHANGE TO MATCH YOUR SETUP Serial.print("."); delay(1000); } Serial.println("\nconnected!"); client.subscribe("energy/main/refreshrate"); //topic to set refresh rate remotely } //mqtt receive callback function void messageReceived(String &topic, String &payload) { Serial.println("incoming: " + topic + " - " + payload); if(topic == "energy/main/refreshrate"){ //refresh rate handler rate = payload.toInt()*1000; Serial.println("new rate "+String(rate)); } }

void setup() { Serial.begin(115200); WiFi.begin(ssid, pass); while (!Serial); client.begin("broker_ip", net); //CHANGE TO MATCH YOUR SETUP client.onMessage(messageReceived); // start the Modbus RTU client if (!ModbusRTUClient.begin(9600)) { Serial.println("Failed to start Modbus RTU Client!"); while (1); } }

void loop() { client.loop(); if (!client.connected()) { //check network connection connect(); } // publish a message after refresh elapsed (non-blocking routine) if (millis() - lastMillis > rate) { lastMillis = millis(); //make all the read calls float volt = readVoltage(); delay(100); float amp = readCurrent(); delay(100); double watt = readPower(); delay(100); float hz = readFreq(); delay(100); double wh = readEnergy(); //publish results under related topics client.publish("energy/main/voltage", String(volt,3)); client.publish("energy/main/current", String(amp,3)); client.publish("energy/main/power", String(watt,3)); client.publish("energy/main/frequency", String(hz,3)); client.publish("energy/main/energy", String(wh,3)); Serial.print(String(volt,3)+"V "+String(amp,3)+"A "+String(watt,3)+"W "); Serial.println(String(hz,3)+"Hz "+String(wh,3)+"kWh"); delay(100); } }

/* Functions to read Finder Energy Meter registers * * Check the modbus protocol manual to understand the code * https://gfinder.findernet.com/public/attachments/7E/EN/PRT_Modbus_7E_64_68_78_86EN.pdf */ float readVoltage(){ float volt = 0.; if (!ModbusRTUClient.requestFrom(0x01, HOLDING_REGISTERS, 0x000C, 2)) { //make the call to the register Serial.print("failed to read voltage! "); Serial.println(ModbusRTUClient.lastError()); //error handler }else{ uint16_t word1 = ModbusRTUClient.read(); //read data from the buffer uint16_t word2 = ModbusRTUClient.read(); uint32_t millivolt = word1 << 16 | word2; //bit math volt = millivolt/1000.0; } return volt; } float readCurrent(){ float ampere = 0.; if (!ModbusRTUClient.requestFrom(0x01, HOLDING_REGISTERS, 0x0016, 2)) { Serial.print("failed to read current! "); Serial.println(ModbusRTUClient.lastError()); }else{ uint16_t word1 = ModbusRTUClient.read(); uint16_t word2 = ModbusRTUClient.read(); int32_t milliamp = word1 << 16 | word2; ampere = milliamp/1000.0; } return ampere; }

double readPower(){ double watt = 0.; if (!ModbusRTUClient.requestFrom(0x01, HOLDING_REGISTERS, 0x0025, 3)) { Serial.print("failed to read power! "); Serial.println(ModbusRTUClient.lastError()); }else{ uint16_t word1 = ModbusRTUClient.read(); uint16_t word2 = ModbusRTUClient.read(); uint16_t word3 = ModbusRTUClient.read(); uint64_t milliwatt; if(word1 >> 7 == 0){ milliwatt = word1 << 32 | word2 << 16 | word3; }else{ word1 &= 0b01111111; milliwatt = 0b1 << 48 | word1 << 32 | word2 << 16 | word3; } watt = milliwatt/1000.; } return watt; } float readFreq(){ float freq = 0.; if (!ModbusRTUClient.requestFrom(0x01, HOLDING_REGISTERS, 0x0040, 2)) { Serial.print("failed to read frequency! "); Serial.println(ModbusRTUClient.lastError()); }else{ uint16_t word1 = ModbusRTUClient.read(); freq = word1/1000.0; } return freq; } double readEnergy(){ double kwh = 0.; if (!ModbusRTUClient.requestFrom(0x01, HOLDING_REGISTERS, 0x0109, 3)) { Serial.print("failed to read energy! "); Serial.println(ModbusRTUClient.lastError()); }else{ uint16_t word1 = ModbusRTUClient.read(); uint16_t word2 = ModbusRTUClient.read(); uint16_t word3 = ModbusRTUClient.read(); uint64_t dwh = word1 << 32 | word2 << 16 | word3; kwh = dwh/10000.0; } return kwh; }