Featured

Empowering Your Smart Home: Building an Optical Utility Meter Reader

Empowering Your Smart Home: Building an Optical Utility Meter Reader

Please read Liability Disclaimer and License Agreement CAREFULLY

Introduction:

In the era of smart homes and energy efficiency, monitoring and managing your utility consumption has become a key aspect of modern living.

This article introduces a DIY project that combines new technology with a user-friendly approach, presenting an Optical Utility Meter Reader based on the ESP8266 microcontroller, BPW34 photodiode, and OPA2348/OPA2354 operational amplifier.

This intelligent device not only counts LED pulses from your utility meter but also seamlessly connects to your home network via WiFi and MQTT, offering a convenient and efficient way to integrate energy data into your smart home ecosystem.

This device will work only with utility meters that have a LED to indicate the consumption, for example my energy meter has a LED that blinks 1000time/kWh. It is marked on the meter as 1000 imp/kWh followed by a square pulse symbol = 1Wh.

Components and Technology:

  1. ESP8266 for WiFi Connectivity: The ESP8266 is a powerful and cost-effective microcontroller with built-in WiFi capabilities. Leveraging its connectivity features, this Optical Utility Meter ensures seamless communication with your home network, allowing you to access real-time energy data from anywhere.

  2. BPW34 Photodiode for Precision Sensing: The BPW34 photodiode serves as the optical sensor in the meter. Its high sensitivity to light ensures accurate counting of LED pulses from your utility meter. This precision is crucial for providing reliable energy consumption data, allowing you to make informed decisions about your usage patterns.

  3. OPA2348/OPA2354 Op-Amp as a Schmidt Trigger: The OPA2348/2354 op-amp is utilized as a Schmidt trigger, helping to shape and stabilize the incoming signal from the photodiode. This ensures that the ESP8266 receives a clean and well-defined signal for processing, enhancing the overall reliability and accuracy of the meter.

  4. MQTT Integration for Smart Home Connectivity: MQTT (Message Queuing Telemetry Transport) is a lightweight and efficient messaging protocol ideal for IoT devices. The Optical Utility Meter employs MQTT to transmit the pulse count data to a designated MQTT broker. This data can then be easily integrated into your smart home system, providing a comprehensive overview of your energy consumption.

Building the Optical Utility Meter Reader: The construction of this device involves connecting the components, programming the ESP8266 to handle the incoming data, and setting up the MQTT communication. Detailed step-by-step instructions and code snippets will be provided to guide you through the process, making it accessible to both beginners and experienced DIY enthusiasts.

The KiCAD schematic and PCB are provided in order to change them if you feel the need and also to make the understanding of the circuit easier.

The R2 value can be changed depending on the ambient light conditions, the larger it is the larger the voltage at the op-amp, I am using a 1MOhm resistor for R2.

The threshold voltage is set by the voltage divider formed by R10 and R11, I have used 470Ohm for R10 and 10kOhm for R11 to get a trigger voltage of 3.15V and also to be able to keep IO3 of the ESP high at boot.

With the values stated above I can read 1000pulses/sec from a 3mm red led 2cm away from photodiode.

The ESP8266 shall be connect with the VCC pin to the + on the PCB and GND to the - on the PCB. UART is not used so you can also use Receive and Transmit pins as you like.

I have attached also the stl files to 3D print the enclosure parts are: Upper cover and Lower Cover. You will need a small self tapping screw with a diameter smaller than 2mm to fix the PCB in the Lower cover. The Upper Cover, after the power cable is connected through the hole, can be glued in place.

Double sided adhesive tape is used to fix the assembled enclosure on the meter.

Optical Utility Meter Enclosure Back Optical Utility Meter Enclosure Front

I have used JLCPCB to manufacture the PCB and for the first time to 3D print the enclosure parts.

This code is designed to count pulses from a utility meter and publish the total energy consumption to an MQTT broker. It uses an ESP8266 microcontroller, a photodiode, and an op-amp as a Schmidt trigger. The code connects to WiFi, subscribes to an MQTT topic, and sends energy consumption data to the MQTT broker. The pulse count and total energy are stored in EEPROM to persist across power cycles. The code also includes comments to explain each section's purpose and functionality.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <EEPROM.h>

