Jasper's gas, water, and electricity meter

Updated 4 Feb 2014

Short description: My Mbed microcontroller posts electricity, water and gas usage, indoor temperature, humidity, air pressure, light intensity and air quality, PIR motion activity to Xively.com every 60 seconds. The LED on my electricity meter flashes 1 time per Wh. You can calculate current power cosumption from the time between two flashes. I used a simple Light Dependent Resistor (LDR)  as a sensor for the flashes. For water and gas sensors I use CNY70 photoreflectors that measures the disc rotations of my meters.  Light and temperature are measured in the livingroom and connected to the Mbed through an unused telephone cable. For light intensity a LDR is used.  For temperature I use a DS18B20. For humidity a DHT11 was used.  For air quality I use a MQ-135 which has an analog output. The MQ-135 is compensated for dependency on temperature and humidity. For air pressure I use a BMP085 connected to the Mbed through I2C. My latest additions are a wireless PIR motion detector and a mail box sensor that detects when new mail has arrived.

Hardware:

I designed a custom PCB and ordered it from Itead Studio PCB in China.

Schematic in pdf, schematic in Eagle, pcb layout in Eagle.

Below is the PCB assembly inside the arduino mega enclosure and screwed to the wall. The sensor wires are connected using screw terminals. On the left is the air quality sensor. On the right is the humidty sensor. The Mbed is placed on header connectors onto my custom designed PCB. On bottom is the ethernet connector and a power supply connector.

Below left is my electricity meter on which a LED flashes 1000 times per kWH. I placed 4 powerfull magnets on the metal plate. In front of that I placed a experiment board with and 4 magnets glued to it and a LDR (light dependent resistor). The LDR detects the flashes.It is basically at voltage divider. When the LED flashes the output voltage goes high. This triggers an interrupt in the program where the time between subsequent flashes is measured and the power is calculated. The formula is watt=3600/time_s.

   

Below is the telephone plug with a DS18S20 temperature sensor and LDR. It was not nescessary to modify the telephone wiring inside my rental house. I only needed to wire my meter to the telephone connector, not change the wiring in my home.

  

Below is a picture of my water meter. It has a disc that rotates one time per liter. It is half reflective so can you use a photoreflector sensor to detect rotations. I placed a piece of experiment board with holes over the plastic pins to position the CNY70 photoreflectosensor exactly above the rotating disc. There was no need to modify the water meter.

Below is the schematic of the CNY70 that was used to detect the water. The photo transistor detects the reflected IR light and this pulls the output signal low. The resistor values were empirically determined and the change in output was maximized. The program samples the output signal 4 times per seconds which is sufficient for water flows up to 60 liter per minute. The program creates two states, high and low, using two threshold levels, or hystereses. These levels were detemined manually. Every time the state changes 0.5 liter water is added to the liter counter.

Below is the display of my gas meter. The third digit after the comma indicates the liters of gas that are used. It has a 3 by 3 mm refelective window behind the "6" that can be detected using the CNY70. Every revolution is 10 liter gas. Again there is no need to modify the meter.

Below left is the schematic of the CNY70 that was used to detect the gas usage. The resistor values were empirically determined and the change in output was maximized. Below right is the analog signal measured with a oscilloscope. The signal is sampled at 10 Hz. This is sufficient to detect gas usage up to 100 liter gas per minute. The program creates two states, high and low, using two threshold levels, or hystereses. Every time the state changes to low, 10 liter gas is added to the gas counter. 

Below left and middle is the PIR movement detector connected to a 433 MHz wireless transmission module using a PT2262 IC. The PIR sensor is powered from an external 5V adapter. Below right is the mail box sensor. It sends a 433 MHz signal as long as the mail box is opened using a tilt (mercury) switch.

Here's a close up picture of the mail box sensor. It is powered from two CR2016 batteries (not placed in the battery holder) and it has a mercury tilt switch.

 

Below is the program code

#include "mbed.h"
#include "BMP085.h"         // required for BMP085 air pressure sensor
#include "EthernetNetIf.h"  // required for ethernet
#include "HTTPClient.h"     // required for Xively
#include "DS18B20.h"        // required for DS1820 temperature sensor
#include "OneWireDefs.h"    // required for DS1820 temperature sensor
#include "DHT.h"            // required for DHT11 humidity sensor
I2C i2c(p9, p10);           // sda,scl required for BMP085 air pressure sensor
BMP085 jasper(i2c);         // required for BMP085 air pressure sensor

