/* 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 Done - use of pre-compilers to slim down code if not needed TO DO - look for any functions to extract TO DO - update BMP code for new Adafruit unified libraries TO DO - Add in Jeelib code to send to Emoncms via radio **BMP set up** VCC of the BMP085 sensor to 3.3V (NOT 5.0V!) - currently powering off Digitial 7 so can sleep sensor GND to Ground SCL to i2c clock Analog 5 SDA to i2c data Analog 4 EOC not used XCLR not used */ #include #include #include #include // Hardware options and set up #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 // Output options #define DEBUG // uncomment to enable debug statements #define DEBUGMAX // uncomment to enable pulse info as well #define ONSCREEN // uncomment to enable verbose serial data //#define SIMPLESERIAL // uncomment to enable simple serial output in database friendly format // 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; } } void setup() { #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.println("Debug mode"); #endif #if defined DEBUGMAX Serial.print("at maximum including pulse info"); #endif pinMode(PIN_433, INPUT); digitalWrite(PIN_433, 1); // pull-up #if defined DEBUG Serial.println("Just before rf12_init_OOK()"); #endif rf12_init_OOK(); // Set up RF12 Serial.println("RFM12B is now set up for 433MHz OOK"); // interrupt on pin change bitSet(PCMSK2, PIN_433); bitSet(PCICR, PCIE2); #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 // enable interrupts sei(); #if defined DEBUG Serial.println("Interrupts set up"); #endif delay(1000); } void loop() { byte i; unsigned long now; //byte *packet; 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 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.println("Packet Datastream: "); Serial.println("ab cd ef gh ij kl mn op qr st"); for(i=0;i<=9;i++) { if ((int) packet[i]<16) Serial.print('0'); Serial.print(packet[i], HEX); Serial.print(" "); } Serial.print("crc: "); Serial.print(calculate_crc(), HEX); Serial.println((valid_crc() ? " GOOD" : " BAD")); Serial.println(); onScreenExternalSensorData(); // print to external sensor data screen #endif #if defined BMPFITTED // print MBMP data to screen onScreenBmpData(); #endif #if defined ONSCREEN Serial.println("-----End of data-----"); Serial.println(); #endif #if defined SIMPLESERIAL // print ":" delineated data SimpleSerialData(); #endif } state = 0; // Reset ready for next stream } } } // 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); } // 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.println(get_temperature_formatted()); } #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 // Send to serial string of info for export to database (to SD card?) #if defined SIMPLESERIAL void SimpleSerialData() { if(valid_crc()) // Unformatted string for LUA script decoding { Serial.print("WH1080:"); Serial.print(get_sensor_id()); Serial.print(":"); Serial.print(get_temperature_formatted()); Serial.print(":"); Serial.print(get_humidity(), DEC); Serial.print(":"); Serial.print(get_avg_wind(), DEC); Serial.print(":"); Serial.print(get_gust_wind(), DEC); Serial.print(":"); Serial.print(direction_name[get_wind_direction()]); Serial.print(":"); Serial.print(get_rain(), DEC); Serial.print(":"); if(bmpTest == 1) { sensors_event_t event; bmp.getEvent(&event); Serial.print(event.pressure); Serial.print(":"); float temperature; bmp.getTemperature(&temperature); Serial.print(temperature); Serial.print(":"); } Serial.print(spacing, DEC); Serial.println(":END"); Serial.println(); } } #endif // 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; } // 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; } } } // Variables for various data 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; }