// Uncomment line below for debug
#define DEBUG

// WiFi credentials
constexpr char *clientID = "YOUR_CLIENT_ID";
constexpr char *ssid = "YOUR_NETWORK_SSID";
constexpr char *password = "YOUR_NETWORK PASSWORD";

// MQTT broker details
constexpr char *mqtt_server = "YOUR_MQTT_SERVER_IP";
constexpr char *mqtt_user = "YOUR_MQTT_USER_NAME";
constexpr char *mqtt_pass = "YOUR_MQTT_SERVER_PASSWORD";
// Change name for each sensor
// energy1, energy2 ...
constexpr char *topic_TotalPulses = "energy1/TotalPulses";
constexpr char *topic_setTotalPulses = "energy1/setTotalPulses";
// Pulses per kWh
constexpr char *topic_SendEvery = "energy1/SendEveryPulses";
constexpr char *topic_setSendEvery = "energy1/setSendEveryPulses";
constexpr uint16_t mqtt_port = 1883;

#ifdef DEBUG
// Serial communication speed
constexpr uint32_t serialSpeed = 115200;
#endif

// EEPROM save location
uint8_t saveLocation = 2;
// Pin for interrupt (pulse counting)
constexpr uint8_t interruptPin = 2; // GPIO2

// EEPROM settings
constexpr uint8_t sizeEEPROM = 22;           // EEPROM size in bytes, 2 for SendToEvery, 5 x 4bytes for 5 locations of TotalPulses
constexpr uint8_t sendToEveryLocation = 0;          // EEPROM address for pulses per kWh
constexpr uint8_t totalPulsesLocation = 2;  // EEPROM address for totalEnergy
constexpr uint8_t pulsesLocations = 5;  // EEPROM address for totalEnergy
// Delay for WiFi connection and MQTT reconnect
constexpr uint16_t delayWiFi = 1000;
// Holds the pulses counted value. It is lost at reset or power loss
// as the hardware is lacking power loss ciruit. To overcome this
// the total energy value can be set from MQTT
volatile uint32_t totalPulses = 0;
uint32_t prevPulses = 0;
// If you have a meter with 1000 pulses/kWh you can use 
// sendEveryN_Pulses = 100 to avoid overloading Zigbee network
uint16_t sendEveryN_Pulses = 0;
uint32_t tim1 = 0;

WiFiClient espClient;
PubSubClient client(espClient);

// Function to set up WiFi connection
void setup_wifi() {
  IPAddress staticIP(10, 0, 1, 111);
  IPAddress gateway(10, 0, 1, 1);
  IPAddress subnet(255, 255, 255, 0);
  IPAddress dns(10, 0, 1, 1);
  WiFi.config(staticIP, subnet, gateway, dns);
  WiFi.mode(WIFI_STA);
  WiFi.hostname(clientID);
  WiFi.disconnect();
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    #ifdef DEBUG
      Serial.print(".");
    #endif
  }
}

// Function to reconnect to MQTT broker
void reconnect() {
  while (!client.connected()) {
    if (client.connect(clientID, mqtt_user, mqtt_pass)) {
      client.subscribe(topic_setTotalPulses);
      client.subscribe(topic_setSendEvery);
    } else {
      #ifdef DEBUG
        Serial.print("*");
      #endif
      delay(delayWiFi);
    }
  }
}

// Function to publish total energy to MQTT broker
void mqttPublish() {
  if (client.connected()) {
    client.publish(topic_TotalPulses, String(totalPulses).c_str());
    client.publish(topic_SendEvery, String(sendEveryN_Pulses).c_str());
  } else {
    reconnect();
  }
}

// Interrupt service routine for pulse counting
void ICACHE_RAM_ATTR countPulse() {
  totalPulses++;
}