#define THERMOMETER DS18B20 // required for DS18B20 temperature sensor

DigitalOut led1(LED1, "led1"); // Mbed LED1 to show mbed is alive
DigitalOut led2(LED2, "led2"); // Mbed LED2 used for electricity
DigitalOut led3(LED3, "led3"); // Mbed LED3 used for pachube
DigitalOut led4(LED4, "led4"); // Mbed LED4 used for water

InterruptIn elect_pin(p8);     // electricitymeter flash detector (LDR)
DHT sensor(p11,DHT11);         // Pin of the DHT11 temp~humidity sensor
// DigitalOut DS1820(p12);     // Pin for DS1820 temp sensor is mentioned below
AnalogIn water_pin(p15);       // water meter IR reflection sensor
AnalogIn gas_pin(p16);         // gas meter IR reflection sensor
AnalogIn AirQ_pin(p17);        // Pin of the Air quality sensor
DigitalOut UnusedDigPin18(p18);// unused analog pin set at digital out to reduce analog noise
AnalogIn light_pin(p19);       // LDR on analog pin
DigitalOut UnusedDigPin20(p20);// unused analog pin set at digital out to reduce analog noise
DigitalIn PIR_pin(p27);        // IR movement detector
DigitalIn mail_pin(p28);       // detects if snail mail has arrived
DigitalOut LEDY(p29);          // ethernet LED yellow
DigitalOut LEDG(p30);          // ethernet LED green

EthernetNetIf eth; // use DHCP assinged IP address and network mask and gateway

/*EthernetNetIf eth(     // Use fixed IP address
    IpAddr(10,0,0,100),  // IP Address
    IpAddr(255,255,255,0)// Network Mask
    IpAddr(10,0,0,1),    // Gateway
    IpAddr(10,0,0,1)     // DNS
);*/

HTTPClient client;  // for authentication, API key is set in client header
Timer t_elect;      // timer to measure time between electricity meter flashes
Timer t_water;      // timer to set interval time to measure water
Timer t_gas;        // timer to set interval time to measure
Timer t_other;      // timer to set interval time to measure DS1820temp/DHTtemp/light/pressure/airquality
Timer t_pachube;    // timer to set interval to post values to pachube
Timer t_timeout;    // timer to stop initilazing ds1820

class Watchdog
{
public:// Load timeout value in watchdog timer and enable
    void kick(float s) {
        LPC_WDT->WDCLKSEL = 0x1;                // Set CLK src to PCLK
        uint32_t clk = SystemCoreClock / 16;    // WD has a fixed /4 prescaler, PCLK default is /4
        LPC_WDT->WDTC = s * (float)clk;
        LPC_WDT->WDMOD = 0x3;                   // Enabled and Reset
        kick();
    }// end of void kick(float s)
    void kick() {
        LPC_WDT->WDFEED = 0xAA;// "kick" or "feed" the dog - reset the watchdog timer
        LPC_WDT->WDFEED = 0x55;// by writing this required bit pattern
    }  // end of void kick()
}; // end of class watchdog

float watt,power,vermogen,time_s,waterread,gasread,lwpm,lgpm,literwater,litergas,DS1820temp;
float light,AirQ,adjAirQValue,BMP085pressure,BMP085temp,BMP085press,RoRs;
float DHTtemp,DHThumidity,DHTdewpoint,humidity;
float waterthresholdmax,waterthresholdmin;
float gasthresholdmax,gasthresholdmin,PIR,mail;
int logPower,calculationinvalid,waterstatus,gasstatus,kWhpd,lgpd,lwpd,tic;//initialize global variables

void int_elect()  // interrupt that measures time between flashed of the electricity meter
{
    time_s = t_elect.read();    // read electricity timer and store in global variable
    t_elect.reset();            // reset electricity timer
    watt=3600/time_s;           // calculate Watt
    if (watt>1&&watt<9200) {     // 40A * 230V = 9200 W max
        power=watt;             // quickly copy watt to power
        logPower=1;             // tell the main loop to log the power
        kWhpd=kWhpd+0.001;      //used to measure kiloWatthour per day
        //printf("another Wh\n");
    }
}

Watchdog wdt; // Setup the watchdog timer

