My generic room-senser (Sensebender with Motion and Light)



  • I have been trying to make a generic sensor to deploy throughout my house to provide variables which my home automation can work from. The primary data streams I was looking for are temp, motion sensing and light level. From these three, a lot of automation can be created. The Sensebender is the perfect platform to build from - small and designed for battery power. I added a ambient light phototransistor and a low power panasonic PIR and used these cheap housings from ALIExpress. I drove the phototransistor from a digital pin (D7) so it could be powered on and off every time thru the loop and powered the whole thing from a CR123 battery.

    The sketch was based upon the Sensebender Micro sketch with minor additions for the PIR and light level sensor. The PIR is setup on the interrupt on D3 and the light sensor follows the same timer schedule as the Si7021.

    // Default sensor sketch for Sensebender Micro module
    // Act as a temperature / humidity sensor by default.
    //
    // If A0 is held low while powering on, it will enter testmode, which verifies all on-board peripherals
    // 
    // Battery voltage is repported as child sensorId 199, as well as battery percentage (Internal message)
    
    
    #include <MySensor.h>
    #include <Wire.h>
    #include <SI7021.h>
    #include <SPI.h>
    #include <SPIFlash.h>
    #include <EEPROM.h>  
    #include <sha204_lib_return_codes.h>
    #include <sha204_library.h>
    
    // Define a static node address, remove if you want auto address assignment
    //#define NODE_ADDRESS   3
    
    #define RELEASE "1.0"
    
    // Child sensor ID's
    #define CHILD_ID_TEMP  1
    #define CHILD_ID_HUM   2
    #define CHILD_ID_PIR   3
    #define CHILD_ID_LIGHT  4
    #define CHILD_ID_BATT  199
    
    // How many milli seconds between each measurement
    #define MEASURE_INTERVAL 60000
    
    // FORCE_TRANSMIT_INTERVAL, this number of times of wakeup, the sensor is forced to report all values to the controller
    #define FORCE_TRANSMIT_INTERVAL 30 
    
    // When MEASURE_INTERVAL is 60000 and FORCE_TRANSMIT_INTERVAL is 30, we force a transmission every 30 minutes.
    // Between the forced transmissions a tranmission will only occur if the measured value differs from the previous measurement
    
    //Pin definitions
    #define TEST_PIN       A0
    #define LED_PIN        A2
    #define PIR_SENSOR_DIGITAL 3
    #define LIGHT_PIN A1
    #define ATSHA204_PIN   17 // A3
    
    const int sha204Pin = ATSHA204_PIN;
    atsha204Class sha204(sha204Pin);
    
    SI7021 humiditySensor;
    SPIFlash flash(8, 0x1F65);
    
    MySensor gw;
    
    // Sensor messages
    MyMessage msgHum(CHILD_ID_HUM, V_HUM);
    MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
    MyMessage msgPir(CHILD_ID_PIR, V_TRIPPED);
    MyMessage msgLight(CHILD_ID_LIGHT, V_LIGHT_LEVEL);
    MyMessage msgBattery(CHILD_ID_BATT, V_VOLTAGE);
    
    // Global settings
    int measureCount = 0;
    boolean isMetric = true;
    
    
    // Storage of old measurements
    float lastTemperature = -100;
    int lastHumidity = -100;
    int lastLightLevel = -1;
    long lastBattery = -100;
    boolean lastTrippedState;
    
    bool highfreq = true;
    
    void setup() {
    
      pinMode(LED_PIN, OUTPUT);
      digitalWrite(LED_PIN, LOW);
    
      pinMode(PIR_SENSOR_DIGITAL, INPUT);
      digitalWrite(PIR_SENSOR_DIGITAL, HIGH);
    
      pinMode(7, OUTPUT);  // “power pin” for Light Sensor
      digitalWrite(7, LOW);  // switch power off
    
      Serial.begin(115200);
      Serial.print(F("Sensebender Micro FW "));
      Serial.print(RELEASE);
      Serial.flush();
    
      // First check if we should boot into test mode
    
      pinMode(TEST_PIN,INPUT);
      digitalWrite(TEST_PIN, HIGH); // Enable pullup
      if (!digitalRead(TEST_PIN)) testMode();
    
      digitalWrite(TEST_PIN,LOW);
      digitalWrite(LED_PIN, HIGH); 
    
    #ifdef NODE_ADDRESS
      gw.begin(NULL, NODE_ADDRESS, false);
    #else
      gw.begin(NULL,AUTO,false);
    #endif
    
      digitalWrite(LED_PIN, LOW);
    
      humiditySensor.begin();
      Serial.flush();
      Serial.println(F(" - Online!"));
      gw.sendSketchInfo("Sensebender 4-Way", RELEASE);
      
      gw.present(CHILD_ID_TEMP,S_TEMP);
      gw.present(CHILD_ID_HUM,S_HUM);
      gw.present(CHILD_ID_PIR, S_MOTION);
      gw.present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
      
      isMetric = gw.getConfig().isMetric;
      Serial.print("isMetric: "); Serial.println(isMetric);
    
    }
    
    
    // Main loop function
    void loop() {
      measureCount ++;
      bool forceTransmit = false;
    
      // When we wake up the 5th time after power on, switch to 1Mhz clock
      // This allows us to print debug messages on startup (as serial port is dependend on oscilator settings).
      if ((measureCount == 5) && highfreq) switchClock(1<<CLKPS2); // Switch to 1Mhz for the reminder of the sketch, save power.
      
      if (measureCount > FORCE_TRANSMIT_INTERVAL) { // force a transmission
        forceTransmit = true; 
        measureCount = 0;
      }
    
      gw.process();
      sendBattLevel(forceTransmit);
      digitalWrite(7, HIGH); // switch power on to LDR
      sendTempHumidityMeasurements(forceTransmit);
      sendLight(forceTransmit);
      digitalWrite(7, LOW); // switch power off to LDR
      sendPir();
      
      gw.sleep(PIR_SENSOR_DIGITAL - 2, CHANGE, MEASURE_INTERVAL);  
    }
    
    /*
     * Sends temperature and humidity from Si7021 sensor
     *
     * Parameters
     * - force : Forces transmission of a value (even if it's the same as previous measurement)
     */
    void sendTempHumidityMeasurements(bool force)
    {
      if (force) {
        lastHumidity = -100;
        lastTemperature = -100;
      }
      
      si7021_env data = humiditySensor.getHumidityAndTemperature();
      
      float temperature = (isMetric ? data.celsiusHundredths : data.fahrenheitHundredths) / 100.0;
        
      int humidity = data.humidityPercent;
    
      if ((lastTemperature != temperature) | (lastHumidity != humidity)) {
        Serial.print("T: ");Serial.println(temperature);
        Serial.print("H: ");Serial.println(humidity);
        
        gw.send(msgTemp.set(temperature,1));
        gw.send(msgHum.set(humidity));
        lastTemperature = temperature;
        lastHumidity = humidity;
      }
    }
    
    /*   
     *  Sends Motion alert on interupt
     */
    
    void sendPir() // Get value of PIR
    {
      boolean tripped = digitalRead(PIR_SENSOR_DIGITAL) == HIGH; // Get value of PIR
      if (tripped != lastTrippedState)
      {  
        Serial.println(tripped? "tripped" : "not tripped");
        gw.send(msgPir.set(tripped?"1":"0"));  // Send tripped value to gw//
      }
      lastTrippedState = tripped;
      
      
        
      }
      
    
    /*
     * Sends Ambient Light Sensor information
     * 
     * Parameters
     * - force : Forces transmission of a value
     */
    
    void sendLight(bool force) // Get light level
    {
      if (force) lastLightLevel = -1;
      int lightLevel =  (analogRead(LIGHT_PIN)) / 10.23;
      if (lightLevel != lastLightLevel) {
       gw.send(msgLight.set(lightLevel));
        lastLightLevel = lightLevel;
      }
      Serial.print("Light: ");
      Serial.println(lightLevel);
    }
    
    /*
     * Sends battery information (both voltage, and battery percentage)
     *
     * Parameters
     * - force : Forces transmission of a value
     */
    void sendBattLevel(bool force)
    {
      if (force) lastBattery = -1;
      long vcc = readVcc();
      if (vcc != lastBattery) {
        lastBattery = vcc;
        // Calculate percentage
    
        vcc = vcc - 1900; // subtract 1.9V from vcc, as this is the lowest voltage we will operate at
        
        long percent = vcc / 14.0;
        gw.sendBatteryLevel(percent);
      }
    }
    
    long readVcc() {
      // Read 1.1V reference against AVcc
      // set the reference to Vcc and the measurement to the internal 1.1V reference
      #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
      #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
        ADMUX = _BV(MUX5) | _BV(MUX0);
      #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
        ADcdMUX = _BV(MUX3) | _BV(MUX2);
      #else
        ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
      #endif  
     
      delay(2); // Wait for Vref to settle
      ADCSRA |= _BV(ADSC); // Start conversion
      while (bit_is_set(ADCSRA,ADSC)); // measuring
     
      uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
      uint8_t high = ADCH; // unlocks both
     
      long result = (high<<8) | low;
     
      result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
      return result; // Vcc in millivolts
    }
    
    void switchClock(unsigned char clk)
    {
      cli();
      
      CLKPR = 1<<CLKPCE; // Set CLKPCE to enable clk switching
      CLKPR = clk;  
      sei();
      highfreq = false;
    }
    
    
    // Verify all peripherals, and signal via the LED if any problems.
    void testMode()
    {
      uint8_t rx_buffer[SHA204_RSP_SIZE_MAX];
      uint8_t ret_code;
      byte tests = 0;
      
      digitalWrite(LED_PIN, HIGH); // Turn on LED.
      Serial.println(F(" - TestMode"));
      Serial.println(F("Testing peripherals!"));
      Serial.flush();
      Serial.print(F("-> SI7021 : ")); 
      Serial.flush();
      
      if (humiditySensor.begin()) 
      {
        Serial.println(F("ok!"));
        tests ++;
      }
      else
      {
        Serial.println(F("failed!"));
      }
      Serial.flush();
    
      Serial.print(F("-> Flash : "));
      Serial.flush();
      if (flash.initialize())
      {
        Serial.println(F("ok!"));
        tests ++;
      }
      else
      {
        Serial.println(F("failed!"));
      }
      Serial.flush();
    
      
      Serial.print(F("-> SHA204 : "));
      ret_code = sha204.sha204c_wakeup(rx_buffer);
      Serial.flush();
      if (ret_code != SHA204_SUCCESS)
      {
        Serial.print(F("Failed to wake device. Response: ")); Serial.println(ret_code, HEX);
      }
      Serial.flush();
      if (ret_code == SHA204_SUCCESS)
      {
        ret_code = sha204.getSerialNumber(rx_buffer);
        if (ret_code != SHA204_SUCCESS)
        {
          Serial.print(F("Failed to obtain device serial number. Response: ")); Serial.println(ret_code, HEX);
        }
        else
        {
          Serial.print(F("Ok (serial : "));
          for (int i=0; i<9; i++)
          {
            if (rx_buffer[i] < 0x10)
            {
              Serial.print('0'); // Because Serial.print does not 0-pad HEX
            }
            Serial.print(rx_buffer[i], HEX);
          }
          Serial.println(")");
          tests ++;
        }
    
      }
      Serial.flush();
    
      Serial.println(F("Test finished"));
      
      if (tests == 3) 
      {
        Serial.println(F("Selftest ok!"));
        while (1) // Blink OK pattern!
        {
          digitalWrite(LED_PIN, HIGH);
          delay(200);
          digitalWrite(LED_PIN, LOW);
          delay(200);
        }
      }
      else 
      {
        Serial.println(F("----> Selftest failed!"));
        while (1) // Blink FAILED pattern! Rappidly blinking..
        {
        }
      }  
    }
    
    
    

    Photos of the assembly -
    The parts:
    IMG_20150622_115335.jpg
    The board with radio, resistors and FTDI header:
    IMG_20150622_131215.jpg
    The board connected to the battery and sensors (the sensors were wire-wrapped utilizing the 'whiskers' from the resistors):
    IMG_20150622_151216.jpg
    The finished enclosures (I made a few):
    IMG_20150622_152033.jpg
    IMG_20150622_153431.jpg

    They work pretty well but after a few days I have noticed some peculiarities which I hope the forum could assist with:

    1. The Si7021 is VERY sensitive and as a result, the sensors update every minute or two and have not yet slept more than 2-3 minutes. Is there an easy way to fix this in the code so they only transmit every .3 or .5 degree temperature change?

    2. The Panasonic PIRs I am using do not have trim pots to adjust their 'standby-after-alert' time and return to detecting motion after 2.5 seconds which leads to a lot of radio traffic to the gateway when someone is in the room. I thought about 'detaching' the interrupt after alert and 'reattaching' on the next run thru the loop. The would give it up to 60 seconds of standby which would work for my needs but I do not know exactly how to go about this and if it would cause more problems than solve.

    3. I oriented the headers over the blank part of the board (over the stylish logo). They are on the opposite side of the board from the radio but are located directly in line with the antenna (see photo 2). Is this a bad idea? I have not observed any transmission problems or at least I do not think I have...


  • Hero Member

    @Dwalt as for (1), replace
    if ((lastTemperature != temperature) | (lastHumidity != humidity)) {

    with:

    if (abs(lastTemperature - temperature) < TEMPERATURE_THRESHOLD....

    define the threshold to be how much change you want to be reported. make sure you keep the "lastTempreture=tempreture" inside the if otherwise nothing will ever be reported...

    this is dry run, so please check it before implementing throughout your house. can be implemented same way for humidity


  • Hero Member

    Oh, forgot to say, great project and wonderful housings! i think i'll get some as well. what are the soldered resistors?



  • Thanks, I will try your suggestion.

    I have a 22k on the PIR to ground and a 2k on the LDR. I need to try differing resistors on the light sensor to adjust to the 3V ( I am used to 5V on my other LDRs).


  • Admin

    @Dwalt

    The preprogrammed sketch is a bit outdated, compared to the one on github where I had been working on minimizing transmissions with a moving average.

    Been running the github version for some weeks in my two prototype nodes, but still need some adjustments to the temperature part, as it still seems to trigger a transmit with a little temperature deviation


  • Admin

    Hmm.. seems like some expensive sensors.. 16.75$ from mouser.



  • @tbowmo I think they have been discontinued and that makes them hard to find, I picked them up from a government surplus sale for cheap. I like them because they are very compact and are low power.


  • Admin

    @Dwalt

    Mouse has ekmb variants on stock, but as said before they are relatively expensive (19€ a piece for the 2uA version, while 6uA is a bit cheaper). Are thinking that I need a couple of samples next time I order from them.



  • @Dwalt - I really like this project and i'm looking to tag along building my own.
    a few questions if you don't mind.

    1. what were the resistors for? the battery?
    2. can i just get the light sensor and run it off d7? i don't need a "light sensor board"?
    3. i like your choice of case. do you think a double A battery holder will fit inside that? everything else would end up being the same, except i'd be using AA instead of CR123

    4)how is your battery life?

    Thanks!



  • @mvader 1. The resistors are for the LDR and PIR.

    1. I use LDRs (and occasionally photo transistors) for light measurement. They do not return lux readings but simple voltage readings which can be converted in your sketch to an unscientific % value of light level which is good enough for most HA purposes.
    2. The case is too small for my 2-AA battery holders.

  • Hero Member

    @Dwalt how is the battery holding? I have a need for very similar sensors but with hall effect or reed to sense the opening and closing of the air conditioning flaps to sense if they are on or off. The case is too small for 2xaa or 2xaaa so looking at other ideas. I remember something being said about using a li-io battery is not a great idea? or was it only rechargeable?



  • @Moshe-Livne The batteries have not dropped in the last two weeks although they all read around 80% during the first battery report. One of three actually has dropped 1%, the other two are steady. The 80% initial reading comes from the sketch which I believe is calibrated for 3.3V as 100%. I tested my CR123s before using them and they all read exactly 3V. They are Lithium (Li-MNO2) but not rechargeable (Lithium Ion). Typically they run around $1-$1.5. I picked up around 50 when RadioShack went belly-up this winter for 5 for $1. They have a very small self discharge rate and 10 year shelf life. Below is a picture of my case lid with a 2-AA holder inserted. Technically it fits, but no room for anything else and the case is hard to close. Also shown is the lid of one of my recently built sensors with the CR123 holder.
    IMG_20150705_215645.jpg


  • Hero Member

    @Dwalt Thanks! I think i'll go that way as well.



  • @Dwalt Thanks for the pics.
    but as I've already invested in over 20 AA battery holders, I'll need to find a new case solution.
    I have decided to use a LDR as well. I was planning to use rechargeable AA batteries.
    but they are 2.6v total instead of 3v so i guess i'm going to stick with alkaline.


  • Hero Member

    @mvader These are perfect for 2aa powered sensors although slightly ominous http://www.aliexpress.com/item/Dummy-Security-Camera-CCTV-Home-Dome-Camera-With-Red-Flashing-Light-Woshida/32317143038.html?spm=2114.32010308.4.11.8z1AAX

    they are basically empty. for 2.5$ you get a case, a battery holder and a clear dome.... Even a switch!!!! Best deal in the world.



  • This post is deleted!

 

219
Online

8.5k
Users

9.4k
Topics

98.7k
Posts