// New version of the Room Node, derived from rooms.pde // 2010-10-19 http://opensource.org/licenses/mit-license.php // $Id$ // see http://jeelabs.org/2010/10/20/new-roomnode-code/ // and http://jeelabs.org/2010/10/21/reporting-motion/ // The complexity in the code below comes from the fact that newly detected PIR // motion needs to be reported as soon as possible, but only once, while all the // other sensor values are being collected and averaged in a more regular cycle. // // Brett - Added OneWire support (RoomNode2) // // ATMEL ATMEGA8 & 168 / ARDUINO // // ATMEGA328 / Arduino / JeeNode mapping // // +-\/-+ // (PCINT14,RESET) PC6 1| |28 PC5 (ADC5,SCL,PCINT13,A0) // (PCINT16,RXD,D0) PD0 2| |27 PC4 (ADC4,SDA,PCINT12,A1) // (PCINT17,TXD,D1) PD1 3| |26 PC3 (ADC3,PCINT11,A2) JP4A // RFM12 INT (PCINT18,INT0,D2) PD2 4| |25 PC2 (ADC2,PCINT10,A3) JP3A // JPint (PCINT19,INT1,D3) PD3 5| |24 PC1 (ADC1,PCINT9,A4) JP2A // JP1D (PCINT20,D4) PD4 6| |23 PC0 (ADC0,PCINT8,A5) JP1A // VCC 7| |22 GND // GND 8| |21 AREF // (PCINT6,XTAL1) PB6 9| |20 AVCC // (PCINT7,XTAL2) PB7 10| |19 PB5 (SCK,PCINT5,D13) // JP2D (PCINT21,D5) PD5 11| |18 PB4 (MISO,PCINT4,D12) RFM12 SDO // JP3D (PCINT22,D6) PD6 12| |17 PB3 (MOSI,OC2A,PCINT3,D11) RFM12 SDI // JP4D (PCINT23,D7) PD7 13| |16 PB2 (SS,OSC1B,PCINT2,D10) RFM12 SEL // (PCINT0,D8) PB0 14| |15 PB1 (OC1,PCINT1,D9) // +----+ #include #include #include #include #include #include "Arduino.h" #include #include #define SERIAL 0 // set to 1 to also report readings on the serial port #define DEBUG 0 // set to 1 to display each loop() run and PIR trigger // SHT11, ONE_WIRE and DHT11 are mutually exclusive. // for One WIRE and DHT11 we refer to the ARDUINO (D 4) PIN 6 // For the other we use JeeNode PORT mapping.... yeah confusing. //#define DHT11PIN 4 #define DHT22_PIN 4 // Define DHT22 port //#define ONE_WIRE_PIN 4 // PD4 - defined in a OneWire sensor is connected to port 1. //#define SHT11_PORT 1 // defined if SHT11 is connected to a port #define LDR_PORT 4 // defined if LDR is connected to a port's AIO pin #define PIR_PORT 4 // defined if PIR is connected to a port's DIO pin // If you change this pin adjust the ISR() vector //#define DHT22_on 1 // use DHT22 #define MEASURE_PERIOD 300 // how often to measure, in tenths of seconds #define RETRY_PERIOD 10 // how soon to retry if ACK didn't come in #define RETRY_LIMIT 5 // maximum number of times to retry #define ACK_TIME 10 // number of milliseconds to wait for an ack #define REPORT_EVERY 5 // report every N measurement cycles #define SMOOTH 3 // smoothing factor used for running averages // set the sync mode to 2 if the fuses are still the Arduino default // mode 3 (full powerdown) can only be used with 258 CK startup fuses #define RADIO_SYNC_MODE 2 // The scheduler makes it easy to perform various tasks at various times: enum { MEASURE, REPORT, TASK_END }; static word schedbuf[TASK_END]; Scheduler scheduler (schedbuf, TASK_END); // Other variables used in various places in the code: static byte reportCount; // count up until next report, i.e. packet send static byte myNodeID; // node ID used for this unit // This defines the structure of the packets which get sent out by wireless: struct { byte light; // light sensor: 0..255 byte moved : 1; // motion detector: 0..1 byte humi : 7; // humidity: 0..100 int temp : 10; // temperature: -500..+500 (tenths) byte lobat : 1; // supply voltage dropped under 3.1V: 0..1 } payload; // Conditional code, depending on which sensors are connected and how: // Commented out as it won't compile for me with this in (MB) //#if ONE_WIRE_PIN //#define TEMPERATURE_PRECISION 12 //#include //#include //// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs) //OneWire oneWire(ONE_WIRE_PIN); //// Pass our oneWire reference to Dallas Temperature. //DallasTemperature sensors(&oneWire); //DeviceAddress deviceAddress; //#endif #if DHT22_PIN DHTxx dht(DHT22_PIN); #endif #if DHT11PIN DHTxx dht(DHT11PIN); #endif #if SHT11_PORT SHT11 sht11 (SHT11_PORT); #endif #if LDR_PORT Port ldr (LDR_PORT); #endif #if PIR_PORT #define PIR_HOLD_TIME 30 // hold PIR value this many seconds after change #define PIR_PULLUP 0 // set to one to pull-up the PIR input pin #define PIR_FLIP 0 // 0 or 1, to match PIR reporting high or low class PIR : public Port { volatile byte value, changed; volatile uint32_t lastOn; public: PIR (byte portnum) : Port (portnum), value (0), changed (0), lastOn (0) { } // this code is called from the pin-change interrupt handler void poll() { // see http://talk.jeelabs.net/topic/811#post-4734 for PIR_FLIP byte pin = digiRead() ^ PIR_FLIP; // if the pin just went on, then set the changed flag to report it if (pin) { if (!state()) changed = 1; lastOn = millis(); } value = pin; } // state is true if curr value is still on or if it was on recently byte state() const { byte f = value; if (lastOn > 0) ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if (millis() - lastOn < 1000 * PIR_HOLD_TIME) f = 1; } return f; } // return true if there is new motion to report byte triggered() { byte f = changed; changed = 0; return f; } }; PIR pir (PIR_PORT); // the PIR signal comes in via a pin-change interrupt ISR(PCINT2_vect) { pir.poll(); } #endif // has to be defined because we're using the watchdog for low-power waiting ISR(WDT_vect) { Sleepy::watchdogEvent(); } // utility code to perform simple smoothing as a running average static int smoothedAverage(int prev, int next, byte firstTime =0) { if (firstTime) return next; return ((SMOOTH - 1) * prev + next + SMOOTH / 2) / SMOOTH; } // spend a little time in power down mode while the SHT11 does a measurement static void shtDelay () { Sleepy::loseSomeTime(32); // must wait at least 20 ms } // wait a few milliseconds for proper ACK to me, return true if indeed received static byte waitForAck() { MilliTimer ackTimer; while (!ackTimer.poll(ACK_TIME)) { if (rf12_recvDone() && rf12_crc == 0 && // see http://talk.jeelabs.net/topic/811#post-4712 rf12_hdr == (RF12_HDR_DST | RF12_HDR_CTL | myNodeID)) return 1; set_sleep_mode(SLEEP_MODE_IDLE); sleep_mode(); } return 0; } // readout all the sensors and other values static void doMeasure() { byte firstTime = payload.humi == 0; // special case to init running avg payload.lobat = rf12_lowbat(); #if SHT11_PORT #ifndef __AVR_ATtiny84__ sht11.measure(SHT11::HUMI, shtDelay); sht11.measure(SHT11::TEMP, shtDelay); float h, t; sht11.calculate(h, t); byte humi = h + 0.5, temp = 10 * t + 0.5; #else //XXX TINY! byte humi = 50, temp = 25; #endif payload.humi = smoothedAverage(payload.humi, humi, firstTime); payload.temp = smoothedAverage(payload.temp, temp, firstTime); #endif #if LDR_PORT ldr.digiWrite2(1); // enable AIO pull-up byte light = ~ ldr.anaRead() >> 2; ldr.digiWrite2(0); // disable pull-up to reduce current draw payload.light = smoothedAverage(payload.light, light, firstTime); #endif #if PIR_PORT payload.moved = pir.state(); #endif #if ONE_WIRE_PIN sensors.requestTemperatures(); payload.temp = sensors.getTempC(deviceAddress) * 10; #endif #if DHT11PIN int t,h; if(dht.reading(t, h)) { payload.temp = t; // Convert from tenth's back to 0-100 range. payload.humi = h/10; } #endif #if DHT22_PIN DHT22 myDHT22(DHT22_PIN); DHT22_ERROR_t errorCode; #if SERIAL Serial.println(" Requesting data..."); Serial.flush(); #endif delay(2100); errorCode = myDHT22.readData(); switch(errorCode) { case DHT_ERROR_NONE: #if SERIAL Serial.print("Got Data "); Serial.print(myDHT22.getTemperatureC()); Serial.print("C "); Serial.print(myDHT22.getHumidity()); Serial.println("%"); Serial.flush(); #endif payload.temp = (myDHT22.getTemperatureCInt()); payload.humi = (myDHT22.getHumidity()); #if SERIAL Serial.print("Got Data "); Serial.print(payload.temp); Serial.print("C "); Serial.print(payload.humi); Serial.println("%"); Serial.flush(); #endif break; case DHT_ERROR_CHECKSUM: #if SERIAL Serial.print("check sum error "); Serial.print(myDHT22.getTemperatureC()); Serial.print("C "); Serial.print(myDHT22.getHumidity()); Serial.println("%"); Serial.flush(); #endif break; case DHT_BUS_HUNG: #if SERIAL Serial.println("BUS Hung "); Serial.flush(); #endif break; case DHT_ERROR_NOT_PRESENT: #if SERIAL Serial.println("Not Present "); Serial.flush(); #endif break; case DHT_ERROR_ACK_TOO_LONG: #if SERIAL Serial.println("ACK time out "); #endif break; case DHT_ERROR_SYNC_TIMEOUT: #if SERIAL Serial.println("Sync Timeout "); Serial.flush(); #endif break; case DHT_ERROR_DATA_TIMEOUT: #if SERIAL Serial.println("Data Timeout "); #endif break; case DHT_ERROR_TOOQUICK: #if SERIAL Serial.println("Polled too quick "); Serial.flush(); #endif break; } #endif } // periodic report, i.e. send out a packet and optionally report on serial port static void doReport() { // the RF12 runs on INT0 this interrupt is on PCINT18 the same as port PD as // the PIR (PCINT23) so when the RF12 is being used the entire PORT gets a PIN // CHANGE event PCIFR. // We need to capture this and mask it off so the PIR does not fire again. #if PIR_PORT PCICR &= ~(1 << PCIE2); // disable the PD pin-change interrupt. #endif rf12_sleep(RF12_WAKEUP); while (!rf12_canSend()) rf12_recvDone(); rf12_sendStart(0, &payload, sizeof payload, RADIO_SYNC_MODE); rf12_sleep(RF12_SLEEP); // When a logic change on any PCINT23:16 pin triggers an interrupt request, // PCIF2 becomes set(one). If the I-bit in SREG and the PCIE2 bit in PCICR // are set (one), the MCU will jump to thecorresponding Interrupt Vector. // The flag is cleared when the interrupt routine is executed. Alternatively, // the flag can be cleared by writing a logical one to it. #if PIR_PORT PCIFR = (1<= REPORT_EVERY) { reportCount = 0; scheduler.timer(REPORT, 0); } break; case REPORT: doReport(); break; } }