int main()
{
    printf("Start main\n" );    // print to serial port
    Timer tm;                   // timer to show that mbed is alive
    printf("Start ethernet\n" );// print to serial port
    eth.setup();                // ethernet setup
    calculationinvalid=true;    // the first time the power must not be calculated
    elect_pin.rise(&int_elect); // attach the address of the flip function to the rising edge
    char buffer [50];           // string containing datastream values
    UnusedDigPin18=0;           // set output low
    UnusedDigPin20=0;           // set output low
    PIR=0;                      // default value
    mail=0;                     // default valu

    tm.start();         //start timer to show we're alive
    t_elect.start();    //start electricity timer to measure power
    t_other.start();    //start timer to measure other seonsors
    t_pachube.start();  //start timer that triggers a post to pachube interval
    t_water.start();    //start timer that triggers a water read interval
    t_gas.start();      //start timer that triggers a gas read interval

// On reset, indicate a watchdog reset or a pushbutton reset on LED 4 or 3
    if ((LPC_WDT->WDMOD >> 2) & 1)
        printf("watchdog time out reset\n");// used for debugging
    else printf("normal button reset\n");   // used for debugging;
    wdt.kick(60.0);                         // setup a 60s timeout on watchdog timer hardware

    THERMOMETER device(true, true, false, p12);// device( crcOn, useAddress, parasitic, mbed pin )
    printf("Start DS1820\n" );                 // print to serial
    while (!device.initialize());              // keep calling until it works
    device.setResolution(twelveBit);           // sets the resolution
    printf("DS1820 Found \n" );                // print to serial port

    while (true) {//continuous loop
        if (t_gas.read()>0.10) {            // 10 times per second.
            t_gas.reset();                  // reset time
            gasread=gas_pin.read();         // read analog value and store
            //printf("gasread: %f\n",gasread);//used for debugging
            gasthresholdmin=0.45;           // manually set thresholdmin
            gasthresholdmax=0.51;           // manually set thresholdmax
            if (gasread>gasthresholdmax) {  // if analog read is above the high threshold
                gasstatus=1;                // then status is high
            }
            if (gasread<gasthresholdmin) {  // if analog read is below the low threshold
                if (gasstatus==1) {         // and if the status is high
                    gasstatus=0;            // then status is low
                    litergas=litergas+10;   // add 10 liter gas
                    //printf("an read +10 liter gas\n");
                }
            }
        }

        if (t_water.read()>0.25) {              // 4 times per second. Max is 27 lwpm in my home
            t_water.reset();                    // reset time
            waterread= water_pin.read();         // read analog value and store
            //printf("waterread: %f\n",waterread);//used for debugging
            waterthresholdmin=0.22;             // manually set thresholdmin
            waterthresholdmax=0.39;             // manually set thresholdmax
            if (waterread>waterthresholdmax) {  // if analog read is above the high threshold
                if (waterstatus==0) {           // and if the status is low
                    waterstatus=1;              // then waterstatus is high
                    literwater=literwater+0.5;  // add 0.5 liter
                    //printf("+0.5 liter water\n");
                    lwpd=literwater;
                    //led4=!led4;                 // toggle led
                }
            }
            if (waterread<waterthresholdmin) {  // if analog read is below the low threshold
                if (waterstatus==1) {           // and if the status is high
                    waterstatus=0;              // then the waterstatus is low
                    literwater=literwater+0.5;  // add 0.5 liter
                    //printf("+0.5 liter water\n");
                    lwpd=literwater;            //used to measure liter water per day
                    //led4=!led4;                 // toggle led
                }
            }
        }

        if (tm.read()>1.0) {    // after every second
            tm.reset();         // reset time
            led1=!led1;         // Show that we're alive Blue LED of mbed
            LEDG=!LEDG;         // Show that we're alive Green LED of ethernet connector
            if(PIR_pin==true) { // check status of PIR_pin
                PIR=1;          // set variable
            }
            if(mail_pin==true) {// check status of mail_pin
                mail=1;         // set variable
            }
        }

        if (t_other.read()>29) {                     // timer for other sensors
            t_other.reset();                         // reset time for other sensors

            //DS1820 temperature
            DS1820temp = device.readTemperature(); // read DS1820 value and store
            printf("DS1820 Temp: %.4f C\n",DS1820temp); // used for debugging

            // Light brightness livingroom
            light=255*light_pin.read();         // 12bit=4096
            printf("LDR Light: %3.0f\n",light);  //used for debugging

            // DHT11 huminity sensor
            sensor.readData();
            DHTtemp = sensor.ReadTemperature(CELCIUS);
            DHThumidity = sensor.ReadHumidity();
            //DHTdewpoint =sensor.CalcdewPointFast(sensor.ReadTemperature(CELCIUS), sensor.ReadHumidity());
            if(DHThumidity>0&&DHThumidity<100) {
                humidity=DHThumidity;
                printf("DHT11 Humidity: %2.0f percent\n", humidity);//used for debugging
            }
            if(DHTtemp>0&&DHTtemp<100) {// sanity check
                DHTtemp=DHTtemp;        // this is bad!
                printf("DHT11 Temp: %2.0f degC \n", DHTtemp);//used for debugging
            } else {
                printf("Error reading DHT11\n");//used for debugging
            }

            // Air quality MQ-135
            AirQ=255*AirQ_pin.read();    // read analog value of the MQ-135
            printf("AirQ: %f\n",AirQ);   // the uncompensated air quality value
           
            //compensate the air quality for temperature and humidity
            if (DS1820temp >= 20.0 && DS1820temp <= 50.0) {
                RoRs = -0.0034 * DS1820temp + 1.067;
            } else if (DS1820temp >= -10.0 && DS1820temp <= 5.0) {
                RoRs = -0.0300 * DS1820temp + 1.4;
            } else if (DS1820temp >= 5.0 && DS1820temp <= 20.0) {
                RoRs = -0.0167 * DS1820temp + 1.333;
            } else {
                RoRs = 1;
            }
            RoRs= RoRs * (-0.001923 * humidity + 1.0634);
            adjAirQValue = AirQ * RoRs;
           
            printf("Adjusted AirQ: %f\n",adjAirQValue);   // used for debugging

            jasper.update();
            BMP085pressure = jasper.get_pressure();
            if(BMP085pressure>800&&BMP085pressure<1200) { //sanity check
                BMP085press=BMP085pressure;
            }
            BMP085temp = jasper.get_temperature();
            printf("BMP085 Temp: %4.2f degC \n", BMP085temp);//used for debugging
            printf("BMP085 Press: %4.2f HPa \n", BMP085press);//used for debugging
        }

        if (logPower==1) {                  // this code runs when the electricity interrupt sets a flag
            logPower=0;                     // clear the flag
            if (calculationinvalid==false) {// if the power is reliable
                vermogen=power;             // copy power to vermogen
                //led2 = !led2;             // indicate that power has been measured
            }                               // end of if (calculationinvalid=false)
            calculationinvalid=false;       // power measurements after the first are reliable
        }                                   // end of if(logPower==1)

        if (t_pachube.read() > 60) {        // sends latest values to pachube every 60 seconds
            t_pachube.reset();              // reset the pachube timer
            lwpm=literwater;                // liter water per minute
            lgpm=litergas;                  // liter gas per minute
            literwater=0;                   // clear liter water per minute
            litergas=0;                     // clear liter gas per minute
            LEDY=false;                     // This LED is on when a internet the data was succesfully sent to Pachube, now its turned OFF
            string apiKey = ""; // my API key for COSM
            string environmentID = ""; // my feed ID for COSM
            sprintf (buffer,"%.0f,%.4f,%.1f,%.0f,%3.0f,%6.2f,%2.0f,%.0f,%1.0f,%1.0f",vermogen,DS1820temp,lwpm,lgpm,light,BMP085press,humidity,adjAirQValue,PIR,mail);// create string from floats
            printf("Sending to pachube: %s\n",buffer); //for debugging
            string data = buffer;           //put datastream value into string
            HTTPClient client;
            client.setRequestHeader("X-PachubeApiKey", apiKey);
            HTTPText csvContent("text/csv");
            csvContent.set(data);
            string uri = "http://api.pachube.com/v1/feeds/" + environmentID + ".csv?_method=put";
            HTTPResult result = client.post(uri.c_str(), csvContent, NULL);
            int response = client.getHTTPResponseCode();
            if (response==200) {
                LEDY=true;   // This LED is on when a internet the data was succesfully sent to Pachube
            }
            printf("Pachube response: %i\n",response);     
            PIR=0;
            mail=0;

            //led3=!led3;                   //toggle led
        }
        // End of main loop so "kick" to reset watchdog timer and avoid a reset
        wdt.kick();
    }//end of while(true) loop
}//end of main