/* WH1080 / Maplin N96GY decoder for OOK devices Compiles with Arduino 1.5.5 M Baldwin February 2014 Tweaked by G Pickard Feb 2014 Resources used http://www.susa.net/wordpress/2012/08/raspberry-pi-reading-wh1081-weather-sensors-using-an-rfm01-and-rfm12b/ http://www.sevenwatt.com/main/wh1080-protocol-v2-fsk/ http://lucsmall.com/2012/04/27/weather-station-hacking-part-1/ http://www.homeautomationhub.com/content/wh1080-weather-station-integration https://github.com/adafruit/Adafruit-BMP085-Library http://jeelabs.net/projects/cafe/wiki/Receiving_OOKASK_with_a_modified_RFM12B Vcc info only via 1.1v voltage ref BMP085 data using Adafruit unified library Jeelib for data transmission Adding standard rf transmit into a function Now tryinf to blend with weather station code NB emonCMS prefers signed 16bit integers - watch out in data types Hardware used: * Moteino * RFM12B with OOK modding (220uF cap used) http://jeelabs.net/projects/cafe/wiki/Receiving_OOKASK_with_a_modified_RFM12B This is mounted on a break out board http://nathan.chantrell.net/20130208/rfm12b-breakout-board/ The breakout is wired to the standard Moteino pins (D10 - SS; D11 - MOSI; D12 - MISO; D13 - SCK) The FSK wire via 100 ohm resistor goes to a Digital pin * BMP085 via ebay on a small breakout board. Pins to A4 - SDA; A5 - SCL; D7 being used to power on and off Recommended node ID allocation ------------------------------------------------------------------------------------------------------------ -ID- -Node Type- 0 - Special allocation in JeeLib RFM12 driver - reserved for OOK use 1-4 - Control nodes 5-10 - Energy monitoring nodes 11-14 --Un-assigned -- 15-16 - Base Station & logging nodes 17-30 - Environmental sensing nodes (temperature humidity etc.) 31 - Special allocation in JeeLib RFM12 driver - Node31 can communicate with nodes on any network group ------------------------------------------------------------------------------------------------------------ */ #include #include #include #include // Hardware options and set up #define FREQ RF12_433MHZ // Frequency of RF12B RF12_433MHZ, RF12_868MHZ or RF12_915MHZ #define nodeID 3 // using this for testing #define networkGroup 210 #define PIN_433 4 // The digital pin connected to the FSK etc connection on RFM12B via 100 ohm resistor #define BMPFITTED // Comment out if no BMP085 fitted #if defined BMPFITTED #define bmpPower 7 // Digital pin used to power BMP byte bmpTest = 1; // set to 1 to enable BMP085 Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085); #endif // Settings #define LED 9 //#define DEBUG // uncomment to enable debug statements //#define DEBUGMAX // uncomment to enable extra info //#define ONSCREEN // uncomment to enable verbose serial data // Most of the variables for the decoding volatile int pulse_433; volatile word pulse_length; volatile unsigned long old = 0; volatile unsigned long packet_count = 0; volatile unsigned long spacing; volatile unsigned long average_interval; word last_433; // never accessed outside ISR's volatile word pulse_count = 0; byte packet[10]; byte state = 0; bool packet_acquired = false; char *direction_name[] = {"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}; // State to track pulse durations measured in the interrupt code ISR(PCINT2_vect) { pulse_433 = 0; if (digitalRead(PIN_433) == LOW) { word ticker = micros(); pulse_433 = ticker - last_433; last_433 = ticker; //++ pulse_count; } if (pulse_433 > 3000) // reset pulse stream and counter { state = 0; pulse_433 = 0; } } // RFM12B RF payload datastructure typedef struct { int vcc; int16_t temp_internal; int16_t pressure; int temp_external; } Payload; Payload rfdata; void setup() { pinMode(LED,OUTPUT); digitalWrite(LED,LOW); #if defined BMPFITTED pinMode(bmpPower, OUTPUT); digitalWrite(bmpPower, HIGH); #endif Serial.begin(57600); Serial.println(); Serial.println("WH1080 / Maplin N96GY serial node with BMP085"); #if defined DEBUG Serial.print("Debug mode"); #endif #if defined DEBUGMAX Serial.println(" at maximum including pulse info"); #else Serial.println(); #endif #if defined DEBUG rftestsequence(); #endif #if defined BMPFITTED if(bmpTest) { if (!bmp.begin()) { Serial.println("Could not find a valid BMP085 sensor, check wiring!"); bmpTest = 0; Serial.print("BMP085 sensor status "); Serial.println(bmpTest); } else { Serial.println("BMP085 sensor found and set up"); displaySensorDetails(); } } else { Serial.println("BMP085 not working, code disabled"); } #endif /* pinMode(PIN_433, INPUT); digitalWrite(PIN_433, 1); // pull-up rf12_init_OOK(); // Set up RF12 #if defined DEBUG Serial.println("RFM12B is now set up for 433MHz OOK"); #endif // interrupt on pin change bitSet(PCMSK2, PIN_433); bitSet(PCICR, PCIE2); // enable interrupts sei(); #if defined DEBUG Serial.println("Interrupts set up"); #endif delay(1000);*/ ookInterrupts(); } void loop() { byte i; unsigned long now; if (pulse_433) { cli(); // Disable interrupts and store pulse width word pulse = pulse_433; pulse_433 = 0; sei(); // Re-enable interrupts #if defined DEBUGMAX Serial.println(pulse); #endif extractData(pulse); // Extract data from pulse stream if (state == 3) // Pulse stream complete { state = 4; now = millis(); spacing = ((now - old)/1000); old = now; if (spacing >= 10) { packet_count ++; average_interval = now / packet_count; #if defined ONSCREEN onScreenPacketInfo(); onScreenExternalSensorData(); // print to external sensor data screen #endif #if defined ONSCREEN // print BMP data to screen onScreenBmpData(); #endif #if defined ONSCREEN Serial.println("-----End of data-----"); Serial.println(); #endif //now to send the rf data #if defined BMPFITTED sensors_event_t event; bmp.getEvent(&event); if (event.pressure) { rfdata.pressure=(event.pressure*10); // Remember to divide by 10 later - raw measure in hPa to 1 DP float temperature; bmp.getTemperature(&temperature); rfdata.temp_internal=(temperature*100); // Remember to divide by 100 later - raw measure in C to 2 DP } else { Serial.println("Sensor error"); } #endif rfdata.vcc=readVccRef(); rfdata.temp_external=(get_temperature_formatted(), DEC); send_rf_data(); #if defined DEBUGMAX delay(10); Serial.println("Data should now be sent by RFM12B as follows ..."); Serial.print("VCC: "); Serial.print(rfdata.vcc); Serial.print(" Temp Int: "); Serial.print(rfdata.temp_internal); Serial.print(" Pressure: "); Serial.print(rfdata.pressure);Serial.print(" Temp Ext: "); Serial.println(rfdata.temp_external); Serial.println("-------------------"); #endif #if defined DEBUG digitalWrite(LED,HIGH); delay(10); digitalWrite(LED,LOW); #endif ookInterrupts(); // back to listening } state = 0; // Reset ready for next stream } } } //*************BMP code*************** // New Adafruit sensor info void displaySensorDetails(void) { sensor_t sensor; bmp.getSensor(&sensor); Serial.println("------------------------------------"); Serial.print ("Sensor: "); Serial.println(sensor.name); Serial.print ("Driver Ver: "); Serial.println(sensor.version); Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id); Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" hPa"); Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" hPa"); Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" hPa"); Serial.println("------------------------------------"); Serial.println(""); delay(500); } //******************Output formats*********************** // Print to screen/serial nicely formatted external data #if defined ONSCREEN void onScreenExternalSensorData() { Serial.println(); Serial.println("----External sensor data---"); Serial.print("External Humidity: "); Serial.print(get_humidity(), DEC); Serial.println("%"); Serial.print("Average wind speed: "); Serial.print(get_avg_wind(), DEC); Serial.println("m/s"); Serial.print("Wind gust speed: "); Serial.print(get_gust_wind(), DEC); Serial.println("m/s"); Serial.print("Wind direction: "); Serial.println(direction_name[get_wind_direction()]); Serial.print("Rainfall: "); Serial.print(get_rain(), DEC); Serial.println("mm"); Serial.print("External Temp: "); Serial.print(get_temperature_formatted()); Serial.println(" C"); } #endif // Print to screen/serial nicely formatted BMP085 data #if defined ONSCREEN void onScreenBmpData() { if (bmpTest == 1) { sensors_event_t event; bmp.getEvent(&event); Serial.println(); Serial.println("----BMP readings---"); float temperature; bmp.getTemperature(&temperature); Serial.print("Internal Temp = "); Serial.print(temperature); Serial.println(" C"); Serial.print("Air Pressure = "); Serial.print(event.pressure); Serial.println(" hPa"); } } #endif #if defined ONSCREEN void onScreenPacketInfo() { Serial.println(); Serial.println("----Start of data----"); Serial.print("Sensor ID: 0x"); Serial.println(get_sensor_id(), HEX); Serial.print("Packet count: "); Serial.println(packet_count, DEC); Serial.print("Spacing: "); Serial.println(spacing, DEC); Serial.print("Average spacing: "); Serial.println(average_interval, DEC); Serial.println(); Serial.print("crc: "); Serial.print(calculate_crc(), HEX); Serial.println((valid_crc() ? " GOOD" : " BAD")); Serial.println(); } #endif //**************The RFM12B code*************** // Send RFM12B test sequence for factory testing void rftestsequence(void) { Serial.println("About to send some test data");Serial.println(); delay(1000); rf12_initialize(nodeID, FREQ, networkGroup); // Initialize RFM12B in standard transmit mode for (int i=0; i<5; i++) { rfdata.vcc=(i+1); rfdata.temp_internal=(i+11); rfdata.pressure=(i+21); rfdata.temp_external=(i+31); rf12_sendNow(0, &rfdata, sizeof rfdata); #if defined DEBUGMAX Serial.print("Transmit: "); Serial.print(i+1);Serial.println(" of 5"); Serial.print("VCC: "); Serial.print(rfdata.vcc); Serial.print(" Temp Int: "); Serial.print(rfdata.temp_internal); Serial.print(" Pressure: "); Serial.print(rfdata.pressure); Serial.print(" Temp Ext: "); Serial.println(rfdata.temp_external); Serial.println("-------------------"); #endif digitalWrite(LED,HIGH); // A blip on transmitting delay(10); digitalWrite(LED,LOW); delay(5000); // Delay between each transmission - we do not want to flood the receiver } rf12_sendWait(2); // reset everything to 0 rfdata.vcc=0; rfdata.temp_internal=0; rfdata.pressure=0; rfdata.temp_external=0; rf12_sleep(RF12_SLEEP); } //Send the standard data void send_rf_data() { rf12_initialize(nodeID, FREQ, networkGroup); // Initialize RFM12B in standard transmit mode rf12_sleep(RF12_WAKEUP); delay(10); rf12_sendNow(0, &rfdata, sizeof rfdata); rf12_sendWait(2); rf12_sleep(RF12_SLEEP); } // Setting up the RFM12B for OOK receiving // http://tools.jeelabs.org/rfm12b.html for set up info static void rf12_init_OOK() { rf12_initialize(0, RF12_433MHZ); rf12_control(0x8017); // 433 Mhz; disable tx register; disable RX rf12_control(0x82c0); // enable receiver; enable basebandblock rf12_control(0xA620); // 433.92 MHz //rf12_control(0xA618); // 433.90 MHz - no real difference rf12_control(0xc691); // datarate 2395 kbps 0xc647 = 4.8kbps //rf12_control(0x9489); // VDI; FAST; 200khz; Gain -6db; DRSSI 97dbm rf12_control(0x9491); // VDI; FAST; 200khz; Gain -14db; DRSSI 97dbm - lots of background noise/signals to filter rf12_control(0xC220); // datafiltercommand; ** not documented cmd rf12_control(0xCA00); // FiFo and resetmode cmd; FIFO fill disabeld rf12_control(0xC473); // AFC run only once; enable AFC; enable frequency offset register; +3 -4 rf12_control(0xCC67); // pll settings command rf12_control(0xB800); // TX register write command not used rf12_control(0xC800); // disable low dutycycle rf12_control(0xC040); // 1.66MHz, 2.2V not used see 82c0 return; } //Set up the interrupts for OOK listening void ookInterrupts() { #if defined DEBUG Serial.println("Just before rf12_init_OOK()"); #endif rf12_init_OOK(); // Set up RF12 as OOK #if defined DEBUG Serial.println("RFM12B is now set up for 433MHz OOK"); #endif // interrupt on pin change bitSet(PCMSK2, PIN_433); bitSet(PCICR, PCIE2); // enable interrupts sei(); #if defined DEBUG Serial.println("Interrupts set up"); delay(500); #endif } //***************Weather station code***************** // Detecting and decoding the weather station data static void extractData(word interval) { /*state 0 == reset state state 1 == looking for preamble state 2 == got preamble, load rest of packet state 3 == packet complete */ bool sample; byte preamble; byte preamble_check = B00011110; // Check if we see the end of the preamble byte preamble_mask = B00011111; // Mask for preamble test (can be reduced if needed) static byte packet_no, bit_no, preamble_byte; if (interval >= 1350 && interval <= 1650) // 1 is indicated by 1500uS pulse { sample = 1; } else if (interval >= 2350 && interval <= 2750) // 0 is indicated by a 2500uS pulse { sample = 0; } else { #if defined DEBUGMAX Serial.println("Out of range"); #endif state = 0; // If interval not in range reset and start looking for preamble again return; } if(state == 0) // Reset { // reset stuff preamble_byte = 0; packet[0] = packet[1] = packet[2] = packet[3] = packet[4] = packet[5] = packet[6] = packet[7] = packet[8] = packet[9] = 0; #if defined DEBUGMAX Serial.println("Looking for preamble"); #endif state = 1; } if (state == 1) // Acquire preamble { preamble_byte <<= 1; // Shift preamble byte left preamble_byte |= sample; //OR with new bit preamble = preamble_byte & preamble_mask; if (preamble == preamble_check) // Preamble acquired { packet_no = 0; bit_no = 2; // Start at 2 because we know the first two bits are 10 packet[0] = packet[1] = packet[2] = packet[3] = packet[4] = packet[5] = packet[6] = packet[7] = packet[8] = packet[9] = 0; packet[0] <<= 1; packet[0] |= 1; // First byte after preamble contains 10 so add 1 packet[0] <<= 1; packet[0] |= 0; // First byte after preamble contains 10 so add 0 #if defined DEBUGMAX Serial.println("Got the preamble"); #endif state = 2; } return; // Not got the preamble yet so return } if (state == 2) // Acquire packet { packet[packet_no] <<= 1; packet[packet_no] |= sample; // Add bit to packet stream bit_no ++; if(bit_no > 7) { bit_no = 0; packet_no ++; } if (packet_no > 9) // Got full packet stream { state = 3; } } } // Functions for various data processing from the weather station feed byte calculate_crc() { return _crc8(packet, 9); } bool valid_crc() { return (calculate_crc() == packet[9]); } int get_sensor_id() { return (packet[0] << 4) + (packet[1] >> 4); } byte get_humidity() { return packet[3]; } byte get_avg_wind() { return packet[4]; } byte get_gust_wind() { return packet[5]; } byte get_wind_direction() { int direction = packet[8] & 0x0f; return direction; } byte get_rain() { return packet[7]; } /* Temperature in deci-degrees. e.g. 251 = 25.1 */ int get_temperature() { int temperature; temperature = ((packet[1] & B00000111) << 8) + packet[2]; // make negative if (packet[1] & B00001000) { temperature = -temperature; } temperature -= 400; return temperature; } String get_temperature_formatted() { int temperature; byte whole, partial; String s; temperature = ((packet[1] & B00000111) << 8) + packet[2]; temperature -= 400; // Temp offset by 400 points whole = temperature / 10; partial = temperature - (whole*10); s = String(); if (packet[1] & B00001000) { s += String('-'); } s += String(whole, DEC); s += '.'; s += String(partial, DEC); return s; } uint8_t _crc8( uint8_t *addr, uint8_t len) // CRC function from OneWire library { uint8_t crc = 0; while (len--) { uint8_t inbyte = *addr++; for (uint8_t i = 8; i; i--) { uint8_t mix = (crc ^ inbyte) & 0x80; // Changed from & 0x01 crc <<= 1; // Changed from right shift if (mix) crc ^= 0x31; // Changed from 0x8C inbyte <<= 1; // Changed from right shift } } return crc; } //**************Vcc calc here******************** // Calculate Vcc based on internal reference voltage long readVccRef() { long result; ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); delay(2); ADCSRA |= _BV(ADSC); while (bit_is_set(ADCSRA,ADSC)); result = ADCL; result |= ADCH<<8; result = 1126400L / result; return result; }