├── wserver.pdf ├── README.md └── WeatherServer ├── D_Interrupts.ino ├── F_Main.ino ├── E_Setup.ino ├── WeatherServer.ino ├── C_Web.ino ├── B_Subs.ino └── A_Functions.ino /wserver.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joegr123/WeatherStation/HEAD/wserver.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My changes: 2 | Fixed some decode bugs, such as the low battery flag for the 5-N-1 3 | Added parity check and crc to the messages 4 | Changed project to run on a NodeMCU (WiFi Arduino) 5 | Added Weather Underground Upload of data 6 | Added some webpages to get data into my home automation system (probably not much use to anyone else) 7 | Added a display and a presure sensor 8 | Added rain rate calculation, wind peak, and wind average 9 | Added signal quality score 10 | Put rain counter in eeprom, so that resets don't mess up the rain amount per day 11 | 12 | I only have a 5-N-1 and a tower sensor. I average the temps and humidity from both to send to WU, you may want to do this differently 13 | I do not have a lightning sensor, so I didn't really do anything with it, and I don't know if what is there works 14 | 15 | The big thing that it still needs is a way to tell time for the midnight resets for the rain amounts. You could add NTP for time/date, 16 | or you could get it from the response from WU. Correcting for time zone is easy, correcting for DST is a pain. 17 | 18 | Sample from debug webpage: 19 | 20 | Weather Server Debug Page 21 | Version: Build 3/20/2018 B 22 | 37.98 hours uptime 23 | 24 | WiFi signal level = -41 dBm, 433MHz receiver signal level = -68 dBm 25 | 26 | Time to next WU update = 34.0 seconds, Last WU response: 27 | HTTP/1.0 200 OK 28 | Content-type: text/html 29 | Date: Fri, 23 Mar 2018 16:46:00 GMT 30 | Content-Length: 8 31 | 32 | success 33 | 34 | 35 | Air Pressure = 30.34 inHg, 102725.80 Pa, Receiver Temperature = 90.1 F 36 | 37 | 5-in-1 Sensor: 38 | ETA of next message: 1 seconds, Last good message was 16 seconds ago, Signal quality = 10 out of 10 39 | Temperature =68.9 F, Relative Humidity = 65% 40 | Daily Rain = 0.00 inches, Current Rain Rate = 0.00 inches/hour, Rain Counter = 10d 41 | Wind = 2.7 MPH, Direction = SE, Average = 5.4 MPH, Peak = 9.4 MPH at SSW Direction 42 | 43 | Tower Sensor: 44 | ETA of next message: 5 seconds, Last good message was 13 seconds ago, Signal quality = 9 out of 10 45 | Temperature =68.2 F, Relative Humidity = 55% 46 | 47 | Reading#, 5N1 Signal, Tower Signal, Rain, Wind Speed, Direction 48 | 32 Good 0.00 2.68 SE 49 | 31 Good 0.00 6.28 SE 50 | 30 Good 0.00 6.28 SE 51 | 29 Good 0.00 2.68 SE 52 | 28 Good 0.00 4.74 S 53 | 27 Good 0.00 9.37 S 54 | 26 Good 0.00 6.28 SSE 55 | 25 Good 0.00 2.68 SSE 56 | 24 Good 0.00 3.71 SSW 57 | 23 Good 0.00 7.31 SSW 58 | 22 Good 0.00 5.77 SSW 59 | 21 Good 0.00 5.77 SSW 60 | 20 Good 0.00 6.28 SSW 61 | 19 Good 0.00 7.82 SSW 62 | 18 Good 0.00 8.34 SSW 63 | 17 Good 0.00 5.77 SSW 64 | 16 Good 0.00 3.71 S 65 | 15 Good 0.00 4.22 S 66 | 14 Good 0.00 8.34 SSW 67 | 13 Good 0.00 6.79 SSW 68 | 12 Good 0.00 3.19 SSE 69 | 11 Missed 0.00 5.77 SSE 70 | 10 Good Good 0.00 5.77 SSE 71 | 9 Good Good 0.00 6.79 SSE 72 | 8 Good Good 0.00 6.28 SSW 73 | 7 Good Good 0.00 5.77 SSW 74 | 6 Good Good 0.00 9.37 SSW 75 | 5 Good Good 0.00 3.19 SSW 76 | 4 Good Good 0.00 6.28 SSW 77 | 3 Good Good 0.00 3.19 SSW 78 | 2 Good Missed 0.00 2.16 E 79 | 1 Good Good 0.00 1.65 E 80 | 81 | 82 | 83 | 84 | Old readme: 85 | 86 | Arduino code decode several Acurite devices OTA data stream. 87 | 88 | Decoding protocol and prototype code from these sources: 89 | Ray Wang (Rayshobby LLC) 90 | http://rayshobby.net/?p=8998 91 | Benjamin Larsson (RTL_433) 92 | https://github.com/merbanan/rtl_433 93 | Brad Hunting (Acurite_00592TX_sniffer) 94 | https://github.com/bhunting/Acurite_00592TX_sniffer 95 | 96 | Written and tested on an Arduino Uno R3 Compatible Board 97 | using a RXB6 433Mhz Superheterodyne Wireless Receiver Module 98 | 99 | This works with these devices but more could be added 100 | Acurite Pro 5-in-1 (8 bytes) 101 | https://tinyurl.com/zwsl9oj 102 | Acurite Ligtning Detector 06045M (9 bytes) 103 | https://tinyurl.com/y7of6dq4 104 | Acurite Room Temp and Humidity 06044M (7 bytes) 105 | https://tinyurl.com/yc9fpx8q 106 | 107 | -------------------------------------------------------------------------------- /WeatherServer/D_Interrupts.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | * Interrupt Handler for IO * 3 | * Fires everytime the data signal from the 433MHz radio changes state * 4 | * Finds and stores sync and data bits * 5 | * Once it finds sync bits and enough data bits, it sets the recieved flag to true and stops processing the signal until * 6 | * received is set to false again. Failure to set received to false quickly, will result in lost messages. * 7 | * ********************************************************************************************************************************************** 8 | */ 9 | 10 | /* Interrupt 1 handler 11 | Tied to pin 3 INT1 of arduino. 12 | Set to interrupt on edge (level change) high or low transition. 13 | Change the state of the Arduino LED (pin 13) on each interrupt. 14 | This allows scoping pin 13 to see the interrupt / data pulse train. 15 | */ 16 | void handler() 17 | { 18 | static unsigned long duration = 0; 19 | static unsigned long lastTime = 0; 20 | static unsigned int ringIndex = 0; 21 | static unsigned int syncCount = 0; 22 | static unsigned int bitState = 0; 23 | static unsigned int IsyncIndex = 0; // was global 24 | static bool IsyncFound = false; // true if sync pulses found 25 | static unsigned int IbytesReceived = 0; 26 | 27 | bitState = digitalRead(DATAPIN); 28 | digitalWrite(LED_BUILTIN, !bitState); // have LED mimic state of RF signal 29 | 30 | long time = micros(); // calculating timing since last change 31 | duration = time - lastTime; 32 | lastTime = time; 33 | 34 | // Known error in bit stream is runt/short pulses. 35 | // If we ever get a really short, or really long, 36 | // pulse we know there is an error in the bit stream 37 | // and should start over. 38 | 39 | if ( (duration > (PULSE_LONG + PULSE_RANGE)) || (duration < (PULSE_SHORT - PULSE_RANGE)) ) 40 | { 41 | if (IsyncFound && IchangeCount >= DATABITSEDGES_MIN) //Check to see if we have received a minimum number of bits we could take 42 | { 43 | if (IchangeCount >= DATABYTESCNT_MID) 44 | { 45 | IbytesReceived = 8; // 5-N-1 46 | } else { 47 | IbytesReceived = 7; // tower 48 | } 49 | if (!received) { // lost this message if main wasn't finished with the last one 50 | received = true; // for the main loop 51 | syncIndex = IsyncIndex; 52 | bytesReceived = IbytesReceived; 53 | for ( int i = 0; i < RING_BUFFER_SIZE; i++ ) { 54 | pulseDurations[i] = IpulseDurations[i]; 55 | } 56 | } 57 | IsyncFound = false; 58 | return; 59 | 60 | } else { 61 | IsyncFound = false; 62 | IchangeCount = 0; // restart looking for data bits 63 | } 64 | } 65 | 66 | ringIndex = (ringIndex + 1) % RING_BUFFER_SIZE; // store data in ring buffer 67 | IpulseDurations[ringIndex] = duration; 68 | IchangeCount++; // found another edge 69 | 70 | if ( isSync(ringIndex) ) // detect sync signal 71 | { 72 | IsyncFound = true; 73 | IchangeCount = 0; // restart looking for data bits 74 | IsyncIndex = ringIndex; 75 | } 76 | 77 | if ( IsyncFound ) // If a sync has been found the start looking for the data bit edges. 78 | { 79 | if ( IchangeCount < DATABITSEDGES_MAX ) { // if not enough bits yet, no message received yet 80 | } 81 | else if ( IchangeCount > DATABITSEDGES_MAX ) { 82 | 83 | IsyncFound = false; 84 | } 85 | /* else if (IchangeCount >= DATABYTESCNT_MID) { 86 | 87 | IbytesReceived = 8; // 5-n-1 88 | if (!received) { // lost this message if main wasn't finished with the last one 89 | received = true; // for the main loop 90 | syncIndex = IsyncIndex; 91 | bytesReceived = IbytesReceived; 92 | for ( int i = 0; i < RING_BUFFER_SIZE; i++ ) { 93 | pulseDurations[i] = IpulseDurations[i]; 94 | } 95 | } 96 | IsyncFound = false; 97 | return; 98 | 99 | } */ 100 | else 101 | { 102 | IbytesReceived = 9; // need to find better way to terminate receive !!!!!!!!! Probably use message ID to know how many bytes to get 103 | if (!received) { // lost this message if main wasn't finished with the last one 104 | received = true; // for the main loop 105 | syncIndex = IsyncIndex; 106 | bytesReceived = IbytesReceived; 107 | for ( int i = 0; i < RING_BUFFER_SIZE; i++ ) { 108 | pulseDurations[i] = IpulseDurations[i]; 109 | } 110 | } 111 | IsyncFound = false; 112 | return; 113 | 114 | } 115 | } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /WeatherServer/F_Main.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | * MAIN LOOP * 3 | ************************************************************************************************************************************************ 4 | */ 5 | 6 | /* 7 | Main Loop 8 | Wait for received to be true, meaning a sync stream plus 9 | all of the data bit edges have been found. 10 | Convert all of the pulse timings to bits and calculate 11 | the results. 12 | */ 13 | void loop() { 14 | 15 | ArduinoOTA.handle(); // for over the air updates 16 | 17 | server.handleClient(); // webserver 18 | 19 | if ( received == true ) // There's a messsage ready 20 | { 21 | 22 | unsigned int ringIndex; // where in the bit buffer are we 23 | bool fail = false; // flag to indicate good message or not 24 | byte dataBytes[bytesReceived]; // Decoded message bytes go here 25 | 26 | fail = false; // reset bit decode error flag 27 | 28 | #ifdef DISPLAY_BIT_TIMING 29 | displayBitTiming(); 30 | #endif 31 | 32 | // ******************* Decode the message bit timings into bytes ******************************* 33 | 34 | for ( int i = 0; i < bytesReceived; i++ ) // clear the data bytes array 35 | { 36 | dataBytes[i] = 0; 37 | } 38 | 39 | ringIndex = (syncIndex + 1) % RING_BUFFER_SIZE; 40 | 41 | for ( int i = 0; i < bytesReceived * 8; i++ ) // Decode the timing into message bytes 42 | { 43 | int bit = convertTimingToBit( pulseDurations[ringIndex % RING_BUFFER_SIZE], 44 | pulseDurations[(ringIndex + 1) % RING_BUFFER_SIZE] ); 45 | 46 | if ( bit < 0 ) 47 | { 48 | fail = true; // fails if any invalid bits 49 | break; // exit loop 50 | } 51 | else 52 | { 53 | dataBytes[i / 8] |= bit << (7 - (i % 8)); // build the bytes 54 | } 55 | 56 | ringIndex += 2; 57 | } 58 | 59 | #ifdef DebugOut 60 | // Display the raw data received in hex 61 | if (fail) 62 | { 63 | Serial.println("Data Byte Display : Decoding error."); 64 | } else { 65 | for( int i = 0; i < bytesReceived; i++ ) 66 | { 67 | Serial.print(dataBytes[i], HEX); 68 | Serial.print(","); 69 | } 70 | } 71 | #endif 72 | 73 | // *************** Validate the message via CRC and PARITY checks ****************************** 74 | if (!fail) { 75 | fail = !acurite_parity(dataBytes, bytesReceived); // check parity of each byte 76 | } 77 | 78 | if (!fail) { 79 | fail = !acurite_crc(dataBytes, bytesReceived); // Check checksum of message 80 | } 81 | 82 | // ********************* Determine which sensor it is from ************************************* 83 | if (fail) { 84 | 85 | } else if ((bytesReceived == 7)) { 86 | decode_Acurite_6044(dataBytes); // Small Tower sensor with Temp and Humidity Only 87 | 88 | } else if ((bytesReceived == 8)) { 89 | decode_5n1(dataBytes); //5n1 sensor 90 | 91 | } else if (bytesReceived == 9) { 92 | decode_Acurite_6045(dataBytes); //Lightening detector 93 | 94 | } else { 95 | #ifdef DebugOut 96 | Serial.println("Unknown or Corrupt Message"); // shouldn't be possible, only 7, 8, or 9 byte messages should get here. 97 | #endif 98 | } 99 | received = false; 100 | } // Done with message, if we got one 101 | 102 | else { 103 | 104 | if ((millis() - Next5N1) > 20000) { // check to see if we missed a message, we have if over 20 seconds since the last one 105 | 106 | Add5N1(false, CurrentWind, CurrentDirection, CurrentRain); // missed the next 18.5 second update message 107 | Next5N1 = millis() - 1500; // go back 1.5 seconds in time, since we were 1.5 seconds overdue 108 | #ifdef DebugOut 109 | Serial.println("Missed 5n1"); 110 | #endif 111 | } 112 | if ((millis() - NextTower) > 18000) { 113 | AddTower(false); // missed the nex 16 second tower update 114 | NextTower = millis() - 2000; // go back 2 seconds in time, since we were 2 seconds overdue 115 | #ifdef DebugOut 116 | Serial.println("Missed Tower"); 117 | #endif 118 | } 119 | } 120 | 121 | if ((millis() > midnight) && (midnight != 0)) { // time to reset rain fall? 122 | 123 | UpdateRainCounter(); // reset daily rain fall by estimate that it is midnight 124 | 125 | midnight = millis() + (24 * 60 * 60 * 1000); // 24 hours * 60 minute/hour * 60 seconds/minute * 1000 milliseconds/second 126 | 127 | } 128 | // update Weather Underground if it is time and if enough enough time before next message 129 | // delay WU update if tower or 5N1 message expected very soon 130 | if ((millis() > WUtime) && ((millis() - Next5N1) < 14000) && ((millis() - NextTower) < 12000) && (WUsend)) { 131 | 132 | CurrentPressure = bmp.readPressure() * 0.000295333727; // get the air pressure 133 | WUupdate = UpdateWeatherUnderground(); // send data to Weather Underground 134 | } 135 | 136 | if ((millis()-displaylast) > 1000) { UpdateDisplay(); } // update display once a second 137 | 138 | } 139 | 140 | -------------------------------------------------------------------------------- /WeatherServer/E_Setup.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | * Setup * 3 | ************************************************************************************************************************************************ 4 | */ 5 | 6 | void setup() 7 | { 8 | Serial.begin(BaudRate); 9 | #ifdef DebugOut // stream data out serial for debug 10 | Serial.println(); 11 | Serial.println("Acurite Receive Started..."); 12 | #endif 13 | 14 | pinMode(LED_BUILTIN, OUTPUT); // LED output 15 | digitalWrite(LED_BUILTIN, HIGH); // start with LED off 16 | 17 | Wire.begin(D3, D4); // I2C (SDA, SCL) 18 | Wire.setClock(100000); 19 | 20 | display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize OLED with the I2C addr 0x3C (for the 128x32), turn on display charge pump 21 | display.clearDisplay(); // Display is 128 x 32 dots, 21 characters x 4 lines 22 | display.setTextSize(2); // Size 2 is 10 characters by 2 lines 23 | display.setTextWrap(false); // Don't jump to the next line 24 | display.setTextColor(WHITE,BLACK); // Over write the background with each character 25 | display.setCursor(16,8); // Start kind of centered 26 | display.println("STARTING"); 27 | display.display(); 28 | 29 | bmp.begin(); // initialize pressure sensor 30 | 31 | #ifdef DebugOut // stream data out serial for debug 32 | Serial.println(); 33 | Serial.print("Connecting to "); 34 | Serial.println(ssid); 35 | #endif 36 | 37 | EEPROM.begin(32); // Need two bytes in EEPROM 38 | RainCounter5N1 = (EEPROM.read(0) << 8) | EEPROM.read(1); // recover rain counter from EEPROM 39 | CurrentRainCounter5N1 = RainCounter5N1; 40 | #ifdef DebugOut 41 | Serial.print("Rain Counter loaded from EEPROM is "); 42 | Serial.println(RainCounter5N1,HEX); 43 | #endif 44 | 45 | WiFi.mode ( WIFI_STA ); 46 | WiFi.begin(ssid, password); // connect to the WiFi 47 | 48 | while (WiFi.status() != WL_CONNECTED) { 49 | delay(500); // This needed to give control back to the esp to run its own code! 50 | #ifdef DebugOut // stream data out serial for debug 51 | Serial.print("."); 52 | #endif 53 | } 54 | 55 | #ifdef DebugOut 56 | Serial.println(""); 57 | Serial.println("WiFi connected"); 58 | #endif 59 | 60 | pinMode(DATAPIN, INPUT); // 433MHz radio data input 61 | attachInterrupt(digitalPinToInterrupt(DATAPIN), handler, CHANGE); // interrupt on radio data signal 62 | 63 | display.clearDisplay(); // Start over 64 | display.setTextSize(1); // text is 6 dots x 8 dots 65 | display.setCursor(0,0); 66 | display.print(WiFi.localIP()); // Printing the ESP IP address 67 | display.setCursor(128-(5*6),0); // top line, 5 characters from the end 68 | display.print("WU:"); 69 | display.setCursor(0,12); // Using 3 lines where 4 would fit, so spread out a bit 70 | display.print("5N1 S:00 T:9999999"); // prefil status lines 71 | display.setCursor(0,24); // 654321 72 | display.print("Tower S:00 T:9999999"); 73 | // 123456789012345678901 74 | display.display(); 75 | 76 | #ifdef DebugOut 77 | Serial.println(WiFi.localIP()); 78 | #endif 79 | 80 | server.on ( "/", handleRoot ); // root webpage, just identify the device 81 | 82 | server.on ( "/debug",handleDebug ); // show info for possible debugging 83 | 84 | server.on ( "/DATA",handleData ); // give weather data to HomeSeer 85 | 86 | server.on ( "/RESET",handleReset ); // midnight signal to reset daily rain amount 87 | 88 | server.onNotFound ( handleNotFound ); 89 | 90 | server.begin(); 91 | 92 | // OTA stuff 93 | // Port defaults to 8266 94 | // ArduinoOTA.setPort(8266); 95 | 96 | // Hostname defaults to esp8266-[ChipID] 97 | ArduinoOTA.setHostname(HostName); 98 | 99 | // No authentication by default 100 | // ArduinoOTA.setPassword("admin"); 101 | // Password can be set with it's md5 value as well 102 | // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 103 | // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); 104 | 105 | ArduinoOTA.onStart([]() { 106 | String type; 107 | if (ArduinoOTA.getCommand() == U_FLASH) 108 | type = "sketch"; 109 | else // U_SPIFFS 110 | type = "filesystem"; 111 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 112 | display.clearDisplay(); 113 | display.setTextSize(2); 114 | display.setCursor(0,0); 115 | display.print("OTA"); 116 | display.setTextSize(1); 117 | display.setCursor(6*2*3+6,0); 118 | display.print("Update"); 119 | display.setCursor(6*2*3+6,8); 120 | display.print(type); 121 | display.display(); 122 | display.setCursor(0,8*2); 123 | display.setTextSize(1); 124 | display.print("Progress: "); 125 | detachInterrupt(digitalPinToInterrupt(DATAPIN)); 126 | }); 127 | ArduinoOTA.onEnd([]() { 128 | display.setCursor(0,8*2); 129 | display.setTextSize(2); 130 | display.print("END "); 131 | display.display(); 132 | detachInterrupt(digitalPinToInterrupt(DATAPIN)); 133 | delay(1000); 134 | }); 135 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 136 | display.setCursor(60,8*2); 137 | display.setTextSize(1); 138 | display.print((progress / (total / 100))); 139 | display.display(); 140 | }); 141 | ArduinoOTA.onError([](ota_error_t error) { 142 | display.setTextSize(1); 143 | display.setCursor(0,24); 144 | if (error == OTA_AUTH_ERROR) display.print("Auth Failed"); 145 | else if (error == OTA_BEGIN_ERROR) display.print("Begin Failed"); 146 | else if (error == OTA_CONNECT_ERROR) display.print("Connect Failed"); 147 | else if (error == OTA_RECEIVE_ERROR) display.print("Receive Failed"); 148 | else if (error == OTA_END_ERROR) display.print("End Failed"); 149 | display.display(); 150 | delay(5000); 151 | }); 152 | ArduinoOTA.begin(); 153 | } 154 | 155 | 156 | -------------------------------------------------------------------------------- /WeatherServer/WeatherServer.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | Arduino code decode several Acurite devices OTA data stream. 3 | 4 | Modified 3/21/2018 Joe Roberts 5 | 6 | 7 | Decoding protocol and prototype code from these sources: 8 | Ray Wang (Rayshobby LLC) 9 | http://rayshobby.net/?p=8998 10 | Benjamin Larsson (RTL_433) 11 | https://github.com/merbanan/rtl_433 12 | Brad Hunting (Acurite_00592TX_sniffer) 13 | https://github.com/bhunting/Acurite_00592TX_sniffer 14 | 15 | Written and tested on an Arduino Uno R3 Compatible Board 16 | using a RXB6 433Mhz Superheterodyne Wireless Receiver Module 17 | 18 | This works with these devices but more could be added 19 | Acurite Pro 5-in-1 (8 bytes) 20 | https://tinyurl.com/zwsl9oj 21 | Acurite Ligtning Detector 06045M (9 bytes) 22 | https://tinyurl.com/y7of6dq4 23 | Acurite Room Temp and Humidity 06044M (7 bytes) 24 | https://tinyurl.com/yc9fpx8q 25 | */ 26 | 27 | #define TestBuild // uncomment for the test build instead of actual unit, also turns on debug output 28 | //#define DebugOut // uncomment to stream data out serial port 29 | //#define DISPLAY_BIT_TIMING // uncomment to show debug bit timings 30 | 31 | /************************************************************************************************************************************************ 32 | * INCLUDES * 33 | ************************************************************************************************************************************************ 34 | */ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include // I2C 42 | 43 | #include 44 | #include // air pressure & temperature sensor 45 | 46 | #include // Display 47 | #include // Specific OLED display 48 | 49 | #include // for OTA updates 50 | #include 51 | #include 52 | 53 | /************************************************************************************************************************************************ 54 | * DEFINES * 55 | ************************************************************************************************************************************************ 56 | */ 57 | 58 | #define OLED_RESET 4 // For I2C OLED display 59 | 60 | #define BaudRate 115200 // baud rate for serial debug data --- should set as high as possible instead of 115200 61 | 62 | 63 | #define RING_BUFFER_SIZE 152 // ring buffer size has to be large enough to fit data and sync signal 64 | 65 | #define SYNC_HIGH 610 // nominal data and sync bit sizes in msec ** was 600 66 | #define SYNC_LOW 610 67 | 68 | #define PULSE_LONG 400 69 | #define PULSE_SHORT 210 // ** was 220 70 | 71 | #define PULSE_RANGE 100 // tolerance on sync and data pulses 72 | 73 | #define BIT1_HIGH_MIN (PULSE_LONG-PULSE_RANGE) //BIT1 74 | #define BIT1_HIGH_MAX (PULSE_LONG+PULSE_RANGE) 75 | #define BIT1_LOW_MIN (PULSE_SHORT-PULSE_RANGE) 76 | #define BIT1_LOW_MAX (PULSE_SHORT+PULSE_RANGE) 77 | 78 | #define BIT0_HIGH_MIN (PULSE_SHORT-PULSE_RANGE) //BIT0 79 | #define BIT0_HIGH_MAX (PULSE_SHORT+PULSE_RANGE) 80 | #define BIT0_LOW_MIN (PULSE_LONG-PULSE_RANGE) 81 | #define BIT0_LOW_MAX (PULSE_LONG+PULSE_RANGE) 82 | 83 | #define SYNCPULSECNT (4) // 4 pulses (8 edges) 84 | #define SYNCPULSEEDGES (SYNCPULSECNT*2) 85 | 86 | #define DATABYTESCNT_MIN (7) // Minimum number of data bytes 87 | #define DATABITSCNT_MIN (DATABYTESCNT_MIN*8) // 7 bytes * 8 bits 88 | #define DATABITSEDGES_MIN (DATABITSCNT_MIN*2) 89 | 90 | #define DATABYTESCNT_MID 128 // 8 Bytes 91 | 92 | #define DATABYTESCNT_MAX (9) // 9 Bytes 93 | #define DATABITSCNT_MAX (DATABYTESCNT_MAX*8) 94 | #define DATABITSEDGES_MAX (DATABITSCNT_MAX*2) 95 | 96 | 97 | #define DATAPIN (D1) // radio data pin, must use pin that can interrupt on any level change 98 | 99 | // 5n1 Tower Message Types 100 | #define MT_WS_WD_RF 49 // wind speed, wind direction, rainfall 101 | #define MT_WS_T_RH 56 // wind speed, temp, RH 102 | 103 | 104 | 105 | 106 | /************************************************************************************************************************************************ 107 | * Global Constants * 108 | ************************************************************************************************************************************************ 109 | */ 110 | 111 | char Version[] = "Build 3/21/2018 A"; 112 | 113 | const char* ssid = "Your SSID"; // local WiFI 114 | const char* password = "Your Password"; 115 | 116 | #ifdef TestBuild 117 | char HostName[] = "wserver-test"; // test build unit name 118 | #else 119 | char HostName[] = "wserver"; // actual build name 120 | #endif 121 | 122 | //char WUserver [] = "weatherstation.wunderground.com"; // normal 123 | char WUserver [] = "rtupdate.wunderground.com"; // rapid fire 124 | char WUpage [] = "GET /weatherstation/updateweatherstation.php?"; 125 | char WUID [] = "Your station ID"; 126 | char WUpassword [] = "Your password"; 127 | 128 | #ifdef TestBuild 129 | bool WUsend = false; // don't send to WU if this is a test unit 130 | #else 131 | bool WUsend = true; // true to send to WU 132 | #endif 133 | 134 | int ID5N1 = 0x0000; // 5N1 ID, set to 0 to allow any 5N1 135 | int IDTower = 0x0000; // Tower ID, set to 0 to allow any tower 136 | 137 | const float winddirections[] = { 315.0, 247.5, 292.5, 270.0, // wind directions in degrees for WU for 5-N-1 138 | 337.5, 225.0, 0.0, 202.5, 139 | 67.5, 135.0, 90.0, 112.5, 140 | 45.0, 157.5, 22.5, 180.0 141 | }; 142 | 143 | char * acurite_5n1_winddirection_str[] = // Wind directions in standard terms, not really needed ** 144 | { "NW", // 0 315 145 | "WSW", // 1 247.5 146 | "WNW", // 2 292.5 147 | "W", // 3 270 148 | "NNW", // 4 337.5 149 | "SW", // 5 225 150 | "N", // 6 0 151 | "SSW", // 7 202.5 152 | "ENE", // 8 67.5 153 | "SE", // 9 135 154 | "E", // 10 90 155 | "ESE", // 11 112.5 156 | "NE", // 12 45 157 | "SSE", // 13 157.5 158 | "NNE", // 14 22.5 159 | "S" // 15 180 160 | }; 161 | 162 | 163 | /************************************************************************************************************************************************ 164 | * Global Variables * 165 | ************************************************************************************************************************************************ 166 | */ 167 | 168 | // The pulse durations are the measured time in milliseconds between pulse edges 169 | unsigned long IpulseDurations[RING_BUFFER_SIZE]; 170 | //unsigned int IsyncIndex = 0; // index of the last bit time of the sync signal 171 | unsigned int IchangeCount = 0; 172 | 173 | unsigned long pulseDurations[RING_BUFFER_SIZE]; 174 | unsigned int syncIndex = 0; // index of the last bit time of the sync signal 175 | bool received = false; // true if sync plus enough bits found 176 | unsigned int bytesReceived = 0; 177 | 178 | 179 | 180 | 181 | 182 | int RainCounter5N1 = 0; // 5-n-1 rain counter, refreshed from eeprom 183 | int CurrentRainCounter5N1 = 0; 184 | float RainYesterday = 0; 185 | 186 | bool Mess5N1[33] = {false, false, false, false, false, false, false, false, false, false, false, // good message flags for last 33 messages 187 | false, false, false, false, false, false, false, false, false, false, false, 188 | false, false, false, false, false, false, false, false, false, false, false}; 189 | float WindSpeed[33] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // table of last 33 wind speed readings 190 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 191 | float Rain[33] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // table of last 33 rain fall readings 192 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 193 | int WindDirection[33]; 194 | 195 | float WindSpeedAverage = 0; // average wind speed (over 10 minutes) 196 | float WindSpeedAverage2m = 0; // average wind speed (over 2 minutes) 197 | float WindSpeedPeak = 0; // Peak wind speed (over 10 minutes) 198 | int WindPeakDirection = 0; // wind direction of 10 minute peak 199 | float WindSpeedPeak2m = 0; // Peak wind speed (over 2 minutes) 200 | int WindPeakDirection2m = 0; // wind direction of 2 minute peak 201 | float RainRate = 0; // Predicted rainfall rate (inchs/hour) 202 | unsigned long Next5N1 = 0; // track time between messages from 5n1 203 | unsigned long LastGood5N1 = 0; // time of last good 5N1 message received 204 | int Signal5N1 = 0; // Signal quality (0 = none, 10 = full) based on 3 minutes of messages 205 | bool BatteryLow5N1 = false; // flag for low battery for 5-n-1 206 | float CurrentRain = 0; // current amount of rain today 207 | float Temperature5N1 = -999; 208 | float CurrentWind = 0; 209 | int CurrentDirection = 0; 210 | int Humidity5N1 = 0; 211 | float CurrentPressure = 0; 212 | unsigned long midnight = 0; // estimated time of next midnight, in milliseconds 213 | 214 | 215 | float TemperatureTower = -999; 216 | int HumidityTower = 0; 217 | bool BatteryLowTower = false; 218 | int SignalTower = 0; 219 | bool MessTower[11] = {false, false, false, false, false, false, false, false, false, false, false}; // good message flags for last 11 messages 220 | unsigned long NextTower = 0; // timer between messages from the tower temp/humidty sensor 221 | unsigned long LastGoodTower = 0; // time of last good tower message received 222 | 223 | int strikeTot = 0; // Lightning sensor stuff 224 | int strikeWrapOffset = 0; 225 | int lastStrikeCount = 0; 226 | unsigned long lightninglast = 0; // timer between messages from the lightning detector 227 | 228 | 229 | unsigned long WUtime = 1000 * 60 * 5; // time to send next Weather Underground message. normally every 37 seconds, but wait 5 minutes to begin with 230 | String WUresponse = "No send attempt yet"; 231 | bool WUupdate = false; // was the last update to Weather Underground successful? 232 | 233 | unsigned long displaylast = 0; 234 | 235 | 236 | 237 | /************************************************************************************************************************************************ 238 | * Library Stuff * 239 | ************************************************************************************************************************************************ 240 | */ 241 | 242 | Adafruit_SSD1306 display(OLED_RESET); // OLED display 243 | 244 | Adafruit_BMP280 bmp; // I2C Pressure Sensor 245 | 246 | ESP8266WebServer server ( 80 ); // Web stuff 247 | 248 | 249 | 250 | #ifdef TestBuild 251 | #define DebugOut 252 | #endif 253 | -------------------------------------------------------------------------------- /WeatherServer/C_Web.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | * UpdateWeatherUnderground() : Routine to send weather data to Weather Underground, rapidfire * 3 | * Returns true if WU accepted the data, false if anything went wrong * 4 | ************************************************************************************************************************************************ 5 | */ 6 | 7 | bool UpdateWeatherUnderground() { 8 | 9 | float avghumidity = 0; 10 | #ifdef DebugOut 11 | Serial.print("Updating WU, "); 12 | #endif 13 | 14 | WiFiClient client; // connect to Weather Underground 15 | if (!client.connect(WUserver, 80)) { 16 | 17 | WUresponse = "Connection Failed"; // Couldn't get a connection! 18 | WUtime = millis() + 60 * 1000; // Try WU update again in 1 minute 19 | if (WUtime < millis()) { WUtime = 0; } // too close to rollover, wait for rollover 20 | #ifdef DebugOut 21 | Serial.println("Connection Fail"); 22 | #endif 23 | return false; 24 | } 25 | 26 | client.print(WUpage); 27 | client.print("ID="); 28 | client.print(WUID); 29 | client.print("&PASSWORD="); 30 | client.print(WUpassword); 31 | client.print("&dateutc="); 32 | client.print("now"); 33 | 34 | client.print("&baromin="); // air pressure from internal sensor 35 | client.print(CurrentPressure,2); 36 | 37 | if (Signal5N1 > 4) { // Send 5N1 data if available 38 | 39 | if (CurrentWind > 1) { // is there enough wind to send a wind direction? 40 | client.print("&winddir="); 41 | client.print(winddirections[CurrentDirection],1); 42 | } 43 | client.print("&windspeedmph="); 44 | client.print(CurrentWind,1); 45 | if (WindSpeedPeak > 1) { 46 | client.print("&windgustmph_10="); // 10 minute peak, they don't seem to do anything with this 47 | client.print(WindSpeedPeak,1); 48 | client.print("&windgustdir_10m="); 49 | client.print(winddirections[WindPeakDirection],1); 50 | } 51 | client.print("&windspdmph_avg2m="); 52 | client.print(WindSpeedAverage2m,1); 53 | if (WindSpeedPeak2m > 1) { 54 | client.print("&windgustmph="); // they don't really say how long for gust, use 2 minute peak 55 | client.print(WindSpeedPeak2m,1); 56 | client.print("&windgustdir="); 57 | client.print(winddirections[WindPeakDirection2m],1); 58 | } 59 | if ((CurrentRain >= 0) && (RainRate >=0)) { 60 | client.print("&rainin="); 61 | client.print(RainRate,2); 62 | client.print("&dailyrainin="); 63 | client.print(CurrentRain,2); 64 | } 65 | } 66 | 67 | if ((SignalTower > 5) && (TemperatureTower > -40) && (HumidityTower > 0) && 68 | (Signal5N1 > 5) && (Temperature5N1 > -40) && (Humidity5N1 > 0)) { // average them if getting both 69 | client.print("&tempf="); 70 | client.print((TemperatureTower + Temperature5N1)/2,1); 71 | client.print("&humidity="); 72 | avghumidity = (HumidityTower + Humidity5N1)/2; 73 | client.print(avghumidity,0); 74 | client.print("&dewptf="); 75 | client.print(DewPointF((TemperatureTower + Temperature5N1)/2,avghumidity)); 76 | } else if ((SignalTower > 2) && (TemperatureTower > -40) && (HumidityTower > 0)) { // still getting tower readings? 77 | client.print("&tempf="); // from the Tower 78 | client.print(TemperatureTower,1); 79 | client.print("&humidity="); 80 | client.print(HumidityTower,DEC); 81 | client.print("&dewptf="); 82 | client.print(DewPointF(TemperatureTower,HumidityTower)); 83 | } else if ((Signal5N1 > 2) && (Temperature5N1 > -40) && (Humidity5N1 > 0)) { // still getting 5n1 readings? 84 | client.print("&tempf="); // from the 5n1 thsn 85 | client.print(Temperature5N1,1); 86 | client.print("&humidity="); 87 | client.print(Humidity5N1,DEC); 88 | client.print("&dewptf="); 89 | client.print(DewPointF(Temperature5N1,Humidity5N1)); 90 | } 91 | 92 | client.print("&softwaretype=Custom%20version1.3&action=updateraw&realtime=1&rtfreq=37"); // finish up 93 | client.println("/ HTTP/1.1\r\nHost: host:port\r\nConnection: close\r\n\r\n"); 94 | 95 | WUresponse = ""; 96 | while(client.connected()) { // get the response 97 | while (client.available()) { 98 | WUresponse += char(client.read()); 99 | } 100 | } 101 | client.flush(); 102 | client.stop(); 103 | 104 | #ifdef DebugOut 105 | Serial.print("WU Response: "); 106 | Serial.println(WUresponse); 107 | #endif 108 | 109 | if (WUresponse.indexOf("success") > -1) { 110 | 111 | WUtime = millis() + 37 * 1000; // WU update again in 37 seconds 112 | if (WUtime < millis()) { WUtime = 0; } // too close to rollover, wait for rollover 113 | return true; 114 | 115 | } else { 116 | 117 | WUtime = millis() + 10 * 60 * 1000; // Try WU update again in 10 minutes 118 | if (WUtime < millis()) { WUtime = 0; } // too close to rollover, wait for rollover 119 | return false; 120 | } 121 | 122 | 123 | } 124 | 125 | 126 | 127 | 128 | /************************************************************************************************************************************************ 129 | ************************************************************************************************************************************************ 130 | ** Webpages ** 131 | ************************************************************************************************************************************************ 132 | ************************************************************************************************************************************************ 133 | */ 134 | 135 | 136 | /************************************************************************************************************************************************ 137 | * Root * 138 | * Just identify ourselves * 139 | ************************************************************************************************************************************************ 140 | */ 141 | 142 | void handleRoot() { 143 | 144 | server.send ( 200, "text/plain", "Personal Weather Server\r\n\ndebug = current debug data\r\nDATA = weather data for HomeSeer\r\nRESET = midnight reset of rain fall" ); 145 | 146 | } 147 | 148 | 149 | /************************************************************************************************************************************************ 150 | * debug * 151 | * Provide debug data * 152 | ************************************************************************************************************************************************ 153 | */ 154 | 155 | void handleDebug() { 156 | 157 | float DTime = 0; 158 | float Level433 = 0; 159 | 160 | String debugstr ="Weather Server Debug Page \r\nVersion: "; // system 161 | debugstr += Version; 162 | debugstr += "\r\n"; 163 | DTime = millis()/(10 * 60 * 60); 164 | DTime = DTime/100; // do it this way to get 1/100th, otherwise comes as an integer 165 | debugstr += String(DTime,2); 166 | 167 | debugstr += " hours uptime\r\n\nWiFi signal level = "; // Radio levels 168 | debugstr += String(WiFi.RSSI()); 169 | 170 | debugstr += " dBm, 433MHz receiver signal level = "; // Crude approximation from the MICRF211 data sheet 171 | Level433 = (((analogRead(A0) - 74)*80)/641) - 120; // note that the AD is apparently 0 to 3.2 Volt input range (default) 172 | Level433 = constrain(Level433, -120, -40); // 0V = 0, 3.2V = 1024 173 | debugstr += String(Level433,0); 174 | 175 | debugstr += " dBm\r\n\nTime to next WU update = "; // Weather Underground 176 | DTime = (WUtime - millis())/1000; 177 | if (WUtime > millis()) { 178 | debugstr += String(DTime,1); 179 | } else { 180 | debugstr += "Overdue"; 181 | } 182 | debugstr += " seconds, Last WU response:\r\n"; 183 | debugstr += WUresponse; 184 | 185 | debugstr += "\r\n\nAir Pressure = "; 186 | debugstr += String(CurrentPressure,2); // Air pressure 187 | debugstr += " inHg, "; 188 | debugstr += String(bmp.readPressure()); 189 | debugstr += " Pa, Receiver Temperature = "; 190 | debugstr += String((bmp.readTemperature()* 9 / 5) +32,1); 191 | 192 | debugstr += " F\r\n\n5-in-1 Sensor:\r\nETA of next message: "; // 5-in-1 193 | DTime = (Next5N1 + 18500 - millis())/1000; 194 | debugstr += String(DTime,0); 195 | debugstr += " seconds, Last good message was "; 196 | DTime = (millis()-LastGood5N1)/1000; 197 | debugstr += String(DTime,0); 198 | debugstr += " seconds ago, Signal quality = "; 199 | debugstr += String(Signal5N1,DEC); 200 | debugstr += " out of 10\r\nTemperature = "; 201 | debugstr += String(Temperature5N1,1); 202 | debugstr += " F, Relative Humidity = "; 203 | debugstr += String(Humidity5N1,DEC); 204 | debugstr += "%\r\nDaily Rain = "; 205 | debugstr += String(CurrentRain,2); 206 | debugstr += " inches, Current Rain Rate = "; 207 | debugstr += String(RainRate,2); 208 | debugstr += " inches/hour, Rain Counter = "; 209 | debugstr += String(RainCounter5N1,HEX); 210 | debugstr += "\r\nWind = "; 211 | debugstr += String(CurrentWind,1); 212 | debugstr += " MPH, Direction = "; 213 | debugstr += acurite_5n1_winddirection_str[CurrentDirection]; 214 | debugstr += ", Average = "; 215 | debugstr += String(WindSpeedAverage,1); 216 | debugstr += " MPH, Peak = "; 217 | debugstr += String(WindSpeedPeak,1); 218 | debugstr += " MPH at "; 219 | debugstr += acurite_5n1_winddirection_str[WindPeakDirection]; 220 | if (BatteryLow5N1 == true) { 221 | debugstr += " Direction\r\nBattery Low\r\n\n"; 222 | } else { 223 | debugstr += " Direction\r\n\n"; 224 | } 225 | 226 | debugstr += "Tower Sensor:\r\nETA of next message: "; // Tower 227 | DTime = (NextTower + 18500 - millis())/1000; 228 | debugstr += String(DTime,0); 229 | debugstr += " seconds, Last good message was "; 230 | DTime = (millis()-LastGoodTower)/1000; 231 | debugstr += String(DTime,0); 232 | debugstr += " seconds ago, Signal quality = "; 233 | debugstr += String(SignalTower,DEC); 234 | debugstr += " out of 10\r\nTemperature = "; 235 | debugstr += String(TemperatureTower,1); 236 | debugstr += " F, Relative Humidity = "; 237 | debugstr += String(HumidityTower,DEC); 238 | if (BatteryLowTower == true) { 239 | debugstr += "%\r\nBattery Low\r\n\nReading#, 5N1 Signal, Tower Signal, Rain, Wind Speed, Direction\r\n"; 240 | } else { 241 | debugstr += "%\r\n\nReading#, 5N1 Signal, Tower Signal, Rain, Wind Speed, Direction\r\n"; 242 | } 243 | 244 | for ( int i = 32; i > 0; i-- ) { // signal, wind, and rain array 245 | debugstr += String(i,DEC); 246 | if (i < 10) { 247 | debugstr += " "; 248 | } 249 | if (Mess5N1[i] == true) { 250 | debugstr += " Good "; 251 | } else { 252 | debugstr += " Missed "; 253 | } 254 | 255 | if (i < 11) { // only have ten message flags for tower sensor 256 | if (MessTower[i] == true) { 257 | debugstr += "Good "; 258 | } else { 259 | debugstr += "Missed "; 260 | } 261 | } else { 262 | debugstr += " "; 263 | } 264 | 265 | debugstr += String(Rain[i],2); 266 | debugstr += " "; 267 | debugstr += String(WindSpeed[i],2); 268 | debugstr += " "; 269 | debugstr += acurite_5n1_winddirection_str[WindDirection[i]]; 270 | debugstr += "\r\n"; 271 | } 272 | debugstr += "\r\n"; 273 | 274 | server.send ( 200, "text/plain", debugstr ); 275 | 276 | } 277 | 278 | 279 | /************************************************************************************************************************************************ 280 | * DATA * 281 | * Give Weather data to HomeSeer. Data is: * 282 | * Signal5N1, Temperature5N1, Humidity5N1, CurrentWind, WindSpeedAverage, WindSpeedPeak, CurrentRain, RainRate, CurrentPressure, * 283 | * BatteryLow5N1, BatteryLowTower * 284 | ************************************************************************************************************************************************ 285 | */ 286 | 287 | void handleData() { 288 | 289 | CurrentPressure = bmp.readPressure() * 0.000295333727; // get the air pressure 290 | String Wdata =""; 291 | 292 | Wdata += String(Signal5N1,DEC); // Signal quality from 0 to 10 293 | Wdata += ","; 294 | 295 | Wdata += String(Temperature5N1,1); // Temperature from - 40.0 to 140.0 in degrees F from the 5n1 296 | Wdata += ","; 297 | Wdata += String(Humidity5N1,DEC); // Relative humidity from 0 to 99 % from the 5n1 298 | Wdata += ","; 299 | 300 | Wdata += String((WindSpeed[32]+WindSpeed[31]+WindSpeed[30]+WindSpeed[29])/4,1); // Current wind speed from 0.0 to 98.8 MPH 301 | Wdata += ","; 302 | 303 | Wdata += String(WindSpeedAverage,1); // Average wind speed from 0.0 to 98.8 MPH 304 | Wdata += ","; 305 | 306 | Wdata += String(WindSpeedPeak,0); // Peak wind speed from 0 to 98.8 MPH 307 | Wdata += ","; 308 | 309 | Wdata += String(CurrentRain,2); // Current daily rain fall amount from 0.00 to ??.?? inches 310 | Wdata += ","; 311 | 312 | Wdata += String(RainRate,2); // Current daily rain fall amount from 0.00 to ??.?? inches 313 | Wdata += ","; 314 | 315 | Wdata += String(CurrentPressure,2); // Current air pressure in inHg 316 | Wdata += ","; 317 | 318 | if (BatteryLow5N1 == true) { // AcuRite 5N1 low battery flag 319 | Wdata += "Low,"; 320 | } else { 321 | Wdata += "Ok,"; 322 | } 323 | 324 | if (BatteryLowTower == true) { // AcuRite Tower low battery flag 325 | Wdata += "Low,"; 326 | } else { 327 | Wdata += "Ok,"; 328 | } 329 | 330 | Wdata += String(SignalTower,DEC); // Acurite Tower Signal quality 331 | 332 | Wdata += ","; 333 | Wdata += String(TemperatureTower,1); // Temperature from the tower 334 | 335 | Wdata += ","; 336 | Wdata += String(HumidityTower,DEC); // Relative humidity from 0 to 99 % from the tower 337 | 338 | Wdata += ",END"; 339 | 340 | server.send ( 200, "text/plain", Wdata ); 341 | 342 | } 343 | 344 | 345 | /************************************************************************************************************************************************ 346 | * RESET * 347 | * Reset daily rainfall, assume it is midnight * 348 | ************************************************************************************************************************************************ 349 | */ 350 | 351 | void handleReset() { 352 | 353 | String Wdata =""; 354 | 355 | Wdata = " Today's total rain was "; 356 | Wdata += String(CurrentRain,2); 357 | Wdata += " inches."; 358 | 359 | UpdateRainCounter(); // reset daily rain fall 360 | 361 | midnight = millis() + (24 * 60 * 60 * 1000) + 5000; // 24 hours * 60 minute/hour * 60 seconds/minute * 1000 milliseconds/second 362 | // add 5 seconds to avoid reset before next reset command. 363 | 364 | server.send ( 200, "text/plain", Wdata ); // today's (yesterday's really) total rain fall 365 | 366 | } 367 | 368 | 369 | /************************************************************************************************************************************************ 370 | * Requested page doesn't exist * 371 | ************************************************************************************************************************************************ 372 | */ 373 | 374 | void handleNotFound() { 375 | 376 | server.send ( 404, "text/plain", "Page Not Found" ); 377 | 378 | } 379 | 380 | -------------------------------------------------------------------------------- /WeatherServer/B_Subs.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | ************************************************************************************************************************************************ 3 | ** Subroutines ** 4 | ************************************************************************************************************************************************ 5 | ************************************************************************************************************************************************ 6 | */ 7 | 8 | 9 | /************************************************************************************************************************************************ 10 | * Add5N1(messFlag,wind,rain) : Routine to add last reading to an array of readings for wind and rain * 11 | * messFlag = true if last message received, false if missed, wind = last received wind reading, winddirection = last wind direction, * 12 | * rain = last received rain reading * 13 | * * 14 | * Signal5N1 : Calculate signal quality based on how many of the last ten messages were received on time. 10 = great, 0 = not at all * 15 | * WindSpeedAverage : Average of the last 33 wind speed readings (10 minute average) * 16 | * WindSpeedAverage2m : Average of the last 7 wind speed readings (2 minute average) * 17 | * WindSpeedPeak : Highest of the last 33 wind speed readings (10 minute peak) * 18 | * WindSpeedPeak2m : Highest of the last 7 wind speed readings (2 minute peak) * 19 | * WindPeakDirection : Wind direction during peak wind of the last 10 minutes * 20 | * WindPeakDirection2m: Wind direction during peak wind of the last 2 minutes * 21 | * RainRate : Amount of rainfall in the last 10 minutes used to estimate the hourly rate * 22 | ************************************************************************************************************************************************ 23 | */ 24 | 25 | void Add5N1(bool messFlag, float wind, int windDirection, float rain) { // Add data to top of readings array, drop off oldest reading 26 | 27 | float OldRate = RainRate; 28 | // 0 is oldest, 32 will be newest 29 | Signal5N1 = 0; // start at zero 30 | WindSpeedPeak = 0; 31 | WindSpeedPeak2m = 0; 32 | WindSpeedAverage = 0; 33 | WindSpeedAverage2m = 0; 34 | 35 | for (uint8_t i = 0; i < 32; i++) // move the data down 36 | { 37 | Mess5N1[i] = Mess5N1[i + 1]; 38 | 39 | if (i > 22) { // only use the last 10 40 | Signal5N1 += Mess5N1[i]; // count up the good messages 41 | } 42 | 43 | if (i > 25) { // look at last seven readings for 2 minute calculations 44 | WindSpeedAverage2m += WindSpeed[i]; // add up the wind speeds 45 | if (WindSpeed[i] > WindSpeedPeak2m) { 46 | WindSpeedPeak2m = WindSpeed[i]; // update peak if this reading is higher 47 | WindPeakDirection2m = WindDirection[i]; 48 | } 49 | } 50 | 51 | WindSpeed[i] = WindSpeed[i + 1]; 52 | WindDirection[i] = WindDirection[i+1]; 53 | WindSpeedAverage += WindSpeed[i]; // add up the windspeeds 54 | 55 | if (WindSpeed[i] > WindSpeedPeak) { 56 | WindSpeedPeak = WindSpeed[i]; // update peak if this reading is higher 57 | WindPeakDirection = WindDirection[i]; 58 | } 59 | Rain[i] = Rain[i + 1]; 60 | } 61 | 62 | Mess5N1[32] = messFlag; 63 | Signal5N1 += messFlag; 64 | 65 | WindSpeed[32] = wind; 66 | WindDirection[32] = windDirection; 67 | WindSpeedAverage = (WindSpeedAverage + wind) / 33; // average over 33 readings (10 minutes) 68 | if (wind > WindSpeedPeak) { 69 | WindSpeedPeak = wind; 70 | WindPeakDirection = windDirection; 71 | } 72 | 73 | WindSpeedAverage2m = (WindSpeedAverage2m + wind) / 7; // average over 7 readings (2 minutes) 74 | if (wind > WindSpeedPeak2m) { 75 | WindSpeedPeak2m = wind; 76 | WindPeakDirection2m = windDirection; 77 | } 78 | 79 | Rain[32] = rain; 80 | 81 | if (millis() < (11 * 60 * 1000)) { // Might be false rain rate if we just powered up 82 | RainRate = 0; 83 | } else { 84 | if ((Rain[32] - Rain[0]) < 0) { 85 | RainRate = (Rain[32] + RainYesterday - Rain[0]) * (3600/592); // Don't zero the rate at midnight 86 | } else { 87 | RainRate = (Rain[32] - Rain[0]) * (3600/592); // convert from rain/592 seconds to rain/3600 seconds (hour) 88 | } 89 | RainRate = (RainRate + (9 * OldRate))/10; // calculate rain rate and weighted average with previous rate to reduce spikes 90 | } 91 | 92 | }; 93 | 94 | 95 | /************************************************************************************************************************************************ 96 | * AddTower(messFlag) : Routine to add last reading to an array of readings for tower signal * 97 | * messFlag = true if last message received, false if missed * 98 | * * 99 | * SignalTower : Calculate signal quality based on how many of the last ten messages were received on time. 10 = great, 0 = not at all * 100 | ************************************************************************************************************************************************ 101 | */ 102 | 103 | void AddTower(bool messFlag) { // Add data to top of readings array, drop off oldest reading 104 | // 0 is oldest, 10 will be newest 105 | SignalTower = 0; // start at zero 106 | 107 | for (uint8_t i = 0; i < 10; i++) // move the data down 108 | { 109 | MessTower[i] = MessTower[i + 1]; 110 | SignalTower = SignalTower + MessTower[i]; // count up the good messages 111 | } 112 | 113 | MessTower[10] = messFlag; 114 | SignalTower = SignalTower - MessTower[0] + messFlag; 115 | 116 | }; 117 | 118 | 119 | /************************************************************************************************************************************************ 120 | * decode_5n1(dataBytes[]) : Routine to decode the message from the 5-n-1 sensor * 121 | * dataBytes{] is the array holding the received message. * 122 | * * 123 | * The 5-in-1 transmits every 18 seconds, alternating between two message types * 124 | * Message type 1 transmits Switch, ID, Wind, Temperature, and Humidity * 125 | * Message type 2 transmits Switch, ID, Wind, Wind direction, and Rainfall * 126 | * Wind updates every 18 seconds, Temperature, Humidity, Wind direction, and Rainfall update every 36 seconds * 127 | ************************************************************************************************************************************************ 128 | */ 129 | 130 | void decode_5n1(byte dataBytes[]) { // 5-n-1 should transmit 3 times every 18 seconds. 131 | // 36 seconds before each message type repeats. 132 | // Effectively, wind is every 18 seconds 133 | int messtype = dataBytes[2] & 0x3F; 134 | 135 | #ifdef DebugOut 136 | Serial.print(millis() - LastGood5N1); 137 | Serial.print("ms - "); 138 | Serial.print("5n1 - "); 139 | Serial.print(acurite_txr_getSensorId(dataBytes[0], dataBytes[1]), HEX); 140 | Serial.print(" CH:"); 141 | Serial.print(GetLetter(dataBytes[0])); 142 | Serial.print(" N:"); 143 | Serial.print(GetMessageNo(dataBytes[0])); 144 | Serial.print("; W:"); 145 | Serial.print(convKphMph(acurite_getWindSpeed_kph(dataBytes[3], dataBytes[4]))); 146 | if (messtype == MT_WS_WD_RF) { // Wind Speed, Direction and Rainfall 147 | Serial.print("; D:"); 148 | Serial.print(acurite_5n1_winddirection_str[dataBytes[4] & 0x0F]); 149 | Serial.print("; R:"); 150 | Serial.print(acurite_getRainfall(dataBytes[5], dataBytes[6])); 151 | Serial.print(";"); 152 | } else if (messtype == MT_WS_T_RH) { // Wind speed, Temp, Humidity 153 | Serial.print("; T:"); 154 | Serial.print(acurite_getTemp_5n1(dataBytes[4], dataBytes[5])); 155 | Serial.print("; H:"); 156 | Serial.print(acurite_getHumidity(dataBytes[6])); 157 | Serial.print("%;"); 158 | } 159 | if ((dataBytes[2] & 0x40) != 0x40) { // Check for low battery warning 160 | Serial.print("Low Battery "); 161 | } 162 | 163 | /* if ((Temperature5N1 > 90) || (Temperature5N1 < 40) || (CurrentWind > 20) || (Humidity5N1 < 1) || (Humidity5N1 > 99)) { 164 | Serial.print("****"); // easier to search for problem in debug log 165 | } */ 166 | #endif 167 | if ((acurite_txr_getSensorId(dataBytes[0], dataBytes[1]) == ID5N1) || (ID5N1 == 0)) { // is this our 5N1? 168 | if (((millis() - LastGood5N1) > 500) || (Signal5N1 == 0)) { // make sure this isn't a redundant message (came in too soon after last good one) 169 | 170 | CurrentWind = convKphMph(acurite_getWindSpeed_kph(dataBytes[3], dataBytes[4])); 171 | 172 | if (messtype == MT_WS_WD_RF) { // Wind Speed, Direction and Rainfall 173 | 174 | CurrentDirection = dataBytes[4] & 0x0F; 175 | CurrentRain = acurite_getRainfall(dataBytes[5], dataBytes[6]); 176 | } else if (messtype == MT_WS_T_RH) { // Wind speed, Temp, Humidity 177 | 178 | Temperature5N1 = acurite_getTemp_5n1(dataBytes[4], dataBytes[5]); 179 | Humidity5N1 = acurite_getHumidity(dataBytes[6]);\ 180 | } 181 | if ((dataBytes[2] & 0x40) != 0x40) { // Check for low battery warning 182 | BatteryLow5N1 = true; 183 | } else { 184 | BatteryLow5N1 = false; 185 | } 186 | Next5N1 = millis(); // reset the message timers 187 | LastGood5N1 = millis(); 188 | Add5N1(true, CurrentWind, CurrentDirection, CurrentRain); // log readings 189 | } else { 190 | #ifdef DebugOut 191 | Serial.print("- Redundant"); 192 | #endif 193 | } 194 | } else { 195 | #ifdef DebugOut 196 | Serial.print(" - Wrong ID"); 197 | #endif 198 | } 199 | #ifdef DebugOut 200 | Serial.println(); 201 | #endif 202 | } 203 | 204 | 205 | /************************************************************************************************************************************************ 206 | * decodeAcurite_6044(dataBytes[]) : Routine to decode the message from the tower sensor * 207 | * dataBytes{] is the array holding the received message. * 208 | * * 209 | * The tower sensor transmits temperature and humidity every 16 seconds * 210 | ************************************************************************************************************************************************ 211 | */ 212 | 213 | void decode_Acurite_6044(byte dataBytes[]) { // transmits ? times every 16 seconds 214 | 215 | #ifdef DebugOut 216 | Serial.print(millis() - LastGoodTower); 217 | Serial.print("ms - "); 218 | Serial.print("Tower - "); 219 | Serial.print(acurite_txr_getSensorId(dataBytes[0], dataBytes[1]), HEX); 220 | Serial.print("; T:"); 221 | Serial.print(convCF(acurite_getTemp_6044M(dataBytes[4], dataBytes[5]))); 222 | Serial.print("; H:"); 223 | Serial.print(acurite_getHumidity(dataBytes[3])); 224 | Serial.print(" %;"); 225 | if ((dataBytes[4] & 0x20) == 0x20 ) { 226 | Serial.print("Battery Low;"); 227 | } 228 | #endif 229 | 230 | if ((acurite_txr_getSensorId(dataBytes[0], dataBytes[1]) == IDTower) || (IDTower == 0)) { // is it our tower sensor? 231 | if (((millis() - LastGoodTower) > 300) || (SignalTower == 0)) { // make sure this isn't a redundant message (came in too soon after last good one) 232 | 233 | TemperatureTower = convCF(acurite_getTemp_6044M(dataBytes[4], dataBytes[5])); 234 | HumidityTower = acurite_getHumidity(dataBytes[3]); 235 | if ((dataBytes[4] & 0x20) == 0x20 ) { 236 | BatteryLowTower = true; 237 | } else { 238 | BatteryLowTower = false; 239 | } 240 | 241 | NextTower = millis(); 242 | LastGoodTower = millis(); 243 | AddTower(true); // log readings 244 | 245 | } else { 246 | #ifdef DebugOut 247 | Serial.print(" - Redundant"); 248 | #endif 249 | } 250 | } else { 251 | #ifdef DebugOut 252 | Serial.print(" - Wrong ID"); 253 | #endif 254 | } 255 | #ifdef DebugOut 256 | Serial.println(); 257 | #endif 258 | } 259 | 260 | 261 | /************************************************************************************************************************************************ 262 | * decode_Acurite_6045(dataBytes[]) : Routine to decode the message from the lightning sensor * 263 | * dataBytes{] is the array holding the received message. * 264 | * * 265 | * The lightning sensor transmits temperature and humidity every 24 seconds when there is no lightning activity. * 266 | * It transmits Lightning data, temperature, and humidity every 8 seconds when lightning is active. * 267 | ************************************************************************************************************************************************ 268 | */ 269 | 270 | void decode_Acurite_6045(byte dataBytes[]) 271 | { 272 | #ifdef DebugOut 273 | Serial.print(millis() - lightninglast); 274 | Serial.print("ms - "); 275 | Serial.print("Lightning - "); 276 | Serial.print(acurite_txr_getSensorId(dataBytes[0], dataBytes[1]), HEX); 277 | Serial.print("; T:"); 278 | Serial.print(acurite_6045_getTemp (dataBytes[4], dataBytes[5])); 279 | Serial.print("; H:"); 280 | Serial.print(acurite_getHumidity(dataBytes[3])); 281 | Serial.print(" %; L:"); 282 | if (((dataBytes[7] & 0x40) == 0x40)) 283 | { 284 | if ((dataBytes[7] & 0x20) == 0x20) 285 | { 286 | Serial.print("Interference"); 287 | } else { 288 | Serial.print("Dist:"); 289 | Serial.print(acurite_6045_strikeRange(dataBytes[7])); 290 | Serial.print(" Count:"); 291 | Serial.print(acurite_6045_strikeCnt(dataBytes[6])); 292 | Serial.print(";"); 293 | } 294 | } else 295 | { 296 | Serial.print("None; "); 297 | } 298 | if ((dataBytes[4] & 0x20) == 0x20 ) 299 | { 300 | Serial.print(" Battery Low;"); 301 | } 302 | Serial.println(); 303 | #endif 304 | lightninglast = millis(); 305 | } 306 | 307 | 308 | /************************************************************************************************************************************************ 309 | * displayBitTiming() : Routine to show the bit timings for a message * 310 | ************************************************************************************************************************************************ 311 | */ 312 | 313 | #ifdef DebugOut 314 | void displayBitTiming() // Print the bit stream for debugging. 315 | { 316 | unsigned int ringIndex; 317 | 318 | Serial.print("IchangeCount = "); 319 | Serial.println(IchangeCount); 320 | Serial.print("bytesReceived = "); 321 | Serial.println(bytesReceived); 322 | Serial.print("syncIndex = "); 323 | Serial.println(syncIndex); 324 | 325 | ringIndex = (syncIndex - (SYNCPULSEEDGES - 1)) % RING_BUFFER_SIZE; 326 | 327 | for ( int i = 0; i < (SYNCPULSECNT + (bytesReceived * 8)); i++ ) 328 | { 329 | int bit = convertTimingToBit( pulseDurations[ringIndex % RING_BUFFER_SIZE], 330 | pulseDurations[(ringIndex + 1) % RING_BUFFER_SIZE] ); 331 | Serial.print("bit "); 332 | Serial.print( i ); 333 | Serial.print(" = "); 334 | Serial.print(bit); 335 | Serial.print(" t1 = "); 336 | Serial.print(pulseDurations[ringIndex % RING_BUFFER_SIZE]); 337 | Serial.print(" t2 = "); 338 | Serial.println(pulseDurations[(ringIndex + 1) % RING_BUFFER_SIZE]); 339 | 340 | 341 | ringIndex += 2; 342 | } 343 | 344 | } 345 | #endif 346 | 347 | 348 | 349 | /************************************************************************************************************************************************ 350 | * UpdateDisplay() : Routine to update the OLED display * 351 | ************************************************************************************************************************************************ 352 | */ 353 | 354 | void UpdateDisplay() { 355 | 356 | if (millis() > (WUtime + 3000)) { // missed last WU update by at least 3 seconds 357 | display.setCursor(128-(2*6),0); 358 | display.print("**"); 359 | WUupdate = false; 360 | } else if (WUupdate) { // was last WU update good? 361 | display.setCursor(128-(2*6),0); 362 | display.print("OK"); 363 | } else if (WUresponse != "No send attempt yet") { // last WU update was bad, unless we haven't tried to send yet 364 | display.setCursor(128-(2*6),0); 365 | display.print("??"); 366 | } 367 | 368 | display.setCursor(6*8,12); 369 | display.print(Signal5N1,DEC); // signal quality 370 | display.print(" "); 371 | display.setCursor(6*14,12); 372 | if ((LastGood5N1 != 0) && ((millis()-LastGood5N1)<9999999)) { 373 | display.print((millis()-LastGood5N1)/1000,DEC); // time since last message in seconds 374 | display.print(" "); 375 | } else { 376 | display.print("9999999"); 377 | } 378 | 379 | display.setCursor(6*8,24); 380 | display.print(SignalTower,DEC); // signal quality 381 | display.print(" "); 382 | display.setCursor(6*14,24); 383 | if ((LastGoodTower != 0) && ((millis()-LastGoodTower)<9999999)) { 384 | display.print((millis()-LastGoodTower)/1000,DEC); // time since last message in seconds 385 | display.print(" "); 386 | } else { 387 | display.print("9999999"); 388 | } 389 | display.display(); 390 | displaylast = millis(); 391 | } 392 | 393 | 394 | -------------------------------------------------------------------------------- /WeatherServer/A_Functions.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************************************ 2 | ************************************************************************************************************************************************ 3 | ** Functions ** 4 | ************************************************************************************************************************************************ 5 | ************************************************************************************************************************************************ 6 | */ 7 | 8 | /************************************************************************************************************************************************ 9 | * isSync(idx) : Function to look for start of message signal * 10 | * idx is the index (pointer) to the last measured signal bit. Uses global array variable pulseDurations, and global variable SYNCPULSEEDGES * 11 | * Returns TRUE if start of message found, FALSE if not. * 12 | * * 13 | * Searches the pulseDurations array backwards looking for two pairs of long high long low pulses. (long is 600 msec +/- 125 msec * 14 | * ********************************************************************************************************************************************** 15 | */ 16 | 17 | bool isSync(unsigned int idx) 18 | { 19 | for ( int i = 0; i < SYNCPULSEEDGES; i += 2 ) // check if we've received 4 pulses of matching timing 20 | { 21 | unsigned long t1 = IpulseDurations[(idx + RING_BUFFER_SIZE - i) % RING_BUFFER_SIZE]; 22 | unsigned long t0 = IpulseDurations[(idx + RING_BUFFER_SIZE - i - 1) % RING_BUFFER_SIZE]; 23 | 24 | if ( t0 < (SYNC_HIGH - PULSE_RANGE) || t0 > (SYNC_HIGH + PULSE_RANGE) || // any of the preceeding 8 pulses are out of bounds, short or long, 25 | t1 < (SYNC_LOW - PULSE_RANGE) || t1 > (SYNC_LOW + PULSE_RANGE) ) // return false, no sync found 26 | { 27 | return false; 28 | } 29 | } 30 | return true; 31 | } 32 | 33 | 34 | /************************************************************************************************************************************************ 35 | * convertTimingToBit(t0,t1) : Function to identify pulse pair as 0, 1, or undefined * 36 | * t0 is the length of the first pulse, and t1 is the length of the second pulse, length is in milliseconds * 37 | * Returns 0 for short high followed by long low, 1 for long high followed by short low, -1 otherwise * 38 | * * 39 | * long is 400 msec +/- 125 msec, short is 220 msec +/- 125 msec * 40 | ************************************************************************************************************************************************ 41 | */ 42 | 43 | int convertTimingToBit(unsigned int t0, unsigned int t1) 44 | { 45 | if ( t0 > (BIT1_HIGH_MIN) && t0 < (BIT1_HIGH_MAX) && t1 > (BIT1_LOW_MIN) && t1 < (BIT1_LOW_MAX) ) 46 | { 47 | return 1; // "1" bit 48 | } 49 | else if ( t0 > (BIT0_HIGH_MIN) && t0 < (BIT0_HIGH_MAX) && t1 > (BIT0_LOW_MIN) && t1 < (BIT0_LOW_MAX) ) 50 | { 51 | return 0; // "0" bit 52 | } 53 | return -1; // undefined or sync 54 | } 55 | 56 | 57 | /************************************************************************************************************************************************ 58 | * acurite_crc(row[],bytes) : Function to verify the CRC of the message * 59 | * row[] is the array of bytes to check. bytes is the number of bytes in the message * 60 | * Returns TRUE if the message CRC is good, FALSE if it is not * 61 | * * 62 | * The checksum is a simple modulo-256 sum of all the bytes of the message except the last. The last is the checksum to match * 63 | * ********************************************************************************************************************************************** 64 | */ 65 | 66 | bool acurite_crc(volatile byte row[], int bytes) { // Validate the CRC value to validate the packet 67 | 68 | int sum = 0; // sum of first n-1 bytes modulo 256 should equal nth byte 69 | 70 | for (int i = 0; i < bytes - 1; i++) { 71 | sum += row[i]; 72 | } 73 | if ((sum != 0) && ((sum & 0xFF) == row[bytes - 1])) { 74 | return true; // validated 75 | } else { 76 | 77 | #ifdef DebugOut 78 | Serial.print("CRC ERROR: "); 79 | Serial.println(sum % 256, HEX); 80 | #endif 81 | 82 | return false; // failed 83 | } 84 | } 85 | 86 | 87 | /************************************************************************************************************************************************ 88 | * Parity(MessByte) : Function to check the parity of a byte * 89 | * MessByte is the 8 bit byte to check parity on. * 90 | * Returns TRUE if the byte has even parity, FALSE if it does not * 91 | * * 92 | * Note that the first two bytes and the last byte of each message do not have parity (true for 5-in-1, true for others? * 93 | * ********************************************************************************************************************************************** 94 | */ 95 | 96 | bool Parity(int MessByte) { 97 | 98 | int bitSum = 0; 99 | int b = 1; // start with bit 0, will end at bit 7 100 | 101 | for (int i = 0; i<8; i++) { // check all eight bits 102 | if (MessByte & (b << i)) { 103 | bitSum++; 104 | } 105 | } 106 | if ((bitSum % 2) == 0) { // is it even (no remainder when divided by two) 107 | return true; 108 | } else { 109 | return false; 110 | } 111 | } 112 | 113 | 114 | /************************************************************************************************************************************************ 115 | * acurite_parity(row[],bytes) : Function to verify the CRC of the message * 116 | * row[] is the array of bytes to check. bytes is the number of bytes in the message * 117 | * Returns TRUE if the parity of all bytes except the first two and the last one is good, FALSE if not * 118 | * ********************************************************************************************************************************************** 119 | */ 120 | 121 | bool acurite_parity(volatile byte row[], int bytes) { // Validate the parity of each byte in the packet 122 | 123 | for( int i = 2; i < bytes - 1; i++ ) { // Skip the first two and the last byte 124 | if (Parity(row[i]) == false){ 125 | #ifdef DebugOut 126 | Serial.print("Parity Error: "); 127 | Serial.print(row[i], HEX); 128 | Serial.println(); 129 | #endif 130 | return false; // exit loop if parity error found 131 | } 132 | } 133 | return true; 134 | } 135 | 136 | 137 | /************************************************************************************************************************************************ 138 | * acurite_6045_getTemp (highbyte,lowbyte) : Function to decode temperature reading from 6045 lightning sensor * 139 | * highbyte is byte 4 of the message, and lowbyte is byte 5 of the message. * 140 | * Returns the temperature in Fahrenhelt, -40.0 F to 158.0 F * 141 | * ********************************************************************************************************************************************** 142 | */ 143 | 144 | float acurite_6045_getTemp (uint8_t highbyte, uint8_t lowbyte) { 145 | int rawtemp = ((highbyte & 0x1F) << 7) | (lowbyte & 0x7F); 146 | float temp = (rawtemp - 1500) / 10.0; 147 | return temp; 148 | } 149 | 150 | 151 | /************************************************************************************************************************************************ 152 | * acurite_getTemp_6044M(highbyte,lowbyte) : Function to decode temperature reading from 6044 tower sensor * 153 | * hibyte is byte 4 of the message, and lobyte is byte 5 of the message. * 154 | * Returns the temperature in Fahrenhelt, -40.0 F to 158.0 F * 155 | * ********************************************************************************************************************************************** 156 | */ 157 | 158 | float acurite_getTemp_6044M(byte hibyte, byte lobyte) { 159 | 160 | int highbits = (hibyte & 0x0F) << 7; 161 | int lowbits = lobyte & 0x7F; 162 | int rawtemp = highbits | lowbits; 163 | float temp = (rawtemp / 10.0) - 100; 164 | return temp; 165 | } 166 | 167 | 168 | /************************************************************************************************************************************************ 169 | * acurite_getTemp_5n1(highbyte,lowbyte) : Function to decode temperature reading from 5-n-1 sensor * 170 | * hibyte is byte 4 of the message, and lobyte is byte 5 of the message. * 171 | * Returns the temperature in Fahrenhelt, -40.0 F to 158.0 F * 172 | * ********************************************************************************************************************************************** 173 | */ 174 | 175 | float acurite_getTemp_5n1(byte highbyte, byte lowbyte) { 176 | 177 | int highbits = (highbyte & 0x0F) << 7 ; 178 | int lowbits = lowbyte & 0x7F; 179 | int rawtemp = highbits | lowbits; 180 | float temp_F = (rawtemp - 400) / 10.0; 181 | return temp_F; 182 | } 183 | 184 | 185 | /************************************************************************************************************************************************ 186 | * convCF(c) : Function to convert a temperature reading in Celsius to Fahrenheit * 187 | * c is the temperature reading in Celsius * 188 | * Returns the temperature reading in Fahrenheit * 189 | ************************************************************************************************************************************************ 190 | */ 191 | 192 | float convCF(float c) { 193 | return ((c * 1.8) + 32); 194 | } 195 | 196 | 197 | /************************************************************************************************************************************************ 198 | * acurite_6045_strikeCnt(strikeByte) : Function to get the number of lightning strikes detected today by the 6045 lightning sensor * 199 | * strikeByte is the 6th byte from the message. Uses several global variables * 200 | * Returns the number of strikes (today) * 201 | * * 202 | * Strike count is reset at midnight (elsewhere) * 203 | ************************************************************************************************************************************************ 204 | */ 205 | 206 | int acurite_6045_strikeCnt(byte strikeByte){ 207 | 208 | int strikeCnt = strikeByte & 0x7f; 209 | if (strikeTot == 0) 210 | { 211 | strikeTot = strikeCnt; //Initialize Strike Counter 212 | strikeWrapOffset = 0; 213 | return 0; 214 | } else if (strikeCnt < strikeTot && strikeCnt > 0) { // Did strike counter on sensor wrap? 215 | strikeWrapOffset = (127 - strikeTot) + strikeWrapOffset; 216 | strikeTot = 1; //*Strikes wrapped around, must set to 1 instead of 0 because of this 217 | } 218 | return (strikeCnt - strikeTot) + strikeWrapOffset; 219 | 220 | } 221 | 222 | 223 | /************************************************************************************************************************************************ 224 | * acurite_6045_strikeRange(strikeRange) : Function to decode the distance of the last lightning strike detected by the 6045 * 225 | * strikeRange is byte 7 of the message * 226 | * Returns the estimated distance of the last lightning strike in miles * 227 | ************************************************************************************************************************************************ 228 | */ 229 | 230 | uint8_t acurite_6045_strikeRange(uint8_t strikeRange) 231 | { 232 | return strikeRange & 0x1f; 233 | } 234 | 235 | 236 | /************************************************************************************************************************************************ 237 | * acurite_txr_getSensorID(hibyte,lobyte) : Function to get the ID of the wireless sensor that transmitted the message * 238 | * hibyte is byte 0 of the message and lobyte is byte 1 of the message * 239 | * Returns the sensor ID as 16 bits (only 14 bits used) * 240 | ************************************************************************************************************************************************ 241 | */ 242 | 243 | uint16_t acurite_txr_getSensorId(uint8_t hibyte, uint8_t lobyte) { 244 | return ((hibyte & 0x0f) << 8) | lobyte; 245 | } 246 | 247 | 248 | 249 | /************************************************************************************************************************************************ 250 | * acurite_getHumidity(byte) : Function to decode the humidity reading from the sensor * 251 | * byte is byte 6 of the 5-n-1 message, byte 3 of the tower and lightning detector messages * 252 | * Returns the relative humidity percentage as an integer ranging from 1 to 99 * 253 | ************************************************************************************************************************************************ 254 | */ 255 | 256 | int acurite_getHumidity (uint8_t byte) { 257 | int humidity = byte & 0x7F; 258 | return humidity; 259 | } 260 | 261 | 262 | /************************************************************************************************************************************************ 263 | * acurite_getWindSpeed_kph(highbyte, lowbyte) : Function to decode the wind speed from the sensor * 264 | * highbyte is byte 3 and lowbyte is byte 4 of the message from the 5-n-1. Byte ? and byte ?+1 of the 3-n-1? * 265 | * Returns the current (peak of last 18 seconds?) wind speed in km/hour * 266 | ************************************************************************************************************************************************ 267 | */ 268 | 269 | float acurite_getWindSpeed_kph (uint8_t highbyte, uint8_t lowbyte) { // range: 0 to 159 kph 270 | 271 | int highbits = ( highbyte & 0x1F) << 3; // raw number is cup rotations per 4 seconds 272 | int lowbits = ( lowbyte & 0x70 ) >> 4; // http://www.wxforum.net/index.php?topic=27244.0 (found from weewx driver) 273 | int rawspeed = highbits | lowbits; 274 | float speed_kph = 0; 275 | if (rawspeed > 0) { 276 | speed_kph = rawspeed * 0.8278 + 1.0; 277 | } 278 | return speed_kph; 279 | } 280 | 281 | 282 | /************************************************************************************************************************************************ 283 | * UpdateRainCounter() : Function to update the rain counter * 284 | ************************************************************************************************************************************************ 285 | */ 286 | void UpdateRainCounter() { 287 | 288 | if (RainCounter5N1 != CurrentRainCounter5N1) { 289 | RainCounter5N1 = CurrentRainCounter5N1; // reset rain counter to current value 290 | EEPROM.write(0,highByte(RainCounter5N1)); 291 | EEPROM.write(1,lowByte(RainCounter5N1)); 292 | EEPROM.commit(); 293 | 294 | RainYesterday = CurrentRain; 295 | CurrentRain = 0; // reset daily rain (would reset on next rain message received anyway 296 | 297 | #ifdef DebugOut 298 | Serial.print(" *Rain Counter set to "); 299 | Serial.print(RainCounter5N1,HEX); 300 | Serial.print("* "); 301 | #endif 302 | } 303 | } 304 | 305 | 306 | /************************************************************************************************************************************************ 307 | * convKphMph(kph) : Function to convert a wind speed in km/hour to miles/hour * 308 | * kph is the windspeed in km per hour * 309 | * Returns the wind speed in miles per hour * 310 | ************************************************************************************************************************************************ 311 | */ 312 | 313 | float convKphMph(float kph) { 314 | return kph * 0.62137; 315 | } 316 | 317 | 318 | /************************************************************************************************************************************************ 319 | * getWindDirection_Descr(byte) : Function to decode the wind direction from the 5-n-1 in standard (terse) direction terms * 320 | * byte = byte 4 of 5-n-1 message, or byte ? of 3-n-1 message * 321 | * Returns the direction as a string such as N, W, E, S, NW, SSE, ... * 322 | ************************************************************************************************************************************************ 323 | 324 | 325 | //char * getWindDirection_Descr(byte b) { 326 | // int direction = b & 0x0F; 327 | // return acurite_5n1_winddirection_str[direction]; 328 | //} 329 | */ 330 | 331 | /************************************************************************************************************************************************ 332 | * acurite_getRainfall(hibyte,lobyte) : Funtion to decode the current rainfall amount from the 5-n-1 message * 333 | * hibyte is byte 5 and lobyte is byte 6 of the message. Uses several global variables * 334 | * Returns today's rainfall amount in inches. Is precise to 1/100th of an inch * 335 | * * 336 | * Need to reset to zero at midnight (elsewhere) * 337 | ************************************************************************************************************************************************ 338 | */ 339 | float acurite_getRainfall(uint8_t hibyte, uint8_t lobyte) { // range: 0 to 99.99 in, 0.01 in incr., rolling counter? 340 | 341 | CurrentRainCounter5N1 = ((hibyte & 0x3f) << 7) | (lobyte & 0x7F); // 5N1's just received rain counter value, 0 to 0x1FFF 342 | 343 | // Was the EEPROM never written? Default is 0xFFFF 344 | // Over 0x1FFF is not possible 345 | // Did 5N1 flag battery change (so counter changed) 346 | if (RainCounter5N1 > 0x1FFF) { // Should we reset the raincounter ? 347 | UpdateRainCounter(); 348 | return (0); 349 | } 350 | 351 | if ((RainRate == 0) && (RainCounter5N1 < 0x1FD0) && (CurrentRainCounter5N1 == 0)) { // rain counter resets to zero when 352 | // batteries are changed in the 5N1 353 | UpdateRainCounter(); 354 | return (0); 355 | } 356 | 357 | if (CurrentRainCounter5N1 >= RainCounter5N1) { // did the counter wrap around? 358 | return ((CurrentRainCounter5N1 - RainCounter5N1) * 0.01); // No, it didn't 359 | } else { 360 | return ((CurrentRainCounter5N1 - (RainCounter5N1 - 0x2000)) * 0.01); // Yes, it did 361 | } 362 | } 363 | 364 | 365 | /************************************************************************************************************************************************ 366 | * DewPointF(Temperature,Humidity) : Function to calculate appoximate dew point in Fahrenheit * 367 | * Temperature is in Fahrenheit, Humidity is 0 to 100 relative humidity * 368 | * Returns the dew point temperature in Fahrenheit * 369 | ************************************************************************************************************************************************ 370 | */ 371 | 372 | float DewPointF(float tempf, int humidity) { 373 | 374 | float A0= 373.15/(273.15 + tempf); 375 | float SUM = -7.90298 * (A0-1); 376 | SUM += 5.02808 * log10(A0); 377 | SUM += -1.3816e-7 * (pow(10, (11.344*(1-1/A0)))-1) ; 378 | SUM += 8.1328e-3 * (pow(10,(-3.49149*(A0-1)))-1) ; 379 | SUM += log10(1013.246); 380 | float VP = pow(10, SUM-3) * humidity; 381 | float T = log(VP/0.61078); 382 | return (241.88 * T) / (17.558-T); 383 | 384 | // return ( tempf - ( 9 * (100 - humidity) / 25)); // the simple way 385 | } 386 | 387 | 388 | /************************************************************************************************************************************************ 389 | * GetLetter(byte0) : Function to return the letter value of the 3 position switch on the sensor * 390 | * byte0 is the first byte of the message * 391 | * Returns A, B, or C for the switch position. Returns E if there is an error * 392 | ************************************************************************************************************************************************ 393 | */ 394 | char GetLetter(int byte0) { 395 | 396 | static char chLetter[4] = {'C','E','B','A'}; 397 | return chLetter[(byte0 & 0xC0) >> 6]; 398 | } 399 | 400 | 401 | /************************************************************************************************************************************************ 402 | * GetMessageNo(byte0) : Function to return the message number of the 5N1 message * 403 | * byte0 is the first byte of the message * 404 | * Returns 1, 2, or 3, is it the 1st, 2nd, or 3rd of each repeated message? * 405 | ************************************************************************************************************************************************ 406 | */ 407 | int GetMessageNo(int byte0) { 408 | return ((byte0 & 0x30) >> 4); 409 | } 410 | 411 | --------------------------------------------------------------------------------