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:
The board with radio, resistors and FTDI header:
The board connected to the battery and sensors (the sensors were wire-wrapped utilizing the 'whiskers' from the resistors):
The finished enclosures (I made a few):
They work pretty well but after a few days I have noticed some peculiarities which I hope the forum could assist with:
-
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?
-
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.
-
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...
-
-
@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
-
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).
-
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
-
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.
-
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.- what were the resistors for? the battery?
- can i just get the light sensor and run it off d7? i don't need a "light sensor board"?
- 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.
2. 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.
3. The case is too small for my 2-AA battery holders.
-
@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.
-
@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.
-
@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!