@@ 0,0 1,1085 @@
+#include "config.h"
+#include <Arduino.h>
+#include <Preferences.h>
+#include <WiFi.h>
+
+#include <TimerInterrupt_Generic.h>
+
+#ifdef WANT_OTA
+#include <ArduinoOTA.h>
+#endif /* WANT_OTA */
+
+#ifdef WANT_NTP
+#include <NTPClient.h>
+#endif /* WANT_NTP */
+
+#ifdef WANT_MDNS
+#include <ESPmDNS.h>
+#endif /* WANT_MDNS */
+
+#include <StreamUtils.h>
+#include <ArduinoJson.h>
+
+#include <HTU2xD_SHT2x_Si70xx.h>
+#include <BME280I2C.h>
+#include <Adafruit_PM25AQI.h>
+
+#include <esp_task_wdt.h>
+//5 seconds WDT
+#define WDT_TIMEOUT 5
+long watchdog_last_patted = 0;
+
+bool got_address = false;
+
+void watchdog_update() {
+ if (millis() - watchdog_last_patted >= 1000) {
+ // Serial.println("Resetting WDT...");
+ // esp_task_wdt_reset();
+ watchdog_last_patted = millis();
+ }
+}
+
+#define LOGERROR(X...) Serial.printf(X);
+
+String ssid = PROJECTNAME "-";
+
+String airsn = "ST-51516";
+String hubsn = "HB-51516";
+String aqsn = "AQ-61521";
+
+int report_frequency = 60;
+String hostname;
+
+int failedSends = 0;
+
+bool statusNotDisplayed = true;
+
+HTU2xD_SHT2x_SI70xx ht2x(HTU2xD_SENSOR, HUMD_08BIT_TEMP_12BIT);
+
+BME280I2C::Settings settings(
+ BME280::OSR_X1,
+ BME280::OSR_X1,
+ BME280::OSR_X1,
+ BME280::Mode_Forced,
+ BME280::StandbyTime_1000ms,
+ BME280::Filter_Off,
+ BME280::SpiEnable_False,
+ BME280I2C::I2CAddr_0x76 // I2C address. I2C specific.
+);
+
+BME280I2C bme280(settings);
+Adafruit_PM25AQI aqi = Adafruit_PM25AQI();
+
+ESP32Timer ITimer0(1);
+bool wantRapidWind = false;
+
+#define DEBUG(X...) do{if(Serial)Serial.println(X);}while(0)
+#define DEBUGCHAR(X...) do{if(Serial)Serial.print(X);}while(0)
+
+double windValues[60];
+int currentWindValue = 0;
+
+/* when storing data in the pseudo-eeprom of the ESP, you must specify how much space
+ * will be needed for the data stored. changing this value will likely cause existing
+ * data to be lost, so best over-estimate before going live.
+ */
+#define EEPROM_BYTES_NEEDED 50
+
+boolean sensorPresent;
+boolean bmeSensorPresent;
+
+#define SLOW 250
+#define MEDIUM 100
+#define FAST 50
+
+#define sleep(X) delay(X)
+#define ON 1
+#define OFF 0
+//
+//WiFiClient wifiClient;
+void weather_report();
+
+
+
+/*
+ * GPIO4 -> GP2: PULSE (WH)
+ * GPIO15 -> GP3: RAIN (BK)
+ * SDA -> GP6: SDA (BL)
+ * SCK -> GP7: SCL (GY)
+ * 3V3 -> 3v3 (RD)
+ * GND -> GND (GR)
+ * GPIO14 -> GP26 WIND (OR)
+ */
+
+#define SDA_PIN SDA
+#define SCL_PIN SCL
+
+#define RAIN_PIN 15
+//#define SPEED_PIN 2
+#define SPEED_PIN 4
+//#define DIR_PIN 26
+//#define EXCITER_PIN 4
+#define DIR_PIN 14
+#define EXCITER_PIN 14
+
+#define RAIN_DEBOUNCE_MS 350
+#define WIND_DEBOUNCE_MS 15
+
+#define SMARTWEATHER_PORT 50222
+
+volatile unsigned long w_last_interrupt_time = 0;
+
+volatile unsigned long wind_time = 0;
+volatile unsigned long wind_interval = 0;
+volatile unsigned long max_wind_interval = 0;
+volatile int last_awoke = 0;
+
+volatile int did_wdt_check = 0;
+
+double calcMaxWindspeed();
+double calcWindSpeed();
+
+uint16_t calcWindDir();
+
+#define TIMER0_INTERVAL_MS 1000000
+
+bool updateWindValues(void * y);
+
+IPAddress br_adr;
+String macAddress;
+
+WiFiUDP Udp;
+
+#ifdef WANT_NTP
+WiFiUDP ntpUdp;
+
+// for some reason NTPClient gets into a DNS query loop on update(),
+// causing the call to hang for 5-20 seconds.
+IPAddress ntpserver(192, 168, 1, 2);
+NTPClient timeClient(ntpUdp, ntpserver);
+#endif /* WANT_NTP */
+
+void rainInterrupt();
+void windInterrupt();
+void reportRain(int increments);
+void rapidWind();
+
+void hubStatus(long tsm);
+void deviceStatus(long tsm);
+void weatherObservation(long tsm);
+
+long ts;
+int reportcnt = 0;
+int lastobservation = 0;
+int lastreport = 0;
+
+#define LED_ON HIGH
+#define LED_OFF LOW
+
+#define MIN_AP_PASSWORD_SIZE 8
+
+#define SSID_MAX_LEN 32
+//From v1.0.10, WPA2 passwords can be up to 63 characters long.
+#define PASS_MAX_LEN 64
+
+typedef struct
+{
+ char wifi_ssid[SSID_MAX_LEN];
+ char wifi_pw [PASS_MAX_LEN];
+} WiFi_Credentials;
+
+typedef struct
+{
+ char hub_sn[10];
+ char air_sn[10];
+ int report_frequency;
+ char hostname[32];
+} App_Parameters;
+
+#define NUM_WIFI_CREDENTIALS 1
+
+typedef struct
+{
+ uint8_t cookie[2];
+ App_Parameters Params;
+} WM_Config;
+
+WM_Config WM_config;
+
+//////
+
+#include <ETH.h>
+
+static bool eth_connected = false;
+void WiFiEvent(WiFiEvent_t event)
+{
+ switch (event) {
+ case ARDUINO_EVENT_ETH_START:
+ Serial.println("ETH Started");
+ //set eth hostname here
+ ETH.setHostname(hostname.c_str());
+ break;
+ case ARDUINO_EVENT_ETH_CONNECTED:
+ Serial.println("ETH Connected");
+ ETH.config(IPAddress(0,0,0,0),
+ IPAddress(0,0,0,0),
+ IPAddress(0,0,0,0),
+ IPAddress(0,0,0,0));
+ if(got_address) eth_connected = true;
+ break;
+ case ARDUINO_EVENT_ETH_GOT_IP:
+ Serial.print("ETH MAC: ");
+ Serial.print(ETH.macAddress());
+ Serial.print(", IPv4: ");
+ Serial.print(ETH.localIP());
+ if (ETH.fullDuplex()) {
+ Serial.print(", FULL_DUPLEX");
+ }
+ Serial.print(", ");
+ Serial.print(ETH.linkSpeed());
+ Serial.println("Mbps");
+ macAddress = ETH.macAddress();
+
+ Serial.print("MAC ADDRESS ");
+ Serial.println(macAddress);
+ ssid += macAddress.substring(9, 11);
+ ssid += macAddress.substring(12, 14);
+ ssid += macAddress.substring(15, 17);
+ Serial.println(ssid);
+
+ if(hostname == NULL) {
+ hostname = ssid;
+ }
+
+ got_address = true;
+ eth_connected = true;
+ break;
+ case ARDUINO_EVENT_ETH_DISCONNECTED:
+ Serial.println("ETH Disconnected");
+ eth_connected = false;
+ break;
+ case ARDUINO_EVENT_ETH_STOP:
+ Serial.println("ETH Stopped");
+ eth_connected = false;
+ break;
+ default:
+ Serial.println("ETH Unknown event");
+ break;
+ }
+}
+
+void blink(int speed, int count) {
+ pinMode(16, OUTPUT);
+ for(; count > 0; count--) {
+ digitalWrite(16, ON);
+ sleep(speed);
+ digitalWrite(16, OFF);
+ sleep(speed);
+ }
+ digitalWrite(16, ON);
+}
+
+void resetFunc() {
+ ESP.restart();
+}
+
+//bool loadConfigData()
+//{
+// memset((void*) &WM_config, 0, sizeof(WM_config));
+//
+// // New in v1.4.0
+// //memset((void*) &WM_STA_IPconfig, 0, sizeof(WM_STA_IPconfig));
+// //////
+//
+// {
+// EEPROM.get(0, WM_config);
+// Serial.println(F("OK"));
+//
+// if(WM_config.cookie[0] != 'W' || WM_config.cookie[1] != 'x') {
+// Serial.println(F("Invalid config in EEPROM"));
+// return false;
+// }
+//
+//
+// airsn = String(WM_config.Params.air_sn);
+// hubsn = String(WM_config.Params.hub_sn);
+// report_frequency = (WM_config.Params.report_frequency);
+// hostname = String(WM_config.Params.hostname);;
+//
+// Serial.println(String(F("Air Serial = ")) + airsn);
+// Serial.println(String(F("Hub Serial = ")) + hubsn);
+// Serial.println(String(F("Report Frequency = ")) + String(report_frequency));
+// Serial.println(String(F("Hostname = ")) + hostname);
+//
+// return true;
+// }
+//}
+
+void setup()
+{
+ Serial.begin(115200);
+ while (!Serial && millis() < 5000);
+
+ while (!Serial && millis() < 5000);
+
+ delay(200);
+
+// Wire1.setSDA(6);
+// Wire1.setSCL(7);
+ Wire.begin();
+ // RP2040 does not have an internal reference, A_REF should be wired
+ // to the approproate refrence, on pico boards, this is set to the 3.3v regulated
+ // line, which is not super accurate.
+ // analogReference(AR_EXTERNAL);
+
+ pinMode(RAIN_PIN, INPUT_PULLUP);
+ pinMode(SPEED_PIN, INPUT_PULLUP);
+ pinMode(DIR_PIN, INPUT);
+
+ Serial.print("Firmware revision ");
+ Serial.println(FIRMWARE_REVISION);
+
+ //Preferences.begin(PROJECT_NAME);
+
+ WiFi.onEvent(WiFiEvent);
+ ETH.begin();
+
+#if WANT_OTA
+
+ArduinoOTA.onStart([]() {
+ String type;
+ if (ArduinoOTA.getCommand() == U_FLASH) {
+ type = "sketch";
+ } else { // U_FS
+ type = "filesystem";
+ }
+
+ // NOTE: if updating FS this would be the place to unmount FS using FS.end()
+ Serial.println("Start updating " + type);
+ });
+
+ ArduinoOTA.onEnd([]() {
+ Serial.println("\nEnd");
+ });
+ ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
+ Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
+ watchdog_update();
+ });
+ ArduinoOTA.onError([](ota_error_t error) {
+ Serial.printf("Error[%u]: ", error);
+ if (error == OTA_AUTH_ERROR) {
+ Serial.println("Auth Failed");
+ } else if (error == OTA_BEGIN_ERROR) {
+ Serial.println("Begin Failed");
+ } else if (error == OTA_CONNECT_ERROR) {
+ Serial.println("Connect Failed");
+ } else if (error == OTA_RECEIVE_ERROR) {
+ Serial.println("Receive Failed");
+ } else if (error == OTA_END_ERROR) {
+ Serial.println("End Failed");
+ }
+ });
+
+#endif /* WANT_OTA */
+
+
+ int attempts = 0;
+
+ while ((sensorPresent = ht2x.begin()) != true && attempts < 10) //reset sensor, set heater off, set resolution, check power (sensor doesn't operate correctly if VDD < +2.25v)
+ {
+ Serial.println(F("HTU2xD/SHT2x not connected, fail or VDD < +2.25v")); //(F()) save string to flash & keeps dynamic memory free
+ delay(10);
+ attempts++;
+ }
+
+ if(sensorPresent)
+ Serial.println(F("HTU2xD/SHT2x OK"));
+
+ attempts = 0;
+ while ((bmeSensorPresent = bme280.begin()) != true && attempts < 10) //reset sensor, set heater off, set resolution, check power (sensor doesn't operate correctly if VDD < +2.25v)
+ {
+ Serial.println(F("BME280 not connected or failed")); //(F()) save string to flash & keeps dynamic memory free
+ delay(10);
+ attempts++;
+ }
+ if(bmeSensorPresent) {
+ Serial.println(F("BME280 OK"));
+ }
+
+ while ((sensorPresent = aqi.begin_I2C()) != true && attempts < 10)
+ {
+ Serial.println(F("PM25AQI not connected, fail or VDD < +2.25v")); //(F()) save string to flash & keeps dynamic memory free
+ delay(10);
+ attempts++;
+ }
+
+ if(sensorPresent)
+ Serial.println(F("PM25AQI Sensor OK"));
+
+
+ // clear the wind values array.
+ for(int i = 0; i < 60; i++)
+ windValues[i] = 0.0;
+ currentWindValue = 0;
+
+ ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS, updateWindValues);
+ attachInterrupt(digitalPinToInterrupt(RAIN_PIN), rainInterrupt, FALLING);
+ attachInterrupt(digitalPinToInterrupt(SPEED_PIN), windInterrupt, FALLING);
+
+// esp_task_wdt_init(WDT_TIMEOUT, true); //enable panic so ESP32 restarts
+// esp_task_wdt_add(NULL); //add current thread to WDT watch
+
+}
+
+boolean haveStarted = false;
+
+bool updateWindValues(void * y) {
+ if(currentWindValue==60) currentWindValue = 0;
+// Serial.print("updateWindValues() ");
+// Serial.println(millis());
+ if(currentWindValue % 3 == 0) {
+ // Serial.println("wantRapidWind");
+ wantRapidWind = true;
+ }
+ //Serial.println(calcWindSpeed());
+ windValues[currentWindValue++] = calcWindSpeed();
+ return true;
+}
+
+double calcMaxWindspeed()
+{
+ double q = windValues[0];
+
+ for(int i = 0; i < 60; i++)
+ if(windValues[i] > q)
+ q = windValues[i];
+
+ return q;
+}
+
+double calcMinWindspeed()
+{
+ double q = windValues[0];
+
+ for(int i = 0; i < 60; i++)
+ if(windValues[i] < q)
+ q = windValues[i];
+
+ return q;
+}
+
+double calcAvgWindspeed()
+{
+ double q = 0.0;
+
+ for(int i = 0; i < 60; i++)
+ q += windValues[i];
+
+ return q/60;
+}
+
+
+// return m/s
+double calcWindSpeed()
+{
+ double q;
+//return wind_interval;
+ if(wind_interval < 5) return 0.0;
+ q = 1000.0/wind_interval;
+
+ return q;
+}
+
+uint16_t getWindDirReading()
+{
+ delay(5);
+
+ // RP2040 does not have an internal reference, A_REF should be wired
+ // to the approproate refrence, on pico boards, this is set to the 3.3v regulated
+ // line, which is not super accurate.
+ //analogReference(AR_EXTERNAL);
+// digitalWrite(EXCITER_PIN, HIGH);
+// delay(5);
+ uint16_t windDir = analogRead(DIR_PIN);
+// digitalWrite(EXCITER_PIN, LOW);
+
+ return windDir;
+}
+
+uint16_t calcWindDirDegrees()
+{
+ uint16_t windDirX;
+
+ windDirX = getWindDirReading();
+
+ windDirX = (uint16_t)(windDirX/11.37);
+
+ return windDirX;
+
+}
+uint16_t calcWindDir()
+{
+ uint16_t windDirX;
+
+ windDirX = getWindDirReading();
+ if(windDirX >= 959)
+ windDirX = 0;
+ else
+ windDirX = (windDirX+(64))/(128);
+
+ return windDirX;
+}
+
+int rainIncrements = 0;
+
+static unsigned long last_rain_interrupt_time = 0;
+
+void windInterrupt()
+{
+ last_awoke = 0;
+
+ unsigned long w_interrupt_time = millis();
+ int x;
+
+ // If interrupts come faster than 10ms, assume it's a bounce and ignore
+ if ((w_interrupt_time - w_last_interrupt_time) > WIND_DEBOUNCE_MS)
+ {
+ if(wind_time)
+ wind_interval = (w_interrupt_time - wind_time);
+ if(wind_interval && (!max_wind_interval || (max_wind_interval > wind_interval)))
+ max_wind_interval = wind_interval;
+ wind_time = w_interrupt_time;
+ w_last_interrupt_time = w_interrupt_time;
+ }
+}
+
+void rainInterrupt() {
+ unsigned long interrupt_time = millis();
+
+ if ((interrupt_time - last_rain_interrupt_time) > RAIN_DEBOUNCE_MS)
+ {
+ rainIncrements++;
+ last_rain_interrupt_time = interrupt_time;
+ }
+}
+
+void loop() {
+ watchdog_update();
+
+ if (haveStarted && eth_connected) {
+#ifdef WANT_OTA
+ ArduinoOTA.handle();
+#else
+#ifdef WANT_MDNS
+ // ArduinoOTA.handle() will call mdns.update(), so only need to do this if
+ // we aren't doing OTA.
+ //
+ // This actually runs the mDNS module. YOU HAVE TO CALL THIS PERIODICALLY,
+ // OR NOTHING WILL WORK! Preferably, call it once per loop().
+ MDNS.update();
+#endif
+#endif /* WANT_OTA */
+
+// MDNS.update();
+#ifdef WANT_NTP
+ timeClient.update();
+ ts = timeClient.getEpochTime();
+#else
+ ts = millis();
+#endif /* WANT_NTP */
+ if ((millis() - w_last_interrupt_time) > 2000 && wind_interval != 0) wind_interval = 0;
+
+// Serial.println(calcWindDirDegrees());
+/*
+ if(((millis() / 1000) % 3) == 0) {
+ Serial.println("Wind Speed");
+ Serial.println(calcWindSpeed());
+ }
+*/
+ if (wantRapidWind) {
+ wantRapidWind = false;
+ rapidWind();
+ }
+ weather_report();
+ }
+
+ if (millis() > 5000 && !haveStarted) {
+ if (eth_connected) {
+ haveStarted = true;
+ statusNotDisplayed = false;
+
+ hostname.toLowerCase();
+ hostname.replace(" ", "-");
+ hostname.replace("_", "-");
+
+ br_adr = ETH.localIP();
+ br_adr[3] = 255;
+
+ Serial.println(F("Local Address"));
+ Serial.println(ETH.localIP());
+ Serial.println(F("Smartweather destination"));
+ Serial.println(br_adr);
+ Serial.println(SMARTWEATHER_PORT);
+
+ Serial.println(F("Setting up UDP communications"));
+ Serial.println(MEMP_NUM_NETCONN);
+ Serial.println(MEMP_NUM_UDP_PCB);
+ Serial.println(MEMP_NUM_PBUF);
+ Serial.println(MEMP_NUM_NETBUF);
+#ifdef WANT_MDNS
+ MDNS.begin(hostname.c_str());
+#endif /* WANT_MDNS */
+#ifdef WANT_NTP
+ Serial.println(F("Registering NTP client"));
+ timeClient.begin();
+#endif /* WANT_NTP */
+
+ Udp.begin(SMARTWEATHER_PORT);
+
+#ifdef WANT_OTA
+ ArduinoOTA.setPassword(OTA_PASSWORD);
+ ArduinoOTA.setHostname(hostname.c_str());
+ ArduinoOTA.begin();
+#else
+#ifdef WANT_MDNS
+ // ArduinoOTA.handle() will call mdns.run(), so only need to do this if
+ // we aren't doing OTA.
+ //
+ // This actually runs the mDNS module. YOU HAVE TO CALL THIS PERIODICALLY,
+ // OR NOTHING WILL WORK! Preferably, call it once per loop().
+ MDNS.begin(hostname.c_str(), WiFi.localIP());
+#endif /* WANT_MDNS */
+#endif /* WANT_OTA */
+
+#ifdef WANT_MDNS
+ // Initialize the mDNS library. You can now reach or ping this
+ // Arduino via the host name "hostname.local", provided that your operating
+ // system is mDNS/Bonjour-enabled (such as MacOS X).
+ // Always call this before any other method!
+
+ Serial.print(F("Registering mDNS hostname: ")); Serial.println(hostname);
+ Serial.print(F("To access, using "));
+ Serial.print(hostname); Serial.println(F(".local"));
+ Serial.print(F("Local address: "));
+ Serial.println(ETH.localIP());
+ // mdns.begin(WiFi.localIP(), hostname.c_str());
+
+ Serial.println(F("Registering MDNS"));
+ // String svc = hostname + "._http";
+ MDNS.addService("http", "tcp", 80);
+
+#ifdef WANT_OTA
+ // MDNS.enableArduino(OTA_PORT, true);
+#endif /* WANT_OTA */
+#endif /* WANT_MDNS */
+
+ }
+
+ }
+}
+void sendFailed()
+{
+ failedSends++;
+ if(failedSends > FAILED_SEND_LIMIT)
+ resetFunc();
+}
+
+void rapidWind() {
+ StaticJsonDocument<386> doc;
+ blink(FAST, 2);
+
+ if (haveStarted) {
+#ifdef WANT_NTP
+ timeClient.update();
+ ts = timeClient.getEpochTime();
+#endif /* WANT_NTP */
+ Serial.println(F("Sending observation"));
+
+ doc["serial_number"] = airsn;
+ doc["hub_sn"] = hubsn;
+ doc["type"] = "rapid_wind";
+ JsonArray ob = doc.createNestedArray("ob");
+ ob.add(ts);
+
+ // speed
+ // direction
+ double_t windSpeed = calcWindSpeed();
+ if (windSpeed < 0.01) ob.add(0.0);
+ else ob.add(windSpeed);
+ ob.add(calcWindDirDegrees());
+
+ int written;
+
+ if (Udp.beginPacket(br_adr, SMARTWEATHER_PORT))
+ Serial.println("Packet begun 1.");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+ if (Udp.endPacket())
+ Serial.println("Sent.");
+ else
+ {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+
+ } else {
+ Serial.println(F("LAN status indicates not connected, not sending rapid wind report."));
+ }
+}
+
+void hubStatus(long tsm)
+{
+ int written = 0;
+
+ Serial.println(F("Last Wind Interrupt"));
+ Serial.println(w_last_interrupt_time);
+ Serial.println(F("Sending hub status"));
+
+#ifdef WANT_NTP
+ timeClient.update();
+ ts = timeClient.getEpochTime();
+#else
+ ts = millis();
+#endif /* WANT_NTP */
+ Serial.println(ts);
+
+ StaticJsonDocument<386> doc;
+
+ doc[F("serial_number")] = hubsn;
+ doc[F("type")] = "hub_status";
+ doc[F("firmware_revision")] = FIRMWARE_REVISION;
+ doc[F("uptime")] = tsm/1000; // millis() / 1000;
+ doc[F("rssi")] = 0;
+ doc[F("timestamp")] = ts;
+
+
+ if (Udp.beginPacket(br_adr, SMARTWEATHER_PORT)) {
+ Serial.println("Packet begun. 2");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+ if (Udp.endPacket())
+ Serial.println("Sent.");
+ reportcnt++;
+ } else {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+}
+
+void deviceStatus(long tsm) {
+ int written = 0;
+
+ Serial.println("Sending device status");
+
+ StaticJsonDocument<436> doc;
+
+ doc["serial_number"] = airsn;
+ doc["hub_sn"] = hubsn;
+ doc["type"] = "device_status";
+ doc["firmware_revision"] = FIRMWARE_REVISION;
+ doc["uptime"] = tsm / 1000;
+ doc["rssi"] = 0;
+ doc["hub_rssi"] = 0;
+ doc["voltage"] = 0.0;
+ doc["timestamp"] = ts;
+
+// if(!did_wdt_check) {
+//// did_wdt_check = 1;
+// if(rtc_get_reset_reason()) {
+// doc["watchdog_reset"] = 1;
+// }
+// }
+
+ if (Udp.beginPacket(br_adr, SMARTWEATHER_PORT))
+ Serial.println("Packet begun. 3");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+ if (Udp.endPacket())
+ Serial.println("Sent.");
+ else {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+}
+
+void aqDeviceStatus(long tsm) {
+ int written = 0;
+
+ Serial.println("Sending AQ device status");
+
+ StaticJsonDocument<436> doc;
+
+ doc["serial_number"] = aqsn;
+ doc["hub_sn"] = hubsn;
+ doc["type"] = "device_status";
+ doc["firmware_revision"] = FIRMWARE_REVISION;
+ doc["uptime"] = tsm / 1000;
+ doc["rssi"] = 0;
+ doc["hub_rssi"] = 0;
+ doc["voltage"] = 0.0;
+ doc["timestamp"] = ts;
+
+// if(!did_wdt_check) {
+//// did_wdt_check = 1;
+// if(rtc_get_reset_reason()) {
+// doc["watchdog_reset"] = 1;
+// }
+// }
+
+ if (Udp.beginPacket(br_adr, SMARTWEATHER_PORT))
+ Serial.println("Packet begun. 3");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+ if (Udp.endPacket())
+ Serial.println("Sent.");
+ else {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+}
+
+void airQualityObservation(long tsm) {
+ StaticJsonDocument<768> doc;
+ int written = 0;
+
+ PM25_AQI_Data data;
+ int attempts = 0;
+ while (attempts < 10 && !aqi.read(&data)) {
+ Serial.println("Could not read from AQI");
+ delay(500); // try again in a bit!
+ attempts++;
+ }
+ Serial.println("AQI reading success");
+
+ Serial.println();
+ Serial.println(F("---------------------------------------"));
+ Serial.println(F("Concentration Units (standard)"));
+ Serial.println(F("---------------------------------------"));
+ Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_standard);
+ Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_standard);
+ Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_standard);
+ Serial.println(F("Concentration Units (environmental)"));
+ Serial.println(F("---------------------------------------"));
+ Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_env);
+ Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_env);
+ Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_env);
+ Serial.println(F("---------------------------------------"));
+ Serial.print(F("Particles > 0.3um / 0.1L air:")); Serial.println(data.particles_03um);
+ Serial.print(F("Particles > 0.5um / 0.1L air:")); Serial.println(data.particles_05um);
+ Serial.print(F("Particles > 1.0um / 0.1L air:")); Serial.println(data.particles_10um);
+ Serial.print(F("Particles > 2.5um / 0.1L air:")); Serial.println(data.particles_25um);
+ Serial.print(F("Particles > 5.0um / 0.1L air:")); Serial.println(data.particles_50um);
+ Serial.print(F("Particles > 10 um / 0.1L air:")); Serial.println(data.particles_100um);
+ Serial.println(F("---------------------------------------"));
+
+#ifdef WANT_NTP
+ ts = timeClient.getEpochTime();
+#endif /* WANT_NTP */
+ Serial.println("Sending observation");
+
+ lastobservation = (tsm/1000);
+ doc["serial_number"] = aqsn;
+ doc["hub_sn"] = hubsn;
+ doc["type"] = "obs_pm";
+ doc["firmware_revision"] = FIRMWARE_REVISION;
+ JsonArray obs = doc.createNestedArray("obs");
+ JsonArray obsValues = obs.createNestedArray();
+ obsValues.add(ts);
+ obsValues.add(data.pm10_standard);
+ obsValues.add(data.pm25_standard);
+ obsValues.add(data.pm100_standard);
+ obsValues.add(data.pm10_env);
+ obsValues.add(data.pm25_env);
+ obsValues.add(data.pm100_env);
+ obsValues.add(data.particles_03um);
+ obsValues.add(data.particles_05um);
+ obsValues.add(data.particles_10um);
+ obsValues.add(data.particles_25um);
+ obsValues.add(data.particles_50um);
+ obsValues.add(data.particles_100um);
+ // sample interval
+ obsValues.add(60);
+
+ // battery voltage
+ obsValues.add(0);
+
+ obsValues.add((report_frequency/60));
+
+ if(Udp.beginPacket(br_adr, SMARTWEATHER_PORT))
+ Serial.println("Packet begun. 4");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+
+ if(Udp.endPacket())
+ Serial.println("Sent.");
+ else
+ {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+}
+
+void weatherObservation(long tsm) {
+ StaticJsonDocument<512> doc;
+ int written = 0;
+
+#ifdef WANT_NTP
+ ts = timeClient.getEpochTime();
+#endif /* WANT_NTP */
+ Serial.println("Sending observation");
+
+ lastobservation = (tsm/1000);
+ doc["serial_number"] = airsn;
+ doc["hub_sn"] = hubsn;
+ doc["type"] = "obs_st";
+ doc["firmware_revision"] = FIRMWARE_REVISION;
+ JsonArray obs = doc.createNestedArray("obs");
+ JsonArray obsValues = obs.createNestedArray();
+ obsValues.add(ts);
+ // lull
+ // avg
+ // gust
+ // direction
+ // sample interval
+ obsValues.add(calcMinWindspeed());
+ obsValues.add(calcAvgWindspeed());
+ obsValues.add(calcMaxWindspeed());
+ obsValues.add(calcWindDirDegrees());
+ obsValues.add(60);
+
+ if(bmeSensorPresent) {
+ float t(NAN),h(NAN),p(NAN);
+
+ BME280::TempUnit tempUnit(BME280::TempUnit_Celsius);
+ BME280::PresUnit presUnit(BME280::PresUnit_Pa);
+
+ bme280.read(p, t, h, tempUnit, presUnit);
+
+ p/=100;
+
+ if(sensorPresent) {
+ float t, h;
+ t = ht2x.readTemperature();
+ h = ht2x.readHumidity();
+ }
+
+ Serial.print("T ");
+ Serial.println(t);
+
+ Serial.print("H ");
+ Serial.println(h);
+
+ Serial.print("P ");
+ Serial.println(p);
+
+ obsValues.add(p);
+ obsValues.add(t);
+ obsValues.add(h);
+
+ } else {
+ obsValues.add(0);
+ if(sensorPresent) {
+ float t,h;
+ t = ht2x.readTemperature();
+ h = ht2x.readHumidity();
+
+ Serial.print("T ");
+ Serial.println(t);
+
+ Serial.print("H ");
+ Serial.println(h);
+
+ obsValues.add(t);
+ obsValues.add(h);
+ } else {
+ obsValues.add(0);
+ obsValues.add(0);
+ }
+ }
+
+ // ill
+ obsValues.add(0);
+ // uv
+ obsValues.add(0);
+ // rad
+ obsValues.add(0);
+ //rain prev minute
+ // employing a 0.01 inch increment gauge
+ int rain = rainIncrements;
+ rainIncrements = 0;
+
+ Serial.println("rain increments");
+ Serial.println(rain);
+ obsValues.add(rain * (0.01 * 25.4));
+
+ // precip type
+ obsValues.add(0);
+ // lightning distance
+ obsValues.add(0);
+ // lightning count
+ obsValues.add(0);
+ // battery voltage
+ obsValues.add(0);
+
+ obsValues.add((report_frequency/60));
+ if(bmeSensorPresent && sensorPresent) {
+ float t, h;
+ t = ht2x.readTemperature();
+ h = ht2x.readHumidity();
+
+ Serial.print("T ");
+ Serial.println(t);
+
+ Serial.print("H ");
+ Serial.println(h);
+
+ obsValues.add(t);
+ obsValues.add(h);
+ }
+
+ if(Udp.beginPacket(br_adr, SMARTWEATHER_PORT))
+ Serial.println("Packet begun. 4");
+ written = serializeJson(doc, Udp);
+ Serial.println(written);
+
+ if(Udp.endPacket())
+ Serial.println("Sent.");
+ else
+ {
+ Serial.println("Failed to send packet.");
+ sendFailed();
+ }
+}
+
+
+void weather_report() {
+ long tsm = millis();
+
+ if(tsm > RESET_INTERVAL) {
+ Serial.println("Uptime limit reached (RESET_INTERVAL). Resetting.");
+ delay(1000);
+ resetFunc();
+ }
+
+ if((tsm/1000) > (lastreport + 30)) {
+// if(WiFi.status() != WL_CONNECTED) {
+// Serial.println("Lost connectivity with WLAN. Resetting.");
+// delay(1000);
+// resetFunc();
+// }
+ lastreport = tsm / 1000;
+#ifdef WANT_NTP
+
+#endif /* WANT_NTP */
+ // blink(SLOW, 3);
+ hubStatus(tsm);
+ deviceStatus(tsm);
+ aqDeviceStatus(tsm);
+ }
+
+ if((tsm/1000) > (lastobservation + report_frequency)) {
+ watchdog_update();
+ weatherObservation(tsm);
+ airQualityObservation(tsm);
+ }
+}
No newline at end of file