// Callback function for handling MQTT messages
void callback(char *topic, byte *payload, unsigned int length) {
  // Ignore any callback for 250ms after we publish
  String payloadStr;
  for (int i = 0; i < length; i++) {
    payloadStr += (char)payload[i];
  }
  if (strcmp(topic, topic_setTotalPulses) == 0) {
    totalPulses = payloadStr.toInt();
    #ifdef DEBUG
      Serial.print("callback settotalPulses = "); Serial.println(totalPulses);
    #endif
    saveLocation = 4;
    setTotalPulsesToEEPROM();
  }
  if (strcmp(topic, topic_setSendEvery) == 0) {
    sendEveryN_Pulses = payloadStr.toInt();
    #ifdef DEBUG
      Serial.print("callback setsendEveryN_Pulses = "); Serial.println(sendEveryN_Pulses);
    #endif
    EEPROM.put(sendToEveryLocation, sendEveryN_Pulses);
    EEPROM.commit();
  }
}

uint32_t getTotalPulsesFromEEPROM(uint8_t crtLocation) {
  uint32_t value = 0;
  uint8_t location = 2 + (crtLocation * 4);
  EEPROM.get(location, value);
  return value;
}

void setTotalPulsesToEEPROM() {
  saveLocation++;
  if(saveLocation == pulsesLocations){
    saveLocation = 0;
  }
  #ifdef DEBUG
    Serial.print("Save location ");Serial.println(saveLocation);
  #endif
  // Volatile variable will not work here
  uint32_t val = totalPulses;
  EEPROM.put(2 + (saveLocation * 4), val);
  EEPROM.commit();
}

void setup() {
  #ifdef DEBUG
    Serial.begin(serialSpeed);
    delay(1000);
  #endif
  EEPROM.begin(sizeEEPROM);
    // Get the number of pulses
  EEPROM.get(sendToEveryLocation, sendEveryN_Pulses);

  // Get the last saved energy
  //0-1 2bytes
  //2-5   4bytes Location 0
  //6-9   4bytes Location 1
  //10-13 4bytes Location 2
  //14-17 4bytes Location 3
  //le18-21 4bytes Location 4
  for(uint8_t cnt = 0; cnt < pulsesLocations; cnt++) {
    // Get the value from EEPROM
    uint32_t val = getTotalPulsesFromEEPROM(cnt);
    // If value > totalPulses then we have a location
    if(val > totalPulses) {
      totalPulses = val;
      saveLocation = cnt;
    }
  }
  // Set up the interrupt
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), countPulse, FALLING);
  // Start WiFi
  setup_wifi();
  // Start MQTT
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  // Add your MQTT broker username and password
  reconnect();
  //client.connect(clientID, mqtt_user, mqtt_pass);
  mqttPublish();
  #ifdef DEBUG
    Serial.print("sendEveryN_Pulses ");Serial.println(sendEveryN_Pulses);
    Serial.print("totalPulses ");Serial.println(totalPulses);
    Serial.println("Done setup");
    Serial.println("_________________________________________");
  #endif
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  // This condition is for low power consumption
  if(prevPulses != totalPulses) {
    // Check if we need to publish
    if ((totalPulses % sendEveryN_Pulses) == 0) {
      mqttPublish();
      setTotalPulsesToEEPROM();
    }
    prevPulses = totalPulses;
  }
  client.loop();
  yield();
}

Feel free to add debugging messages like:

if (!client.connected()) {
   Serial.println("MQTT connection lost. Reconnecting...");
   reconnect();
}

Or use proper MQTT topics name

// Dynamically create MQTT topic based on device ID
constexpr char *mqtt_topic_energy = "your_mqtt_base_topic/ESP8266DeviceID/energy";

Remember to thoroughly test any modifications to ensure they align with your project requirements. Additionally, consider the specific needs of your application and adjust the code accordingly.

Conclusion: By building your Optical Utility Meter with ESP8266, BPW34 photodiode, and OPA2348 op-amp, you take a significant step towards creating a smarter, more energy-efficient home. The integration of WiFi and MQTT ensures that you can effortlessly monitor and analyze your energy consumption data within your existing smart home ecosystem. Empower your living space with data-driven insights and contribute to a more sustainable and efficient future.

Comments powered by CComment