Navigation

    • Register
    • Login
    • OpenHardware.io
    • Categories
    • Recent
    • Tags
    • Popular
    1. Home
    2. dpcr
    • Profile
    • Following
    • Followers
    • Topics
    • Posts
    • Best
    • Groups

    dpcr

    @dpcr

    5
    Reputation
    38
    Posts
    669
    Profile views
    1
    Followers
    0
    Following
    Joined Last Online

    dpcr Follow

    Best posts made by dpcr

    • Gas Meter Reading Using a Magnetometer

      I was trying to find a way to measure the amount of gas our gas meter was using. I checked out several ways but they all involved using either a hall sensor or a reed switch or a IR sensor or a light sensor. But all of these would not work for my application for a number a reasons. First my meter is outside, second there is no magnet in my meter, third the utility company will not allow us to attach any device to the dials or the electronics of the meter (US smart meter). By the way my meter is a Rockwell 250 (Siemens). So after much researching I'm using a HMC5883L magnetometer. It measures very low magnetic fields.

      Please forgive my lack of pictures and links but when I was assembling it I never thought about it. I can get some pictures of the finished sensor if needed.

      I found the sweet spot on my gas meter using an Android app on my phone called Sensors Box, which allows one to access the magnetometer inside of the phone. While the gas was flowing I just moved it around over the meter until I found a fluctuation that coincided with the gas flow. This is what I call the "sweet spot". I then built a sensor using the HMC5883L per (https://www.sparkfun.com/tutorials/301). But as of this writing I found that they are no longer making it but do have a replacement (https://www.sparkfun.com/products/retired/10530). I have it successfully connected to my serial gateway and am using Domoticz as a controller.

      The initial problem I found was trying to convert the output a the magnetometer to something useful. The data that comes out is stream of numbers from three vectors that goes up and down. After looking at them in the serial plotter of the Arduino IDE I found one vector (y in my case) that looked like a sine wave. Then with a lot of help from my son, who wrote most of the code for the Arduino, it seems to be putting out decent data. However it does need to be fine tuned.

      What we ended up doing is auto detecting the top an bottom of the sine wave, then dividing it up into 10 pieces to get useful pulses.

      I'm working on getting the Metric and English units out put correct but for now the metric units work OK. Any input is welcome:

      UPDATE: 3/12/17 - we have been using the most recent sketch (v.6) and it has been running for two weeks now without a problem. I do plan some updates to the code that I hope will include an interactive serial monitor to set up the sensor for the first use. Also if you have looked at the picture of the sensor it is quite large and I would like to make it a bit smaller.

      Does anyone have any idea how to make it smaller yet water proof?

      /*
       * 
       * 
       * 
       * 
       * Currently the autoDetectMaxMin in set to true which will find the TOP and BOTTOM of the wave, however if you want 
       * to use it the gas must be flowing.
       */
      
      
      
      #define MY_DEBUG
      #define MY_RADIO_NRF24
      
      #include <MySensors.h>                  
      #include <Wire.h>                       //I2C Arduino Library
      
      #define CHILD_ID 1                      //ID of the sensor child
      #define SLEEP_MODE false                //prevent sensor from sleeping
      #define address 0x1E                    //0011110b, I2C 7bit address of HMC5883
      
      int TOP = 0;                            //highest magnetic field registered from meter (Ga)Initialize low if using AutoDetectMaxMin
      int BOTTOM = 10000;                     //lowest magnetic field registered from meter (Ga) Initialize high if using AutoDetectMaxMin
      int tol = 50;
      unsigned long SEND_FREQUENCY = 30000;   // Minimum time between send (in milliseconds). We don't want to spam the gateway.
      
      bool metric = true;                     //sets units to Metric or English
      bool autoDetectMaxMin = true;           //lets Arduino decide the values for TOP and BOTTOM
      bool pcReceived = false;                //whether or not the gw has sent us a pulse count
      bool rising = true;                     //whether or not a pulse has been triggered
      bool inside = true;                     //whether the magnetic field is within TOP and BOTTOM limits
      unsigned long pulsecount = 0;           //total number of pulses measured ever
      unsigned long oldPulseCount = 0;        //old total
      double vpp = metric ? 0.17698 : 0.00625;//Volume of gas per pulse (Rockwell gas meter 250)
      unsigned long lastSend = 0;             //time since last transmission - msec
      double volume = 0;                      //Cumulative amount of gas measured
      const int len = 3;                      //number of flow rate measurements to save
      double flow [len];                      //array of previous gas flow rate measurements
      double avgFlow = 0;                     //average of all elements in flow array
      double oldAvgFlow = 0;                  //previous average flow
      int divider = 1;                        //Current divider
      int totDividers = 10;                    //Number of dividers
      int increment = (TOP - BOTTOM) / totDividers;   //space b/w dividers
      
      MyMessage flowMsg(CHILD_ID,V_FLOW);
      MyMessage volumeMsg(CHILD_ID,V_VOLUME);
      MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
      
      void setup(){
        //Initialize Serial and I2C communications
        Serial.begin(9600);
        Wire.begin();
      
        // Fetch last known pulse count value from gw
        request(CHILD_ID, V_VAR1);
        
        //Put the HMC5883 IC into the correct operating mode
        Wire.beginTransmission(address); //open communication with HMC5883
        Wire.write(0x02); //select mode register
        Wire.write(0x00); //continuous measurement mode
        Wire.endTransmission();
        
        int y = 0;
        int oldy = 0;
      
        //WARNING: MAKE SURE GAS IS RUNNING IF USING THIS OPTION!!!
        if(autoDetectMaxMin){
          //determine max and min magnetic field strength over a few minutes
          lastSend = millis();
          
          while(millis() - lastSend < 300000){
            y = readMag();
            if(y > TOP){
              TOP = y;                        //update TOP if new max has been detected
            }
            else if(y < BOTTOM){
              BOTTOM = y;                     //update BOTTOM if new min has been detected
            }
          }
          
          TOP -= tol;                         //nudge TOP and BOTTOM so that they have a chance of being triggered
          BOTTOM += tol;
      
          increment = (TOP - BOTTOM) / totDividers;    //recalculate increment to match new TOP and BOTTOM
          autoDetectMaxMin = false;           //finished determining TOP and BOTTOM
        }
      
        Serial.print("Increment = ");
        Serial.println(increment);
        y = readMag();
        oldy = readMag();
        while(abs(y - oldy) < increment / 2){ //wait until difference b/w y and oldy is greater than half an increment
          y = readMag();
        }
        rising = (y > oldy);
        Serial.println(rising ? "Magnetic field is rising" : "Magnetic field is falling");
      }
      
      void presentation()
      {
          // Send the sketch version information to the gateway and Controller
          sendSketchInfo("Gas Meter", "0.4");
      
          // Register this device as Gas sensor
          present(CHILD_ID, S_GAS);
      }
      
      void loop(){
        if (!pcReceived) {
          //Last Pulsecount not yet received from controller, request it again
          request(CHILD_ID, V_VAR1);
          return;
        }
        //detecting magnetic pulses - Fractional Simple Method
        while(millis() - lastSend < SEND_FREQUENCY){
          int y = readMag();
      
          if(inside && rising && y > BOTTOM + divider * increment){
            divider++;
            pulsecount++;
          }
          else if(inside && !rising && y < TOP - divider * increment){
            divider++;
            pulsecount++;
          }
      
          if(inside && (y > TOP || y < BOTTOM)){        //switch directions once TOP or BOTTOM divider has been reached
            inside = false;                 //keep this from happening multiple times once signal exceeds TOP or BOTTOM
            Serial.println("OUTSIDE");
          }
          else if(!inside && (y < TOP - increment / 2 && y > BOTTOM + increment / 2)){
            rising = !rising;
            divider = 1;
            inside = true;
            Serial.println("INSIDE");
          } 
        }
        
        //shift all flow array elements to the right by 1, ignore last element
        for(int idx = len; idx > 0; idx--){
          flow[idx] = flow[idx - 1];
        }
        //calculate newest flow reading and store it as first element in flow array
        flow[0] = (double)(pulsecount - oldPulseCount) * (double)vpp * 60000.0 / (double)SEND_FREQUENCY;
        //display flow array state
        Serial.print("Flow Array State: [");
        for(int idx = 0; idx < len - 1; idx++){
          Serial.print(flow[idx]);
          Serial.print("|");
        }
        Serial.print(flow[len]);
        Serial.println("]");
        //calculate average flow
        avgFlow = 0;                                //reset avgFlow
        for(int idx = 0; idx < len; idx++){         //calculate sum of all elements in flow array
          avgFlow += flow[idx];
        }
        avgFlow /= len;                             //divide by number of elements to get average
        Serial.print("Average flow: ");             //display average flow
        Serial.println(avgFlow);
        //send flow message if avgFlow has changed
        if(avgFlow != oldAvgFlow){
          oldAvgFlow = avgFlow;
          send(flowMsg.set(avgFlow, 2));
        }
      
        //send updated cumulative pulse count and volume data, if necessary
        if(pulsecount != oldPulseCount){
          oldPulseCount = pulsecount;              //update old total
          
          //calculate volume
          volume = (double)oldPulseCount * (double)vpp / 1000.0;
      
          //send pulse count and volume data to gw
          send(lastCounterMsg.set(pulsecount));
          send(volumeMsg.set(volume, 3));
        }
      
        lastSend = millis();
        
      }
      
      void receive(const MyMessage &message)
      {
        if (message.type==V_VAR1) {
          unsigned long gwPulseCount=message.getULong();
          pulsecount = gwPulseCount;
          oldPulseCount = pulsecount;
          Serial.print("Received last pulse count from gw:");
          Serial.println(pulsecount);
          pcReceived = true;
          lastSend = millis();
        }
      }
      
      int readMag(){
        int x = 0, y = 0, z = 0;
        
        //Tell the HMC5883 where to begin reading data
        Wire.beginTransmission(address);
        Wire.write(0x03); //select register 3, X MSB register - was called Wire.send but the compiler had an error and said to rename to to Wire.write
        Wire.endTransmission();
      
        //Read data from each axis, 2 registers per axis
        Wire.requestFrom(address, 6);
        if(6<=Wire.available()){
          x = Wire.read()<<8; //X msb
          x |= Wire.read(); //X lsb
          z = Wire.read()<<8; //Z msb
          z |= Wire.read(); //Z lsb
          y = Wire.read()<<8; //Y msb
          y |= Wire.read(); //Y lsb
        }
      
        if(!autoDetectMaxMin){
          //show real-time magnetic field, pulse count, and pulse count total
          Serial.print("y: ");
          Serial.print(y);
          Serial.print(rising ? "  Rising, " : "  Falling, ");
          Serial.print("next pulse at: ");
          Serial.print(rising ? BOTTOM + divider * increment : TOP - divider * increment);
          Serial.print("  Current Number of Pulses: ");
          Serial.print(pulsecount - oldPulseCount);
          Serial.print("  Last Total Pulse Count Sent to GW: ");
          Serial.println(oldPulseCount);
        }
        else{
          //show real-time magnetic field, TOP, BOTTOM, and time left in auto-detect mode
          Serial.print("y: ");
          Serial.print(y);
          Serial.print("  TOP: ");
          Serial.print(TOP);
          Serial.print("  BOTTOM: ");
          Serial.print(BOTTOM);
          unsigned long remainingTime = 300000 + lastSend - millis();
          Serial.print("  Time remaining: ");
          Serial.print(remainingTime / 60000);
          Serial.print(":");
          remainingTime = (remainingTime % 60000) / 1000;
          if(remainingTime >= 10){
            Serial.println(remainingTime);
          }
          else{
            Serial.print("0");
            Serial.println(remainingTime);
          }
          
        }
        
        return y;
       
      }
      
      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @gohan I had to use a magnetometer because my meter did not have a magnet in it. I tried a reed switch but it never actuated and my meter is outside. So I was stuck using a magnetometer which I mounted in a water proof plastic box. The issue I had was getting the sine wave to create a pulse that was usable for MySensors. I did look at using an inverse sine, or as someone else mentioned, use an exponential moving average of the data but they both failed. When the gas stops flowing, a pilot light as an example as seen in the attached picture, there is little to no gas flowing so the angle doesn't change much. It looks like no gas is flowing when it actually is. I decided to go with dividing the wave or meter movement into separate divisions to create a more accurate way of measuring the gas flow in all flow situations.

      I haven't had a chance to test the accuracy of my math against the gas meter but I will at some point. The issue I'm currently having is that the bellows inside of the meter that the magnetometer is reading do not move at the same rate at all times. This causes the FLOW output to change when I know the gas is flowing at a steady rate. The VOLUME doesn't seem to be effected too much. So what I did was to take a moving average of the last three FLOW readings. This helps a little.

      The hardware and ideas were done by me but the coding was done by my 24 year old son. I always enjoy a challenge. Please feel free to comment.

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @markcame Updated sketch:

      It sounds like your pulse count is not correct, this could probably be due to your top and bottom values being incorrect.

      One hardware change was to add some memory because the EEPROM can only handle so many cycles. I used Adafruit I2C FRAM but you can use whatever you like. This sketch uses Adafruit I2C FRAM, what I had.

      There have been many changes since the last sketch. We found that the magnetism values were changing due to changing outside air temperatures (the top and bottom values are very important). When the arduino is turned on for the very first time (top and bottom values are not in memory) it will run for a period of 2 minutes where it will find the top and bottom values, so the gas has to be flowing during that time (turn the stove on). It then stores those values in memory. Going forward we calculate the top and bottom values on the fly to keep them updated as the magnetism values change with the outside air temperature. If not it would go through a 'auto program' every time there was a reset. The gas has to be flowing during the 'auto program' and if its summer and no gas is flowing your top and bottom values will be incorrect. During testing I powered the arduino with the serial connector to get the serial data. It is currently powered with a separate 3.3 v wall wart.

      We removed several bugs and updated the way it calculates pulses and put a smoothing on the FLOW output.The smoothing help keep the FLOW values going down to 0 but doesn't stop it at all times.

      I uploaded this to the Arduino to first verify the correct vector. I could have use a total vector calculation (square root of x2+y2+z2) but this would have just amplified some of the noise. Using a chosen vector worked fine but took a little more work. After finding the correct vector (sorry - first build and mount the magnetometer on the meter), download the sketch below.

      /*This version is different in several ways.
       * first is is using some memory (Adafruit I2C FRAM) memory to store the top and bottom. This is used to keep the top and bottom
       * values after a restart. I guess we could have put them on the controller but can't remember why we didn't.
       * second it will only calculate the top and bottom if the values are not in memory.
       * third it auto calculates the top and bottom values as it is running. 
       * the only settings that should be changed are vpp, SEND_FREQUENCY, and metric. However I am currently using
       * Domoticz as my controller and it doesn't seem to accept English units well so for not I'm using Metric.
       */
      
      
      
      #define MY_DEBUG
      //#define MY_DEBUG_VERBOSE
      #define MY_RADIO_NRF24
      
      #include <MySensors.h>                  
      #include <Wire.h>                       //I2C communications library
      #include <Adafruit_FRAM_I2C.h>          //Adafruit FRAM memory library
      
      #define CHILD_ID 1                      //ID of the sensor child
      #define SLEEP_MODE false                //prevent sensor from sleeping
      #define address 0x1E                    //0011110b, I2C 7bit address of HMC5883 magnetometer
      
      int top = 0;                            //highest magnetic field registered from meter (Ga)Initialize low if using autoDetectMaxMin
      int bottom = 0;                         //lowest magnetic field registered from meter (Ga) Initialize high if using autoDetectMaxMin
      int tol = 50;
      unsigned long SEND_FREQUENCY = 30000;   // Minimum time between send (in milliseconds). We don't want to spam the gateway.
      
      bool metric = true;                     //sets units to Metric or English TODO: move to void setup()
      bool pcReceived = false;                //whether or not the gw has sent us a pulse count
      bool autoDetect = false;                //true if the program is auto detecting Top and Bottom
      bool rising = true;                     //whether or not a pulse has been triggered
      bool safe = false;                      //whether or not it is safe to switch directions
      unsigned long pulsecount = 0;           //total number of pulses measured ever
      unsigned long oldPulseCount = 0;        //old total
      double vpp = metric ? 0.160891193181 : 0.00568124219857;//Volume of gas per pulse
      unsigned long lastSend = 0;             //time since last transmission - msec
      double volume = 0;                      //Cumulative amount of gas measured
      const int len = 3;                      //number of flow rate measurements to save
      double flow [len];                      //array of previous gas flow rate measurements
      double avgFlow = 0;                     //average of all elements in flow array
      double oldAvgFlow = 0;                  //previous average flow
      int y = 0;                              //magnetic field reading
      int oldy = 0;                           //previous magnetic field reading
      int newTop = -9000;                         //potential new Top
      int newBottom = 9000;                  //potential new Bottom
      int totDividers = 10;                   //Number of dividers
      int increment = 0;                      //space b/w dividers
      int counter = 0;                        //used to count pulses over periods longer than SEND_FREQUENCY
      int topAddr = 0;                        //address of TOP in FRAM
      int bottomAddr = topAddr + 2;           //address of BOTTOM in FRAM
      
      MyMessage flowMsg(CHILD_ID,V_FLOW);
      MyMessage volumeMsg(CHILD_ID,V_VOLUME);
      MyMessage lastCounterMsg(CHILD_ID,V_VAR1);
      
      Adafruit_FRAM_I2C fram = Adafruit_FRAM_I2C();
      
      void setup(){
        //Initialize Serial, I2C, and FRAM communications
        Serial.begin(9600);
        Wire.begin();
        fram.begin();
      
        // Fetch last known pulse count value from gw
        request(CHILD_ID, V_VAR1);
        
        //Put the HMC5883 IC into the correct operating mode
        Wire.beginTransmission(address); //open communication with HMC5883
        Wire.write(0x02); //select mode register
        Wire.write(0x00); //continuous measurement mode
        Wire.endTransmission();
      
        //get Top and Bottom from FRAM. addresses are hard-coded seeing as there are only 2
        newTop = readInt(topAddr);
        newBottom = readInt(bottomAddr);
        updateBounds();
        
        //WARNING: MAKE SURE GAS IS RUNNING ON FIRST RUNNING OF THIS PROGRAM!!!
        if(top == 0 && bottom == 0){    
          autoDetect = true;
          newTop = -9000;
          newBottom = 9000;
          
          //determine max and min magnetic field strength over a few minutes
          Serial.println("FRAM has been cleared. Auto-detecting max and min magnetic field reading.");
          Serial.println("WARNING: MAKE SURE GAS IS RUNNING!!");
          lastSend = millis();
          
          while(millis() - lastSend < 120000){
            y = readMag();
            detectMaxMin();
      
            //display details
            Serial.print("y: ");
            Serial.print(y);
            Serial.print("  Top: ");
            Serial.print(newTop);
            Serial.print("  Bottom: ");
            Serial.print(newBottom);
            unsigned long remainingTime = 120000 + lastSend - millis();
            Serial.print("  Time remaining: ");
            Serial.print(remainingTime / 60000);
            Serial.print(":");
            remainingTime = (remainingTime % 60000) / 1000;
            if(remainingTime >= 10){
              Serial.println(remainingTime);
            }
            else{
              Serial.print("0");
              Serial.println(remainingTime);
            }
          }
      
          updateBounds();
          autoDetect = false;
        }
      
        y = readMag();
        oldy = readMag();
        while(abs(y - oldy) < increment / 2){ //wait until difference b/w y and oldy is greater than half an increment
          y = readMag();
        }
        rising = (y > oldy);
        Serial.println(rising ? "Magnetic field is rising" : "Magnetic field is falling");
      }
      
      void presentation()
      {
          // Send the sketch version information to the gateway and Controller
          sendSketchInfo("Gas Meter", "0.6 (2/24/17)");
      
          // Register this device as Gas sensor
          present(CHILD_ID, S_GAS);
      }
      
      void loop(){
        if (!pcReceived) {
          //Last Pulsecount not yet received from controller, request it again
          request(CHILD_ID, V_VAR1);
          return;
        }
        
        //detecting magnetic pulses - variable boundary method
        while(millis() - lastSend < SEND_FREQUENCY){
          //check if the signal has significantly increased/decreased
          if(abs(oldy - y) > increment){
            pulsecount ++;
            //increment or decrement oldy by one increment based on direction
            oldy += rising ? increment : -1 * increment;     
            safe = false;             //reset safe now that oldy has updated     
          }
          //check if the signal has recently switched directions
          else if(safe){                  //first make sure y has moved a significant distance from oldy
            if((rising && y <= oldy) || (!rising && y >= oldy)){
              pulsecount ++;              //add one extra pulse
              rising = !rising;           //update direction
              safe = false;
            }
          }
          
          //take another reading
          y = readMag();
          //check if y has moved a significant distance from oldy
          if(abs(y - oldy) > tol / 2){
            safe = true;
          }
          
          //update newTop and newBottom
          detectMaxMin();                
        }
        
        //shift all flow array elements to the right by 1, ignore last element
        for(int idx = len - 1; idx > 0; idx--){
          flow[idx] = flow[idx - 1];
        }
        //calculate newest flow reading and store it as first element in flow array
        flow[0] = (double)(pulsecount - oldPulseCount) * (double)vpp * 60000.0 / (double)SEND_FREQUENCY;
        //display flow array state
        Serial.print("Flow Array State: [");
        for(int idx = 0; idx < len - 1; idx++){
          Serial.print(flow[idx]);
          Serial.print("|");
        }
        Serial.print(flow[len - 1]);
        Serial.println("]");
        //calculate average flow
        avgFlow = 0;                                //reset avgFlow
        for(int idx = 0; idx < len; idx++){         //calculate weighted sum of all elements in flow array
          avgFlow += (flow[idx] * (len - idx));
        }
        avgFlow /= (len * (len + 1) / 2);           //divide by triangle number of elements to get linear weighted average
        Serial.print("Average flow: ");             //display average flow
        Serial.println(avgFlow);
        //send flow message if avgFlow has changed
        if(avgFlow != oldAvgFlow){
          oldAvgFlow = avgFlow;
          send(flowMsg.set(avgFlow, 2));
        }
      
        //send updated cumulative pulse count and volume data, if necessary
        if(pulsecount != oldPulseCount){    
          //calculate volume
          volume = (double)pulsecount * (double)vpp / (metric ? 1000.0 : 1);
      
          //send pulse count and volume data to gw
          send(lastCounterMsg.set(pulsecount));
          send(volumeMsg.set(volume, 3));
      
          counter += (pulsecount - oldPulseCount);      //update counter
          if(counter >= ((totDividers + 1) * 2)){
            updateBounds();                 //update bounds if at least 1 cycle has been read
            counter = 0;                    //reset counter
          }
      
          oldPulseCount = pulsecount;              //update old total
        }
      
        lastSend = millis();
        
      }
      
      void receive(const MyMessage &message)
      {
        if (message.type==V_VAR1) {
          unsigned long gwPulseCount=message.getULong();
          pulsecount = gwPulseCount;
          oldPulseCount = pulsecount;
          Serial.print("Received last pulse count from gw:");
          Serial.println(pulsecount);
          pcReceived = true;
          lastSend = millis();
          //set magnetic field starting point
          oldy = readMag();
          y = readMag();
        }
      }
      
      void updateBounds(){
        if(((top + tol) != newTop) && ((bottom - tol) != newBottom)){   //check if anything has actually changed
          //lock in Top and Bottom
          top = newTop - tol;
          bottom = newBottom + tol;
          
          //recalculate increment to match new top and bottom
          increment = (top - bottom) / totDividers;
        
          //reset newTop and newBottom
          newTop = -9000;
          newBottom = 9000;
        
          //store updated Top and Bottom in FRAM
          writeInt(topAddr,top);
          writeInt(bottomAddr,bottom);
      
          //reset newTop and newBottom
          newTop = -9000;
          newBottom = 9000;
        
          //display new bounds
          Serial.println("NEW BOUNDARIES SET:");
          Serial.print("Top = ");
          Serial.println(top);
          Serial.print("Bottom = ");
          Serial.println(bottom);
          Serial.print("Increment = ");
          Serial.println(increment);
        }
      }
      
      void detectMaxMin(){
        if(y > newTop){
              newTop = y;                    //update newTop if new max has been detected
            }
        else if(y < newBottom){
          newBottom = y;                     //update newBottom if new min has been detected
        }
      }
      
      void writeInt(int addr, int val){       //write an int value to memory
        byte b = highByte(val);
        fram.write8(addr,b);
        b = lowByte(val);
        fram.write8(addr + 1,b);
      }
      
      int readInt(int addr){                  //read an int value from memory
        int result = 0;
        result += (int)fram.read8(addr);
        result = result << 8;
        result += (int)fram.read8(addr + 1);
        return result;
      }
      
      int readMag(){
        int x = 0, z = 0;
        
        //Tell the HMC5883 where to begin reading data
        Wire.beginTransmission(address);
        Wire.write(0x03); //select register 3, X MSB register - was called Wire.send but the compiler had an error and said to rename to to Wire.write
        Wire.endTransmission();
      
        //Read data from each axis, 2 registers per axis
        Wire.requestFrom(address, 6);
        if(6<=Wire.available()){
          x = Wire.read()<<8; //X msb
          x |= Wire.read(); //X lsb
          z = Wire.read()<<8; //Z msb
          z |= Wire.read(); //Z lsb
          y = Wire.read()<<8; //Y msb
          y |= Wire.read(); //Y lsb
        }
      
        if(!autoDetect){
          //show real-time magnetic field, pulse count, and pulse count total
          Serial.print("y: ");
          Serial.print(y);
          Serial.print(rising ? "  Rising, " : "  Falling, ");
          Serial.print("next pulse at: ");
          Serial.print(rising ? oldy + increment : oldy - increment);
          Serial.print("  Current Number of Pulses: ");
          Serial.print(pulsecount - oldPulseCount);
          Serial.print("  Last Total Pulse Count Sent to GW: ");
          Serial.println(oldPulseCount);
        }
        
        return y;
       
      }
      

      I still have not verified the vpp with the meter but I know it's close. When the water heater is on it Domoticz says I'm flowing enough gas for 30,500 BTU/H. The water heater has an input rating of 28,000 BTU/H.

      Hope this helps, please let me know if you have any questions. I will try to add some pics.

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @dpcr New Sketch: This one is version 1.0 and works well with the hardware I'm using. It has an interactive start up menu in the serial monitor when starting which allows one to choose several options and var1 (variable used to store the pulse count on the controller) was removed and the pulse count is now stored in FRAM. It's been running for several months with no problem.

      /*
       * Created by Sean Creel <creels15@gmail.com>
       * 
       * Based on these projects by Henrik Ekblad and Korneliusz Jarzębski:
       * https://www.mysensors.org/build/pulse_water
       * https://github.com/jarzebski/Arduino-HMC5883L
       * 
       * This program is free software; you can redistribute it and/or
       * modify it under the terms of the GNU General Public License
       * version 2 as published by the Free Software Foundation.
       * 
       * DESCRIPTION:
       * Uses an I2C triple-axis magnetometer to measure a home's gas usage by
       * detecting small fluxuations in the local magnetic field caused by the
       * meter's bellows pumping in and out. Requires users to determine how 
       * many cycles their meter completes per unit volume of gas flow.
       */
      
      
      
      #define MY_DEBUG                        //enables debugging of MySensors messages
      #define MY_RADIO_NRF24                  //lets MySensors know what radio you're using
      
      #include <MySensors.h>         
      #include <Wire.h>                       //I2C communications library
      #include <Adafruit_FRAM_I2C.h>          //Adafruit FRAM memory library
      
      #define CHILD_ID 1                      //ID of the sensor child
      #define SLEEP_MODE false                //prevent sensor from sleeping
      #define address 0x1E                    //0011110b, I2C 7bit address of HMC5883 magnetometer
      #define topAddr 4                       //address of Top in FRAM
      #define bottomAddr 6                    //address of Bottom in FRAM
      #define pulseAddr 0                     //address of pulseCount in FRAM
      
      //USER-DEFINED CONSTANTS
      #define METRIC true                     //specifies the units of measurement
      #define AXIS "Y"                        //specifies which axes of the magnetometer to read from (multiple axes permitted, e.g. "ZX" or "YXZ")
      #define CYCLES_PER_CUFT 8               //cycles of the meter's bellows per cubic foot of gas (varies by  meter model)
      #define NUM_DIVISIONS 10                //desired number of subdivisions per cycle. higher => more accuracy, lower => more tolerant of noise
      #define ERR 0.00221513400975639000      //used to fix discrepancies between theoretical and actual volume per pulse (vpp)
      #define LEN 3                           //number of flow rate measurements to save for moving average. set to 1 if you want to send raw flow data instead
      #define SEND_FREQUENCY 30000            //Minimum time between messages to the GW (msec)
      #define TOL 50                          //margin of error to account for noise coming from the sensor
      
      bool autoDetect = false;                //true if the program is auto detecting Top and Bottom
      bool rising = true;                     //whether or not a pulse has been triggered
      bool safe = false;                      //whether or not it is safe to switch directions
      unsigned long pulseCount = 0;           //total number of pulses measured ever
      unsigned long oldPulseCount = 0;        //old total
      double vpp = ERR + (METRIC ? 28.3168 : 1.0) / (CYCLES_PER_CUFT * 2 * (NUM_DIVISIONS + 1));//Volume of gas per pulse ft^3/L
      unsigned long lastSend = 0;             //time since last transmission - msec
      double flow [LEN];                      //array of previous gas flow rate measurements
      double avgFlow = 0;                     //average of all elements in flow array
      double oldAvgFlow = 0;                  //previous average flow
      int b = 0;                              //magnetic field reading
      int oldB = 0;                           //previous magnetic field reading
      int top = 0;                            //highest magnetic field registered from meter (Ga)Initialize low if using autoDetectMaxMin
      int bottom = 0;                         //lowest magnetic field registered from meter (Ga) Initialize high if using autoDetectMaxMin
      int newTop = -9000;                     //potential new Top
      int newBottom = 9000;                   //potential new Bottom
      int increment = 0;                      //space b/w dividers
      int counter = 0;                        //used to count pulses over periods longer than SEND_FREQUENCY
      
      MyMessage flowMsg(CHILD_ID,V_FLOW);
      MyMessage volumeMsg(CHILD_ID,V_VOLUME);
      
      Adafruit_FRAM_I2C fram = Adafruit_FRAM_I2C();
      
      void setup(){
        //Initialize Serial, I2C, and FRAM communications
        Serial.begin(9600);
        Wire.begin();
        fram.begin();
        
        //Put the HMC5883 IC into the correct operating mode
        Wire.beginTransmission(address); //open communication with HMC5883
        Wire.write(0x02); //select mode register
        Wire.write(0x00); //continuous measurement mode
        Wire.endTransmission();
      
        //Prompt user for permission to clear all or part of FRAM
        Serial.println("Do you wish to clear all or part of long-term memory?");
        Serial.println("Please enter a number 0-3:");
        Serial.println("0: (default) Clear nothing");
        Serial.println("1: Clear Top and Bottom");
        Serial.println("2: Clear Pulse Count");
        Serial.println("3: Clear Top, Bottom, and Pulse Count");
        int choice = 0;
        lastSend = millis();
        while(Serial.available() == 0 && millis() - lastSend < 30000); //wait 30s max for user input
        choice += Serial.parseInt();
        if(choice == 1){
          clearFram(4,8);
          Serial.println("Top and Bottom values reset");
        }
        else if(choice == 2){
          clearFram(0,4);
          Serial.println("Pulse Count reset");
        }
        else if(choice == 3){
          clearFram(0,8);
          Serial.println("All stored values reset");
        }
      
        //get pulseCount from FRAM
        pulseCount = readUL(pulseAddr);
        oldPulseCount = pulseCount;
        //get Top and Bottom from FRAM
        newTop = readInt(topAddr);
        newBottom = readInt(bottomAddr);
        updateBounds();
        
        //WARNING: MAKE SURE GAS IS RUNNING ON FIRST RUNNING OF THIS PROGRAM!!!
        if(top == 0 && bottom == 0){    
          autoDetect = true;
          newTop = -9000;
          newBottom = 9000;
          
          //determine max and min magnetic field strength over a few minutes
          Serial.println("FRAM has been cleared. Auto-detecting max and min magnetic field reading.");
          Serial.println("WARNING: MAKE SURE GAS IS RUNNING!!");
          lastSend = millis();
          
          while(millis() - lastSend < 120000){
            readMag();
            detectMaxMin();
      
            //display details
            Serial.print("Magnetic Field: ");
            Serial.print(b);
            Serial.print("  Top: ");
            Serial.print(newTop);
            Serial.print("  Bottom: ");
            Serial.print(newBottom);
            unsigned long remainingTime = 120000 + lastSend - millis();
            Serial.print("  Time remaining: ");
            Serial.print(remainingTime / 60000);
            Serial.print(":");
            remainingTime = (remainingTime % 60000) / 1000;
            if(remainingTime >= 10){
              Serial.println(remainingTime);
            }
            else{
              Serial.print("0");
              Serial.println(remainingTime);
            }
          }
      
          updateBounds();
          autoDetect = false;
        }
      
        readMag();
        oldB = b;
        while(abs(b - oldB) < increment / 2){ //wait until difference b/w b and oldB is greater than half an increment
          readMag();
        }
        rising = (b > oldB);
        Serial.println(rising ? "Magnetic field is rising" : "Magnetic field is falling");
        Serial.print("Volume Per Pulse = ");
        Serial.print(vpp,16);
        Serial.println(METRIC ? " L/pulse" : "cuft/pulse");
        lastSend = millis();
      }
      
      void presentation()
      {
          // Send the sketch version information to the gateway and Controller
          sendSketchInfo("Gas Meter", "1.0 (4/4/17)");
      
          // Register this device as Gas sensor
          present(CHILD_ID, S_GAS);
      }
      
      void loop(){  
        //detecting magnetic pulses - variable boundary method
        while(millis() - lastSend < SEND_FREQUENCY){
          //check if the signal has significantly increased/decreased
          if(abs(oldB - b) > increment){
            pulseCount ++;
            //increment or decrement oldB by one increment based on direction
            oldB += rising ? increment : -1 * increment;     
            safe = false;             //reset safe now that oldB has updated     
          }
          //check if the signal has recently switched directions
          else if(safe){                  //first make sure b has moved a significant distance from oldB
            if((rising && b <= oldB) || (!rising && b >= oldB)){
              pulseCount ++;              //add one extra pulse
              rising = !rising;           //update direction
              safe = false;
            }
          }
          
          //take another reading
          readMag();
          //check if b has moved a significant distance from oldB
          if(abs(b - oldB) > TOL / 2){
            safe = true;
          }
          
          //update newTop and newBottom
          detectMaxMin();                
        }
        
        //shift all flow array elements to the right by 1, ignore last element
        for(int idx = LEN - 1; idx > 0; idx--){
          flow[idx] = flow[idx - 1];
        }
        //calculate newest flow reading and store it as first element in flow array
        flow[0] = (double)(pulseCount - oldPulseCount) * vpp * 60000.0 / (double)SEND_FREQUENCY;
        //display flow array state
        Serial.print("Flow Array State: [");
        for(int idx = 0; idx < LEN - 1; idx++){
          Serial.print(flow[idx]);
          Serial.print("|");
        }
        Serial.print(flow[LEN - 1]);
        Serial.println("]");
        //calculate average flow
        avgFlow = 0;                                //reset avgFlow
        for(int idx = 0; idx < LEN; idx++){         //calculate weighted sum of all elements in flow array
          avgFlow += (flow[idx] * (LEN - idx));
        }
        avgFlow /= (LEN * (LEN + 1) / 2);           //divide by triangle number of elements to get linear weighted average
        Serial.print("Average flow: ");             //display average flow
        Serial.println(avgFlow);
        //send flow message if avgFlow has changed
        if(avgFlow != oldAvgFlow){
          oldAvgFlow = avgFlow;
          send(flowMsg.set(avgFlow, 2));
        }
      
        //send updated volume data if necessary
        if(pulseCount != oldPulseCount){
      
          //send volume data to gw
          send(volumeMsg.set(((double)pulseCount * vpp / (METRIC ? 1000.0 : 1)), 3));
      
          //store updated pulse count total in FRAM
          writeUL(pulseAddr,pulseCount);
      
          counter += (pulseCount - oldPulseCount);      //update counter
          if(counter >= ((NUM_DIVISIONS + 1) * 2)){
            updateBounds();                 //update bounds if at least 1 cycle has been read
            counter = 0;                    //reset counter
          }
      
          oldPulseCount = pulseCount;              //update old pulse count total
        }
      
        lastSend = millis();
        
      }
      
      void updateBounds(){
        if((top != newTop) && (bottom != newBottom)){   //check if anything has actually changed
          //lock in Top and Bottom
          top = newTop;
          bottom = newBottom;
          
          //recalculate increment to match new top and bottom
          increment = (top - bottom - (2 * TOL)) / NUM_DIVISIONS;
        
          //store updated Top and Bottom in FRAM
          writeInt(topAddr,top);
          writeInt(bottomAddr,bottom);
      
          //reset newTop and newBottom
          newTop = -9000;
          newBottom = 9000;
        
          //display new bounds
          Serial.println("NEW BOUNDARIES SET:");
          Serial.print("Top = ");
          Serial.println(top);
          Serial.print("Bottom = ");
          Serial.println(bottom);
          Serial.print("Increment = ");
          Serial.println(increment);
        }
      }
      
      void detectMaxMin(){
        if(b > newTop){
              newTop = b;                    //update newTop if new max has been detected
            }
        else if(b < newBottom){
          newBottom = b;                     //update newBottom if new min has been detected
        }
      }
      
      void writeInt(int addr, int val){       //write an int value to FRAM
        byte b = highByte(val);
        fram.write8(addr,b);
        b = lowByte(val);
        fram.write8(addr + 1,b);
      }
      
      void writeUL(int addr, unsigned long val){  //write an unsigned long falue to FRAM
        byte b = B00000000;
        for(int idx = 0; idx < sizeof(unsigned long); idx++){
          b = val >> 8 * (sizeof(unsigned long) - 1 - idx);
          fram.write8(addr + idx,b);
        }
      }
      
      unsigned long readUL(int addr){         //read an unsigned long value from FRAM
        unsigned long result = 0;
        for(int idx = 0; idx < sizeof(unsigned long); idx++){
          result = result << 8;
          result += (unsigned long)fram.read8(addr + idx);
        }
        return result;
      }
      
      int readInt(int addr){                  //read an int value from FRAM
        int result = 0;
        result += (int)fram.read8(addr);
        result = result << 8;
        result += (int)fram.read8(addr + 1);
        return result;
      }
      
      void clearFram(int first, int last){
        for(int addr = first; addr < last; addr++){
          fram.write8(addr,byte(0));
        }
      }
      void readMag(){
        static int x = 0, y = 0, z = 0;
        
        //Tell the HMC5883 where to begin reading data
        Wire.beginTransmission(address);
        Wire.write(0x03); //select register 3, X MSB register - was called Wire.send but the compiler had an error and said to rename to to Wire.write
        Wire.endTransmission();
      
        //Read data from each axis, 2 registers per axis
        Wire.requestFrom(address, 6);
        if(6<=Wire.available()){
          x = Wire.read()<<8; //X msb
          x |= Wire.read(); //X lsb
          z = Wire.read()<<8; //Z msb
          z |= Wire.read(); //Z lsb
          y = Wire.read()<<8; //Y msb
          y |= Wire.read(); //Y lsb
        }
      
        if(String(AXIS).equalsIgnoreCase("X")){
          b = x;
        }
        else if(String(AXIS).equalsIgnoreCase("Y")){
          b = y;
        }
        else if(String(AXIS).equalsIgnoreCase("Z")){
          b = z;
        }
        else if(String(AXIS).equalsIgnoreCase("XY") || String(AXIS).equalsIgnoreCase("YX")){
          b = x * x + y * y;
        }
        else if(String(AXIS).equalsIgnoreCase("XZ") || String(AXIS).equalsIgnoreCase("ZX")){
          b = x * x + z * z;
        }
        else if(String(AXIS).equalsIgnoreCase("YZ") || String(AXIS).equalsIgnoreCase("ZY")){
          b = y * y + z * z;
        }
        else if(String(AXIS).equalsIgnoreCase("XYZ") || String(AXIS).equalsIgnoreCase("XZY")
                || String(AXIS).equalsIgnoreCase("YXZ") || String(AXIS).equalsIgnoreCase("YZX")
                || String(AXIS).equalsIgnoreCase("ZYX") || String(AXIS).equalsIgnoreCase("ZXY")){
          b = x * x + y * y + z * z;
        }
        else {
          b = 0;
        }
      
        if(!autoDetect){
          //show real-time magnetic field, pulse count, and pulse count total
          Serial.print("Magnetic Field: ");
          Serial.print(b);
          Serial.print(rising ? "  Rising, " : "  Falling, ");
          Serial.print("next pulse at: ");
          Serial.print(rising ? oldB + increment : oldB - increment);
          Serial.print("  Current Number of Pulses: ");
          Serial.print(pulseCount - oldPulseCount);
          Serial.print("  Last Total Pulse Count Sent to GW: ");
          Serial.println(oldPulseCount);
        } 
      }```
      posted in My Project
      dpcr
      dpcr

    Latest posts made by dpcr

    • RE: 💬 Water Meter Pulse Sensor

      @rejoe2 A magnetometer doesn't need a magnet to work, my gas meter doesn't have a magnet and it works fine. Most all smartphones have magnetometers in them to sense direction. However your solution looks to work as well and probably with less code.

      posted in Announcements
      dpcr
      dpcr
    • RE: 💬 Water Meter Pulse Sensor

      @smilvert You could try a magnometer. I successfully used one on my gas meter that didn't have a magnet in it. Look up "gas meter using a magnometer" in this forum. The code is much different than using the standard water meter sketch.

      posted in Announcements
      dpcr
      dpcr
    • RE: 💬 Water Meter Pulse Sensor

      @ladislav Sounds like a radio problem. Are you using a capacitor on the radio power supply pins? A capacitor on all radios in your network. I have a an Ethernet gateway in our basement and a node in our attic that is two floors above it and it works fine using a NRF24 for all radios and the radio power is on the default setting and it communicates fine.

      posted in Announcements
      dpcr
      dpcr
    • RE: 💬 Water Meter Pulse Sensor

      Has it ever received a pulse count?

      posted in Announcements
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @pihome I power mine with an adapter as well. This node sends too much information to be battery powered. I was researching solar because the sensor sits outside on the south side of our home but never followed up on it. Maybe for another day.

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @Ed1500 Good luck on on your project, keep us updated on your progress. I'm also thinking about trying to measure our electricity consumption using CT's rather than at the meter.

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @dynamite Makes sense, how would you measures the flow when it gets real low?

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @Ed1500 As Dynamite said, it measures the change in the magnet field produced by the movement inside of the meter. I believe all ferris metals can have some sort of effect as well as an electrical field (something like that produced by an AA battery) on the sensor. There is no magnet in my gas meter (Rockwell 250). I used an app on my phone to determine the best possible location of the sensor. Look at the first post.

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @dynamite Just checking in, how is your set up working? We just had some major problems with too many extra pulses. It looks like something you experienced earlier, we added some smoothing to the readMag output similar to what you have in your sketch and it seems to working fine. Any updates?

      posted in My Project
      dpcr
      dpcr
    • RE: Gas Meter Reading Using a Magnetometer

      @dynamite I noticed some pulses missing as well. It looks like the newer sketch catches them. I also checked the volume used with our gas suppliers volume used and it was pretty close. We added an error correction variable to try to be more accurate. When I get a chance I'm going to try your sketch, it looks interesting.

      posted in My Project
      dpcr
      dpcr