Initial import.

Initial import.

--- sketch/arduino-golem/arduino-golem.ino
+++ sketch/arduino-golem/arduino-golem.ino
@@ -0,0 +1,474 @@
+// vim: softtabstop=2 tabstop=2 tw=120
+
+#include <RCSwitch.h>
+#include <SPI.h>
+#include <EthernetClient.h>
+#include <Ethernet.h>
+#include <EthernetServer.h>
+#include <string.h>
+
+//#define DEBUG
+
+#define MAX_ROUTE_LENGTH 20
+#define MAX_HEADER_NAME_LENGTH 20
+#define MAX_HEADER_VALUE_LENGTH 20
+#define REQUEST_TIMEOUT 1000
+#define REQUEST_TRANSFER_BUFFER_SIZE 200
+#define URL_PARSE_BUFFER_SIZE 10
+
+#define SERIAL_BAUD 115200
+#define MAC_ADDRESS 0x00, 0x16, 0x3E, 0x54, 0x5E, 0xA1
+#define IP_ADDRESS 192, 168, 1, 10
+#define SERVER_PORT 80
+
+#define RF_EMITTER_PIN 5
+#define SEND_REPEAT 1
+#define SEND_REPEAT_DELAY 10
+
+class FixedLengthString {
+  public:
+
+    FixedLengthString(char* buffer, uint16_t size) : buffer(buffer), max_len(size - 1), len(0) {
+      buffer[0] = 0;
+    }
+
+    uint16_t Length() const {
+      return len;
+    }
+
+    operator const char*() const {
+      return buffer;
+    }
+
+    boolean Add(char character) {
+      if (len == max_len) return false;
+
+      buffer[len++] = character;
+      buffer[len] = 0;
+
+      return true;
+    }
+
+    FixedLengthString& Clear() {
+      len = 0;
+      buffer[0] = 0;
+
+      return *this;
+    }
+
+    uint16_t MaxLength() {
+      return max_len;
+    }
+
+  private:
+
+    FixedLengthString(const FixedLengthString&);
+    FixedLengthString& operator =(const FixedLengthString&);
+
+    char* buffer;
+    uint16_t max_len;
+    uint16_t len;
+};
+
+
+template<unsigned int M> class AllocatedFixedLengthString : public FixedLengthString {
+  public:
+
+    AllocatedFixedLengthString() : FixedLengthString(buffer, M + 1) {}
+
+  private:
+
+    char buffer[M + 1];
+};
+
+
+class HttpParser {
+  public:
+
+    enum status_t {STATUS_PARSING, STATUS_SUCCESS, STATUS_FAILURE};
+
+    HttpParser() : state(STATE_REQUEST_METHOD), last_char(0) {}
+
+    HttpParser& PushChar(char character) {
+      boolean isTerminator = (character == '\n') && (last_char == '\r');
+
+      if (character == '\r') {
+        last_char = character;
+        return *this;
+      }
+
+      switch (state) {
+        case STATE_REQUEST_METHOD:
+          if (isTerminator) {
+            SetState(STATE_FAIL);
+          } else if (character == ' ') {
+            #ifdef DEBUG
+              Serial.print("Request Method: ");
+              Serial.println(method);
+            #endif
+
+            SetState(STATE_REQUEST_URL);
+          } else {
+            method.Add(character);
+          }
+          break;
+
+        case STATE_REQUEST_URL:
+          if (isTerminator) {
+            SetState(STATE_FAIL);
+          } else if (character == ' ') {
+            #ifdef DEBUG
+              Serial.print("Request Route: ");
+              Serial.println(route);
+            #endif
+
+            SetState(STATE_REQUEST_PROTOCOL);
+          } else {
+            route.Add(character);
+          }
+          break;
+
+        case STATE_REQUEST_PROTOCOL:
+          if (isTerminator) {
+            #ifdef DEBUG
+              Serial.print("Request Protocol: ");
+              Serial.println(protocol);
+            #endif
+
+            SetState(STATE_HEADER_NAME);
+          } else {
+            protocol.Add(character);
+          }
+          break;
+
+        case STATE_HEADER_NAME:
+          if (isTerminator) {
+            SetState(header_name.Length() == 0 ? STATE_SUCCESS : STATE_FAIL);
+          } else if (character == ':') {
+            #ifdef DEBUG
+              Serial.print("Header Name: ");
+              Serial.println(header_name);
+            #endif
+
+            SetState(STATE_HEADER_VALUE);
+          } else {
+            header_name.Add(character);
+          }
+          break;
+
+        case STATE_HEADER_VALUE:
+          if (isTerminator) {
+            #ifdef DEBUG
+              Serial.print("Header Value: ");
+              Serial.println(header_value);
+            #endif
+
+            SetState(STATE_HEADER_NAME);
+          } else {
+            if (character != ' ' || header_value.Length() > 0)
+              header_value.Add(character);
+          }
+          break;
+
+       }
+
+       return *this;
+    }
+
+    status_t Status() const {
+      switch (state) {
+        case STATE_SUCCESS:
+          return STATUS_SUCCESS;
+          break;
+
+        case STATE_FAIL:
+          return STATUS_FAILURE;
+          break;
+
+        default:
+          return STATUS_PARSING;
+      }
+    }
+
+    HttpParser& Abort() {
+      SetState(STATE_FAIL);
+      return *this;
+    }
+
+    const FixedLengthString& Route() {
+      return route;
+    }
+
+  private:
+
+    enum parser_state_t {
+      STATE_REQUEST_METHOD,
+      STATE_REQUEST_URL,
+      STATE_REQUEST_PROTOCOL,
+      STATE_HEADER_NAME,
+      STATE_HEADER_VALUE,
+      STATE_SUCCESS,
+      STATE_FAIL
+    };
+
+    void SetState(parser_state_t new_state) {
+      state = new_state;
+
+      switch (state) {
+        case STATE_HEADER_NAME:
+          header_name.Clear();
+          break;
+
+        case STATE_HEADER_VALUE:
+          header_value.Clear();
+          break;
+      }
+    }
+
+    HttpParser(const HttpParser&);
+    HttpParser& operator=(const HttpParser&);
+
+    parser_state_t state;
+
+    AllocatedFixedLengthString<7> method;
+    AllocatedFixedLengthString<MAX_ROUTE_LENGTH> route;
+    AllocatedFixedLengthString<8> protocol;
+    AllocatedFixedLengthString<MAX_HEADER_NAME_LENGTH> header_name;
+    AllocatedFixedLengthString<MAX_HEADER_VALUE_LENGTH> header_value;
+
+    char last_char;
+};
+
+
+class UrlParser {
+  public:
+
+    UrlParser(const char* url) : url(url), pos(0) {
+      url_length = strlen(url);
+
+      if (url_length > 0 && url[0] == '/') pos = 1;
+    }
+
+    boolean NextPathElement(char* buffer, uint16_t buffer_size) {
+      uint16_t current_pos = pos;
+      FixedLengthString element(buffer, buffer_size);
+
+      while (true) {
+
+
+        if (current_pos >= url_length || url[current_pos] == '/') {
+          current_pos++;
+          break;
+        }
+
+        if (!element.Add(url[current_pos])) return false;
+
+        current_pos++;
+      }
+
+      pos = current_pos;
+
+      #ifdef DEBUG
+        Serial.print("Path fragment: ");
+        Serial.println(element);
+      #endif
+
+      return true;
+    }
+
+    boolean AtEnd() {
+      return pos >= url_length;
+    }
+
+  private:
+
+    const char* url;
+    uint16_t url_length;
+    uint16_t pos;
+};
+
+
+class Response {
+  public:
+
+    virtual void Send(EthernetClient&) = 0;
+};
+
+
+class BadRequestResponse: public Response {
+  public:
+
+    virtual void Send(EthernetClient& client) {
+      client.write("HTTP/1.1 400 BAD REQUEST\r\n\r\n");
+      client.write("<title>Page not found!</title>Page not found!\n");
+    }
+};
+
+
+class RouteNotFoundResponse: public Response {
+  public:
+
+    virtual void Send(EthernetClient& client) {
+      client.write("HTTP/1.1 404 NOT FOUND\r\n");
+    }
+};
+
+
+class RequestOKResponse: public Response {
+  public:
+
+    virtual void Send(EthernetClient& client) {
+      client.write("HTTP/1.1 200 OK\r\n\r\n");
+    }
+};
+
+
+boolean toggle_switch(uint8_t switch_index, boolean toggle, RCSwitch& rc_switch) {
+  if (switch_index > 3) return false;
+
+  #ifdef DEBUG
+    Serial.print("Toggle switch ");
+    Serial.print(switch_index);
+    Serial.println(toggle ? " on" : " off");
+  #endif
+
+  char code[14] = "000FFFF0FFFFS";
+
+  switch (switch_index) {
+    case 3:
+      code[3] = '0';
+      break;
+
+    case 2:
+      code[4] = '0';
+      break;
+
+    case 1:
+      code[6] = '0';
+      break;
+
+    case 0:
+      code[5] = '0';
+      break;
+  }
+
+  if (!toggle) code[11] = '0';
+
+  #ifdef DEBUG
+    Serial.print("Sending code ");
+    Serial.print(code);
+  #endif
+
+  for (uint8_t i = 0; i < SEND_REPEAT; i++) {
+    if (i) delay(SEND_REPEAT_DELAY);
+    rc_switch.sendTriState(code);
+  }
+
+  return true;
+}
+
+
+Response& handle_request(HttpParser& parser, RCSwitch& rc_switch) {
+  static BadRequestResponse response_bad_request;
+  static RouteNotFoundResponse response_not_found;
+  static RequestOKResponse response_ok;
+
+  if (parser.Status() != HttpParser::STATUS_SUCCESS) {
+    return response_bad_request;
+  }
+
+  UrlParser url_parser(parser.Route());
+  char buffer[URL_PARSE_BUFFER_SIZE];
+
+  if (!url_parser.NextPathElement(buffer, URL_PARSE_BUFFER_SIZE)) return response_not_found;
+  if (strcmp(buffer, "socket") != 0) return response_not_found;
+
+  if (!url_parser.NextPathElement(buffer, URL_PARSE_BUFFER_SIZE)) return response_not_found;
+  if (strlen(buffer) > 2 ) return response_not_found;
+
+  unsigned int switch_index;
+  if (sscanf(buffer, "%u", &switch_index) == 0) return response_not_found;
+
+  if (!url_parser.NextPathElement(buffer, URL_PARSE_BUFFER_SIZE)) return response_not_found;
+  if (!url_parser.AtEnd()) return response_not_found;
+
+  if (strcmp(buffer, "on") == 0) {
+    if (!toggle_switch(switch_index, true, rc_switch)) return response_not_found;
+  } else if (strcmp(buffer, "off") == 0) {
+    if (!toggle_switch(switch_index, false, rc_switch)) return response_not_found;
+  } else {
+    return response_not_found;
+  }
+
+  return response_ok;
+}
+
+
+EthernetServer server(SERVER_PORT);
+RCSwitch rc_switch;
+
+
+void setup() {
+  byte macAddress[] = {MAC_ADDRESS};
+  IPAddress ip(IP_ADDRESS);
+
+  pinMode(10, OUTPUT);
+  pinMode(4, OUTPUT);
+
+  digitalWrite(4, HIGH);
+  digitalWrite(10, LOW);
+
+  pinMode(RF_EMITTER_PIN, OUTPUT);
+  rc_switch.enableTransmit(RF_EMITTER_PIN);
+
+  Serial.begin(SERIAL_BAUD);
+  Ethernet.begin(macAddress, ip);
+  server.begin();
+
+  Serial.print("Server listening at ");
+  Serial.println(Ethernet.localIP());
+}
+
+void loop() {
+  EthernetClient client = server.available();
+
+  if (client) {
+    #ifdef DEBUG
+      Serial.println("Incoming connection...");
+    #endif
+
+    HttpParser parser;
+    uint32_t start_timestamp = millis();
+
+    while (client.connected() && abs(millis() - start_timestamp) <= REQUEST_TIMEOUT && parser.Status() == HttpParser::STATUS_PARSING) {
+      int bytes_available;
+
+      while(parser.Status() == HttpParser::STATUS_PARSING && (bytes_available = client.available())) {
+         char buffer[REQUEST_TRANSFER_BUFFER_SIZE];
+         uint8_t to_read = bytes_available > REQUEST_TRANSFER_BUFFER_SIZE ? REQUEST_TRANSFER_BUFFER_SIZE : bytes_available;
+
+         client.readBytes(buffer, to_read);
+         for (uint8_t i = 0; i < to_read; i++) {
+           parser.PushChar(buffer[i]);
+         }
+      };
+
+      delay(10);
+    }
+
+    if ((abs(millis() - start_timestamp)) > REQUEST_TIMEOUT) {
+      #ifdef DEBUG
+        Serial.println("Request timeout");
+      #endif
+
+      parser.Abort();
+    }
+
+    Response& response(handle_request(parser, rc_switch));
+    response.Send(client);
+
+    delay(10);
+
+    client.stop();
+  }
+}


Implements buzzer via buzzer.ino

Implements buzzer via buzzer.ino

error: pathspec 'buzzer.ino' did not match any file(s) known to git

--- buzzer.ino
+++ buzzer.ino
@@ -0,0 +1,54 @@
+// resonant frequency of the buzzer. At this frequency the buzzer
+// is the loudest
+#define BUZZER_FREQ_HZ                (4000)
+
+// standard duration of an alert
+#define BUZZER_DURATION_MS            (250)
+
+void buzzer_init() {
+}
+
+/**
+ * Plays jingle number n
+ */
+void buzzer_jingle(uint8_t n) {
+  buzzer_sound(261, 100);
+  buzzer_sound(293, 100);
+  buzzer_sound(329, 100);
+  buzzer_sound(349, 100);
+  buzzer_sound(391, 100);
+  buzzer_sound(440, 100);
+}
+
+/**
+ * Sounds the buzzer. If freq_hz is 0 then the centre frequency
+ * of the piezo is used.
+ *
+ * If dur_ms is 0, then a default duration of 250 ms is used.
+ */
+void buzzer_sound(uint16_t freq_hz, millis_delta_t dur_ms) {
+  if (freq_hz == 0) freq_hz = BUZZER_FREQ_HZ;
+  if (dur_ms == 0) dur_ms = BUZZER_DURATION_MS;
+
+  // we need to end SPI b/c we use MISO to control
+  // the buzzer
+  SPI.end();
+
+  // make MISO an output
+  DDRB |= _BV(PB3);
+
+  millis_t now = millis();
+
+  // generate a 2 kHz tone using delay. Not elegant but close enough
+  uint16_t period_us = 1000000/freq_hz;
+
+  while (millis() - now < dur_ms) {
+    PORTB |= _BV(PB3);
+    delayMicroseconds(period_us/2);
+    PORTB &= ~_BV(PB3);
+    delayMicroseconds(period_us/2);
+  }
+
+  // restart SPI or OLED will not work
+  SPI.begin();
+}


Added first tracked version of main sketch

Sensitive information factored out into Marshall.h, not included in repository

error: pathspec 'ArduinoWeatherStation.ino' did not match any file(s) known to git

--- ArduinoWeatherStation.ino
+++ ArduinoWeatherStation.ino
@@ -0,0 +1,1903 @@
+/*
+ Weather Shield Example
+ By: Nathan Seidle
+ SparkFun Electronics
+ Date: November 16th, 2013
+ License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
+
+ Much of this is based on Mike Grusin's USB Weather Board code: https://www.sparkfun.com/products/10586
+
+ */
+const String wxVersion = "L12jB3"; // jjj
+const String wxOwner = "-M-";
+const byte ina219a_HWaddr = 0x40;  //0x40 for everyone but Lance. 0x44 for Lance.
+const byte ina219b_HWaddr = 0x41;  //
+const byte bme280a_HWaddr = 0x76;  //Default may be 0x77 depending on mfgr
+const bool disableNTP = true;      //Set to false to allow NTP, but it can cause crashes if it doesn't get a response.
+const String startupMessage = "UM Weather Station (ver L12jB3 2017/09/07) starting at ms "; // jjj
+
+
+#include <avr/wdt.h>   // WatchDog Timer. If I hit an endless loop, reset. Kindof. May not reset Ethernet properly.
+#include <EEPROM.h>    // will need this if I start writing to eeprom
+#include <Wire.h>      // I2C needed for weather station sensors
+#include <Math.h>      // Need cos() for calculating sunrise & sunset
+#include <Time.h>      // https://github.com/PaulStoffregen/Time  but this header doesn't seem to be needed?
+#include <TimeLib.h>   // https://github.com/PaulStoffregen/Time
+#include <Timezone.h>  // https://github.com/JChristensen/Timezone, might conflict with timelib above?
+#include <DS3232RTC.h> // https://github.com/JChristensen/DS3232RTC, using a DS3231, but it's still supported.
+//#include "TinyGPS.h" // https://github.com/mikalhart/TinyGPS, GPS module for time with no Internet
+#include "SparkFunMPL3115A2.h" // Pressure sensor - Search "SparkFun MPL3115" and install from Library Manager
+#include "SparkFunHTU21D.h"    // Humidity sensor - Search "SparkFun HTU21D" and install from Library Manager
+#include "Adafruit_INA219.h"   // Voltage/Current sensor. https://github.com/adafruit/Adafruit_INA219
+#include "SparkFunBME280.h"    // High precision Temp & Humidity sensor. https://github.com/sparkfun/SparkFun_BME280_Arduino_Library
+
+#include "Marshall.h" // Site-specific parameters that cannot currently be published
+
+//#define logOneLine( line) logFile.println(line); Serial.println(line);
+//#define logOneLine2( line, base) logFile.println(line, base); Serial.println(line,base);
+//#define logSome( line) logFile.print(line); Serial.print(line);
+#define logOneLine( line) Serial.println(line);
+#define logOneLine2( line, base) Serial.println(line,base);
+#define logSome( line) Serial.print(line);
+
+
+Adafruit_INA219 ina219a(ina219a_HWaddr);     // First  ina219 sensor: A
+Adafruit_INA219 ina219b(ina219b_HWaddr);     // Second ina219 sensor: B
+BME280 bme280a;                              // First  bme280 sensor: A
+MPL3115A2 myPressure; //Create an instance of the pressure sensor
+HTU21D myHumidity; //Create an instance of the humidity sensor
+
+//Hardware pin definitions, weather station
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+// digital I/O pins
+const byte WSPEED = 3;
+const byte RAIN = 2;
+const byte STAT1 = 7;
+const byte STAT2 = 8;
+
+// analog I/O pins
+const byte WDIR = A0;
+const byte LIGHT = A1;
+const byte BATT = A2;
+const byte REFERENCE_3V3 = A3;
+const byte SLA_BATT = A12;
+
+// I2C devices
+// 0x40  64 Humidity/temp sensor?
+// 0x44  68 INA219 on 3rd address (1st address is 0x40, conflicts with humidty sensor)
+// 0x41  65 INA219 on 2nd address
+// 0x60  96 Humidity/temp sensor?
+// 0x76 118 BME280 environmental sensor
+
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+// EEPROM MAP of used addresses
+// 00-08 RESERVED
+//    09 PowerSave flag
+//    10 Watchdog flag
+// 11-14 Watchdog time_t
+//
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+const int eePowerSave = 9;
+const int eeWatchdog = 10;
+const int eeWatchdogTime = 11;
+
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+//Global Variables
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+unsigned long lastSecond;   //The millis counter to see when a second rolls by
+unsigned long loopCounter;  //see how often we loop
+unsigned long loopDelta;    //see how many loops since the last print
+unsigned long msTemp;       //store millis() now and then to see how long something took, for Serial.print()s all over
+unsigned long usTemp;       //store micros() now and then to see how long something took. Much smaller than millis!
+byte seconds;               //When it hits 60, increase the current minute
+byte minutes;               //Keeps track of where we are in various arrays of data
+byte minutes_10m;           //Keeps track of where we are in wind gust/dir over last 10 minutes array of data
+byte minutes_5m;            //Keep track of wind gust over previous 5 minutes.
+byte hours;
+int days;                   //Yes I'm optimistic
+int sunrise;                //Minutes after midnight for sunrise
+int sunset;                 //Minutes after midnight for sunset
+byte sunriseDay = 0;        //Day we last calculated sunrise/sunset for. If it's not today, calc again.
+bool justBooted = true;     //Some stuff settles after the first minute, so let's keep track of that.
+bool justRestarted = true;  //Print an R at the end of the first upload attempt to make it easy to see a reboot.
+bool powerSave = false;     //Set a flag when we're in power save mode. Do some stuff different.
+time_t reportWatchdog = 0;  //Do we need to report a watchdog reset?
+time_t recentTime = 0;      //Set the current time periodically so we can use it in the Watchdog Interrupt
+time_t lastCrashTime = 0;
+const char charComma = ',';       //Save 16 bytes with Serial.print(charComma) instead of Serial.print(",") all over.
+//const char compile_date[] = __DATE__ " " __TIME__;
+
+long lastWindCheck = 0;
+volatile long lastWindIRQ = 0;
+volatile byte windClicks = 0;
+
+// For Marshall weather, keep track of and report the following:
+/*
+ * 1: time
+ * 2: date
+ * 3: Wind speed mph, 1 min avg
+ * 4: Wind speed mph, 10 min Peak (needs to change to 5 min peak)
+ * 5: Wind direction, 1 min avg
+ * 6: Temerature in F, outside, current
+ * 7: Humidity, outside, current
+ * 8: Barometric pressure in kPa (divide by 100), sampled once a minute
+ * 9: Barometric pressure delta since last sample (1 minute ago)
+ * 10: rain for the day (not used, set to -LR- for identification)
+ * 11: rain long term?  (not used, set to -LR- for identification)
+ * 12: Temperature in C from RTC
+ * 13: Humidity, inside enclosure, currently same as #7
+ * 13: voltage 1: battery
+ * 14: current 1: battery
+ * 15: voltage 2: 5v
+ * 16: current 2: 5v
+ * 17: light level (accurate, but probably zero inside enclosure)
+ * 18: uptime in HH:MM:SS
+ * 19: raw wind direction reading for calculating best binning values
+ * 20: Status stuff: reboots, socket status, failures, etc...
+ */
+
+float windSpeedAvg = 0; //Lets try keeping avg using MATH!
+
+#define WIND_DIR_AVG_SIZE 60
+#define WIND_SPEED_AVG_SIZE 60
+#define VOLTAGE_AVG_SIZE 10
+int winddiravg[WIND_DIR_AVG_SIZE];  //120 (now 60) ints to keep track of 2 (now 1) minute average.
+float windgust_10m[10];             //10 floats to keep track of 10 minute max
+float windgust_5m[5];               //5 floats to keep track of highest gust each of the last 5 minutes.
+int windgustdirection_10m[10];      //10 ints to keep track of 10 minute max
+int windgustdirection_5m[5];        //5 ints to keep track of 5 minute max
+volatile float rainHour[60];        //60 floating numbers to keep track of 60 minutes of rain
+
+//These are all the weather values that wunderground expects:
+int winddir = 0;              // [0-360 instantaneous wind direction]
+unsigned int winddirRaw = 0;   // Keep the raw ADC reading for troubleshooting
+float windspeedmph = 0;       // [mph instantaneous wind speed]
+float windgustmph = 0;        // [mph current wind gust, using software specific time period]
+int windgustdir = 0;          // [0-360 using software specific time period]
+int winddir_avg2m = 0;        // [0-360 2 minute average wind direction]
+float windgustmph_10m = 0;    // [mph past 10 minutes wind gust mph ]
+float windgustmph_5m = 0;     // [mph past 5 minutes wind gust]
+float windmax_1m = 0;
+float windmin_1m = 0;
+int windgustdir_10m = 0;      // [0-360 past 10 minutes wind gust direction]
+int windgustdir_5m = 0;       // 0-360 past 5 minutes wind gust direction
+String strWindDir = "ERR";    // N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW
+
+float humidity = 0; // [%]
+float tempf = 0; // [temperature F]
+float rainin = 0; // [rain inches over the past hour)] -- the accumulated rainfall in the past 60 min
+volatile float dailyrainin = 0; // [rain inches so far today in local time]
+float pressure = 0;
+float oldPressure = 0;
+float pres5min[5];
+//float dewptf; // [dewpoint F] - It's hard to calculate dewpoint locally, do this in the agent
+
+float batt_lvl = 11.8;     // [analog value from 0 to 1023]
+float sla_lvl = 11.8;      // [analog value from 0 to 1023; measures Sealed Lead Acid battery voltage via external voltage divider]
+int   sla_raw = 0;         // integer, raw 0-1023 reading from Analog Port
+float sla_30sec = 11.8;    // 10 second rolling average of SLA battery voltage (~12v)
+int   sla_10secRaw = 1180; // 10 second average of AnalogPort reading (0-1023).
+float light_lvl = 455;     // [analog value from 0 to 1023]
+float ina219a_volts;
+float ina219a_current;
+float ina219b_volts;
+float ina219b_current;
+
+// volatiles are subject to modification by IRQs
+volatile unsigned long raintime, rainlast, raininterval, rain;
+
+
+//****************************
+//***  Data Storage in RAM ***
+//****************************
+
+// 10 bytes so far, that's 2,400 bytes to store 4 hours. Not bad?
+struct wxCache_struct {
+  byte wd : 6;      //Wind direction / 6 (remember to multiply)
+  byte ws : 6;      //Wind speed
+  byte gust : 4;    //Wind gust = gust * 2 + wind speed!!
+  byte temp1;   // in F
+  byte hum1;
+  byte pres1;   //Pressure in hPa minus 900
+  byte temp2;
+  byte volt1;   //Voltage * 10 (remember to divide by 10)
+  byte amp1;    //Milliamps / 10 (remember to multiply by 10)
+  byte volt2;
+  byte amp2;
+};
+
+time_t wxCache_time;
+byte wxCache_count;
+#define WX_CACHE_MAX 240
+
+wxCache_struct wxCache[WX_CACHE_MAX];
+//2,028 used or 6,164 free without storage
+//4,428 used or 3,764 free with 4 hours storage
+
+
+
+//**************************
+//***  Ethernet & NTP  *****
+//**************************
+#include <SPI.h>
+#include <Ethernet.h>
+#include <EthernetUdp.h>
+#include <utility/W5100.h>
+
+// Enter a MAC address for your controller below.
+// Newer Ethernet shields have a MAC address printed on a sticker on the shield
+// [MarshallProprietary] byte mac[] = { ??? };
+// Set the local static IP address to use if the DHCP fails to assign
+IPAddress ip(192, 168, IPq3, 222);
+IPAddress dnsServer(8, 8, 8, 8);
+IPAddress gateway(192, 168, IPq3, IPgw);  // Must be 254 for Marshall, set at TOP of sketch since we need to check/change often.
+IPAddress subnet(255, 255, 255, 0);
+
+// if you don't want to use DNS (and reduce your sketch size)
+// use the numeric IP instead of the name for the server:
+//IPAddress server(74,125,232,128);    // numeric IP for Google (no DNS)
+//char server[] = "www.google.com";    // name address for Google (using DNS)
+char server[] = "www.crestlinesoaring.org"; // Web server to connect to.
+// [MarshallProprietary]
+
+// Some debugging and record keeping variables
+bool ethStopped = true;
+unsigned long ethLastMillis = 0;
+const int ETH_TIMEOUT = 6000;
+byte ethTimeouts = 0;
+byte ethConnFails = 0;
+int ethLastFailureCode = 0;
+uint8_t ethSockStatus[MAX_SOCK_NUM];
+
+// NTP (date & time) stuff
+unsigned int NTPlocalPort = 8888;    // Local port to send from & listen for UDP NTP packets
+int timeZone = -7;                   // Pacific (Daylight = -7 / Standard = -8) Time (FIXME: add DST handling someday)
+time_t pacific, utc;
+TimeChangeRule *tcr;
+TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};  //UTC - 7 hours
+TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};   //UTC - 8 hours
+Timezone usPacific(usPDT, usPST);
+//utc = now();  //current time from the Time Library
+//pacific = usPacific.toLocal(utc, &tcr);
+
+
+const int NTP_PACKET_SIZE = 48;
+byte packetBuffer[ NTP_PACKET_SIZE ];
+char timeServer[] = "us.pool.ntp.org";
+// [MarshallProprietary]
+//IPAddress timeServerIP(128, 138, 141, 172);   //time.nist.gov IP at one point
+unsigned long msNTPrequest;          // miliseconds at which NTP request was made (so we can see how long it took)
+
+
+// Initialize the Ethernet client library
+// with the IP address and port of the server
+// that you want to connect to (port 80 is default for HTTP):
+EthernetClient client;
+EthernetUDP Udp;
+
+
+//**************************************
+//****** SD Card Reading ***************
+//**************************************
+
+//#include <SD.h>
+
+/*Sd2Card card;
+SdVolume volume;
+SdFile root;
+File logFile;
+String filename = "log-nodate";
+
+const int SdChipSelect = 4;
+*/
+
+
+
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+//
+//Interrupt routines (these are called by the hardware interrupts, not by the main code)
+//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+void rainIRQ()
+// Count rain gauge bucket tips as they occur
+// Activated by the magnet and reed switch in the rain gauge, attached to input D2
+{
+    raintime = millis(); // grab current time
+    raininterval = raintime - rainlast; // calculate interval between this and last event
+
+    if (raininterval > 20) // ignore switch-bounce glitches less than 10mS after initial edge
+    {
+        dailyrainin += 0.011; //Each dump is 0.011" of water
+        rainHour[minutes] += 0.011; //Increase this minute's amount of rain
+
+        rainlast = raintime; // set up for next event
+    }
+}
+
+void wspeedIRQ()
+// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
+{
+    if (millis() - lastWindIRQ > 20) // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes. Now 20ms, 70mph max.
+    {
+        lastWindIRQ = millis(); //Grab the current time
+        windClicks++; //There is 1.492MPH for each click per second.
+    }
+}
+
+// Watchdog timer fired, let's record a little and report it next bootup.
+ISR(WDT_vect)
+{
+    //Serial.println("--==000 Entered Watchdog Interrupt Service Request Handler 000==--");
+
+    //time_t lastCrashTime;  //Made this a volatile global variable
+
+    // Get the last crash time. If it's too recent, we won't write a new time to avoid thrashing EEPROM too frequently.
+    EEPROM.put(10, 1);
+//    EEPROM.get(11, lastCrashTime);
+//    if (recentTime > lastCrashTime + 600) {
+//      EEPROM.put(10, 1);                // write a "1" to the first byte to indicate the data in second byte is valid and the ISR triggered properly
+//      EEPROM.put(11, recentTime);
+//    }
+
+    //Serial.println("---===00 Done with Watchdog ISR 00===---");
+
+    while(true);                  // triggers the second watchdog timeout for a reset (does this actually work?)
+}
+
+
+/**********************************************************
+ *    _____   ______   _______   _    _   _____
+ *   / ____| |  ____| |__   __| | |  | | |  __ \
+ *  | (___   | |__       | |    | |  | | | |__) |
+ *   \___ \  |  __|      | |    | |  | | |  ___/
+ *   ____) | | |____     | |    | |__| | | |
+ *  |_____/  |______|    |_|     \____/  |_|
+ *
+ ***********************************************************/
+void setup()
+{
+    //Serial.begin(9600);
+    //Serial.begin(38400);
+    Serial.begin(115200);
+    Serial.println();
+    Serial.print(startupMessage);
+    Serial.println(millis());
+
+    //Enable the WatchDog, 8 second timeout.
+    wdt_enable(WDTO_8S);
+
+    //delay(300);
+
+
+    if (EEPROM.read(eePowerSave) == 255) {
+      Serial.println("EEPROM eePowerSave was 255, is this a new Arduino? Setting to false (0).");
+      EEPROM.update(eePowerSave, false);
+    }
+
+    // Check EEPROM to see if we should be in power save mode. If so, shut some stuff off immediately.
+	  Serial.print("Reading EEPROM to see power save state: ");
+    if (EEPROM.read(eePowerSave)) {
+  	  Serial.print("Shutting off Eth and Ubiquity... ");
+      powerSave = true;
+
+  	  pinMode(30, OUTPUT);     //             common GND source for "YYD-3" FET switches.
+      digitalWrite(30, LOW);   //             must always be low
+
+      pinMode(28, OUTPUT);     //             prepares Ubiquiti power control pin
+      digitalWrite(28, LOW);   //             turns Ubiquiti off (on=HIGH / off=low, default=on)
+
+      pinMode(8, OUTPUT);      //             prepares ETH power control pin
+      digitalWrite(8, HIGH);   //             turns ETH shield off (on=low / off=HIGH, default=on)
+
+    } else {
+	    Serial.print("Turning on Eth and Ubiquity... ");
+  	  powerSave = false;
+
+      pinMode(30, OUTPUT);     //             common GND source for "YYD-3" FET switches.
+      digitalWrite(30, LOW);   //             must always be low
+
+      pinMode(28, OUTPUT);     //             prepares Ubiquiti power control pin
+      digitalWrite(28, HIGH);  //             turns Ubiquiti on (on=HIGH / off=low, default=on)
+
+      pinMode(8, OUTPUT);      //             prepares ETH power control pin
+      digitalWrite(8, LOW);    //             turns ETH shield on (on=low / off=HIGH, default=on)
+
+  	}
+    Serial.println("Done.");
+
+
+
+    //Weather Station stuff
+    pinMode(STAT1, OUTPUT); //Status LED Blue
+    pinMode(STAT2, OUTPUT); //Status LED Green
+
+    pinMode(WSPEED, INPUT_PULLUP); // input from wind meters windspeed sensor
+    pinMode(RAIN, INPUT_PULLUP); // input from wind meters rain gauge sensor
+
+    pinMode(REFERENCE_3V3, INPUT);
+    pinMode(LIGHT, INPUT);
+
+    //Configure the humidity sensor
+    Serial.print(F("Starting Sparkfun W-Shield Humidity sensor, took ")); usTemp = micros();
+    myHumidity.begin();
+    Serial.print(micros() - usTemp); Serial.println("us");
+
+    //Setup INA219 voltage and current sensor(s)
+    Serial.print(F("Starting INA219a Volt & current sensor A: ")); usTemp = micros();
+    ina219a.begin();
+    Serial.print(micros() - usTemp); Serial.println("us.");
+
+    Serial.print(F("Starting INA219b Volt & current sensor B: ")); usTemp = micros();
+    ina219b.begin();
+    Serial.print(micros() - usTemp); Serial.println("us.");
+
+    //Setup BME280 temperatue and humidity sensor(s)
+    Serial.print(F("Starting BME280a Temperature & Humidity sensor A, status: 0x")); usTemp = micros();
+    bme280a.settings.commInterface = I2C_MODE;
+    bme280a.settings.I2CAddress = bme280a_HWaddr;
+    bme280a.settings.runMode = 3;
+    bme280a.settings.tempOverSample = 1;  //oversample rate: 1-5 equate to 1, 2, 4, 8, 16
+    bme280a.settings.pressOverSample = 1; //oversample rate: 1-5 equate to 1, 2, 4, 8, 16
+    bme280a.settings.humidOverSample = 1; //oversample rate: 1-5 equate to 1, 2, 4, 8, 16
+    Serial.print(bme280a.begin(), HEX);
+    Serial.print(", took "); Serial.print(micros() - usTemp); Serial.println("us.");
+
+    //Configure the Sparkfun pressure sensor
+    Serial.print(F("Starting Sparkfun pressure, took ")); msTemp = millis();
+    delay(250);                            // Little delay in case it helps
+    myPressure.begin();                    // Get sensor online
+    myPressure.setModeBarometer();         // Measure pressure in Pascals from 20 to 110 kPa
+    myPressure.setOversampleRate(7);       // Set Oversample to the recommended 128
+    myPressure.enableEventFlags();         // Enable all three pressure and temp event flags
+    pressure = myPressure.readPressure();  // Get an initial reading so we can report in the first minute
+    oldPressure = pressure;                // Make sure the "pressure delta" is zero at startup
+    Serial.print(millis() - msTemp); Serial.println("ms.");
+
+    seconds = 0;
+    lastSecond = millis();
+
+    // attach external interrupt pins to IRQ functions
+    attachInterrupt(0, rainIRQ, FALLING);
+    attachInterrupt(1, wspeedIRQ, FALLING);
+
+    // turn on interrupts
+    interrupts();
+
+    Serial.println();
+    Serial.print(F("Startup done at "));
+    Serial.print(millis());
+    Serial.println("ms!");
+
+    loopCounter = 0;
+    loopDelta = 0;
+
+    // DEBUG: populates the array so it takes up RAM. Enough for 4 hours of data if WX_CACHE_MAX is 240.
+    for (byte i = 0; i < WX_CACHE_MAX; i++) {
+      wxCache[i].ws = i;
+      //now 4428 used 3764 free
+    }
+
+    //**********************************
+    //***** SD Card Stuff  *************
+    //**********************************
+
+    /*
+    Serial.print("\nInitializing SD card in 250ms... ");
+    delay(250);
+    wdt_reset();
+
+    pinMode(SS, OUTPUT);
+    if (!SD.begin(SdChipSelect)) {
+      Serial.println("SD Failed to start!!");
+    }
+    //File startLog = SD.open("start.log");
+    File root = SD.open("/");
+    printDirectory(root, 0);
+    root.close();
+    */
+
+    // we'll use the initialization code from the utility libraries
+    // since we're just testing if the card is working!
+    /*
+    if (!card.init(SPI_HALF_SPEED, SdChipSelect)) {
+      Serial.println("initialization failed. Things to check:");
+      Serial.println("* is a card inserted?");
+      Serial.println("* is your wiring correct?");
+      Serial.println("* did you change the chipSelect pin to match your shield or module?");
+    } else {
+      Serial.println("Wiring is correct and a card is present.");
+
+      // print the type of card
+      Serial.print("\nCard type: ");
+      switch (card.type()) {
+        case SD_CARD_TYPE_SD1:
+          Serial.println("SD1");
+          break;
+        case SD_CARD_TYPE_SD2:
+          Serial.println("SD2");
+          break;
+        case SD_CARD_TYPE_SDHC:
+          Serial.println("SDHC");
+          break;
+        default:
+          Serial.println("Unknown");
+      }
+
+      // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
+      if (!volume.init(card)) {
+        Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
+        return;
+      }
+
+
+      // print the type and size of the first FAT-type volume
+      uint32_t volumesize;
+      Serial.print("\nVolume type is FAT");
+      Serial.println(volume.fatType(), DEC);
+      Serial.println();
+
+      volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
+      volumesize *= volume.clusterCount();       // we'll have a lot of clusters
+      volumesize *= 512;                         // SD card blocks are always 512 bytes
+      Serial.print("Volume size (bytes): ");
+      Serial.println(volumesize);
+      Serial.print("Volume size (Kbytes): ");
+      volumesize /= 1024;
+      Serial.println(volumesize);
+      Serial.print("Volume size (Mbytes): ");
+      volumesize /= 1024;
+      Serial.println(volumesize);
+
+
+      Serial.println("\nFiles found on the card (name, date and size in bytes): ");
+      root.openRoot(volume);
+
+      // list all files in the card with date and size
+      root.ls(LS_R | LS_DATE | LS_SIZE);
+    } */
+
+    /**********************************************
+     * EEPROM READ, see if we did a Watchdog crash!
+     **********************************************/
+
+    if (EEPROM.read(10)) {
+      //We had a watchdog event. It contains a time_t with the time. Save it.
+      //example: return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
+
+      // Copy the saved date & time into reportWatchdog so we can repeatedly use it until we have a successful upload
+      EEPROM.get(11, reportWatchdog);
+
+      //Print the date & time the watchdog was tripped.
+      Serial.println();
+      Serial.print(F("Watchdog caused the last reboot, at: "));
+      Serial.print(year(reportWatchdog));
+      Serial.print("/");
+      Serial.print(month(reportWatchdog));
+      Serial.print("/");
+      Serial.print(day(reportWatchdog));
+      Serial.print(charComma);
+      Serial.print(hour(reportWatchdog));
+      Serial.print(":");
+      Serial.print(minute(reportWatchdog));
+      Serial.print(":");
+      Serial.print(second(reportWatchdog));
+      Serial.println();
+    }
+
+    Serial.println();
+    Serial.println(F("Beginning Ethernet."));
+    Serial.println();
+
+    //**********************************
+    //***** Ethernet Stuff *************
+    //**********************************
+
+    pinMode(SS, OUTPUT);
+    // Ethernet is slow, let's delay a bit, then reset the watchdog often
+    delay(250);
+    wdt_reset();
+    wdt_disable();
+    Serial.print("Watchdog was reset and disabled at ");
+    Serial.print(millis());
+    Serial.println("ms for Ethernet");
+
+    // Ethernet static for testing, maybe permanent. DHCP method is commented out a few lines down.
+    Ethernet.begin(mac, ip, dnsServer, gateway, subnet);
+
+    // start the Ethernet connection:
+    /* if (Ethernet.begin(mac) == 0) {
+      Serial.println(F("Failed to configure Ethernet using DHCP"));
+      // try to congifure using IP address instead of DHCP:
+      Ethernet.begin(mac, ip, dnsServer, gateway, subnet);
+    } */
+    wdt_enable(WDTO_8S);
+
+    // give the Ethernet shield a second to initialize:
+    delay(1000);
+    Serial.print(millis());
+    Serial.print(F("ms: Started Ethernet (watchdog enabled). Local IP is "));
+    Serial.println(Ethernet.localIP());
+
+    // Request the date & time from RTC or NTP
+    wdt_reset();
+    if (RTC.get()) {
+      // If we have a working RTC, let's just use it. Every few minutes we'll check for NTP too.
+      Serial.println(F("RTC selected as time source."));
+      setSyncProvider(RTC.get);
+      setSyncInterval(300);           // Update system time with the RTC because it actually slews pretty fast; 4 second an hour is typical.
+    } else {
+      // 0 Means the RTC didn't work, set NTP as the sync provider, with an agressive Sync Interval because we depend on it.
+      Serial.print(F("No RTC? Sending NTP packet at "));
+      Serial.println(millis());
+      setSyncProvider(getNtpTime);    // Use the time library's setSyncProvider to check periodically for time
+      setSyncInterval(60);            // Check for time updates every minute. This gets set to "less often" once we have a time fix.
+
+    }
+
+    if (timeStatus() == timeSet) {
+      if (CheckDST()) {
+        timeZone = -7;
+      } else {
+        timeZone = -8;
+      }
+    }
+
+    /*  NOSD
+    if (timeStatus() == timeSet) {
+      filename = "log" + year() + month() + day();
+    } else {
+      filename = "log-nodate";
+    }
+    logFile = SD.open(filename, FILE_WRITE);
+    NOSD */
+
+    //Print a header row.
+    Serial.println(F("time , date     ,w1avg,wmax,dir, tmp, hum, baro , dv ,rain,wnow, tmp, hum, 5v , 5v ,lght, uptime   , FreeMem  ,Raw Wdir, RTC time, Misc error messages"));
+//                    18:22,12/22/2016,02.16,13.1, -1,76.9,41.8,1009.6, 0.0,0.00,00.7,76.9,41.8,4.33,4.33,0.03,0.00:01:20,14532426,1928538
+//                    20:14,8/1/2017,00.00,00.0,-10,89.4,32.1,976.5,-0.01,-LR-,-LR-,89.4,32.1,3.95,38.14,0.07,0.07:57:55,5264,1023
+
+}
+
+/*
+ ***************************************
+  _         ____     ____    _____
+ | |       / __ \   / __ \  |  __ \
+ | |      | |  | | | |  | | | |__) |
+ | |      | |  | | | |  | | |  ___/
+ | |____  | |__| | | |__| | | |
+ |______|  \____/   \____/  |_|
+
+ ***************************************
+ */
+
+
+void loop()
+{
+  //Do "once a second stuff", mostly weather. Also keep track of which minute it is.
+  if(millis() - lastSecond >= 1000)
+  {
+    digitalWrite(STAT1, HIGH); //Blink stat LED to show how long we're doing the "once a second" work
+
+    lastSecond += 1000;
+    wdt_reset(); //I think once a second is enough for our 8 second watchdog.
+
+    //Calc the wind speed and direction every second for 120 second to get 2 minute average
+    float currentSpeed;
+    currentSpeed = get_wind_speed();
+
+    //vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+    //FIXME: Generate a random wind speed for TESTING
+    //float fRand = (float)random(-30.0, 32.0);
+    //fRand = fRand / 10.0;
+    //currentSpeed = windspeedmph + fRand; //For testing, wind from 0-15 randomly rising/falling up to 3mph per sec
+    //currentSpeed = constrain(currentSpeed, 0, 15);
+    //FIXME: done with the random wind speed
+    //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+    windspeedmph = currentSpeed; //update global variable for windspeed when using the printWeather() function
+    int currentDirection = get_wind_direction();
+
+    //Calculate 1 min moving average wind speed instead of using an array
+    float windInc = currentSpeed / WIND_SPEED_AVG_SIZE;
+    float windDec = windSpeedAvg / WIND_SPEED_AVG_SIZE;
+    windSpeedAvg = windSpeedAvg - windDec + windInc;
+
+    //Check to see if this is a gust for the minute
+    if(currentSpeed > windgust_10m[minutes_10m])
+    {
+        windgust_10m[minutes_10m] = currentSpeed;
+        windgustdirection_10m[minutes_10m] = currentDirection;
+    }
+    if(currentSpeed > windgust_5m[minutes_5m])
+    {
+        windgust_5m[minutes_5m] = currentSpeed;
+        windgustdirection_5m[minutes_5m] = currentDirection;
+    }
+
+
+    //Check to see if this is a gust for the day (assuming daily reboots; this never resets on its own).
+    if(currentSpeed > windgustmph)
+    {
+        windgustmph = currentSpeed;
+        windgustdir = currentDirection;
+    }
+
+    // Once-a-minute tasks
+    if(++seconds > 59)
+    {
+      seconds = 0;
+      if(++minutes > 59)
+      {
+        minutes = 0;
+        if(++hours > 23)
+        {
+          hours = 0;
+          ++days;
+        }
+      }
+      if(++minutes_10m > 9) minutes_10m = 0;
+      if(++minutes_5m > 4) minutes_5m = 0;
+
+      // Zero out this minute's rainfall and gust amounts
+      rainHour[minutes] = 0;
+      windgust_10m[minutes_10m] = 0;
+      windgust_5m[minutes_5m] = 0;
+
+      // Set the RTC if it needs it and we have good time from NTP
+      // FIXME: We should use RTC first and only occasionally check it against NTP.
+      if (timeStatus() == timeSet) {
+        // Long as we've got a good time source, only need to update every 10 minutes.
+        setSyncInterval(600);
+        recentTime = now();             //Update this once a minute. It will be used in the Watchdog ISR to give a rough date/time estimate.
+        if (minute() % 10 == 0) {
+          unsigned int diffNTPRTC = 0;
+          time_t timeRTC = RTC.get();
+          time_t timeNTP = getNtpTime();
+          if ((timeRTC == 0) or (timeNTP == 0)) {
+            // RTC and NTP will both return zero on error. Don't bother if either one is invalid / unreadable.
+            diffNTPRTC = 0;
+          } else if (timeNTP > timeRTC) {
+            diffNTPRTC = timeNTP - timeRTC;
+          } else {
+            diffNTPRTC = timeRTC - timeNTP;
+          }
+          if (diffNTPRTC > 0) {
+            Serial.print(F("NTP and RTC differ by "));
+            Serial.print(diffNTPRTC);
+            if (diffNTPRTC == 1) Serial.print(" second.");
+            if (diffNTPRTC >  1) Serial.print(" seconds.");
+            if (diffNTPRTC >  5) {
+              Serial.print("RTC time is ");
+              Serial.print(timeRTC);
+              Serial.print(", setting RTC to ");
+              Serial.print(timeNTP);
+              byte rtcSetStatus;
+              rtcSetStatus = RTC.set(timeNTP);
+              if (rtcSetStatus) {
+                Serial.print(" FAILED. Error code: ");
+                Serial.print(rtcSetStatus);
+              } else {
+                Serial.print(" done.");
+                setTime(timeNTP);
+              }
+            }
+            Serial.println();
+          }
+          if (CheckDST()) {
+            timeZone = -7;
+          } else {
+            timeZone = -8;
+          }
+        } // END of Every 10 Minutes, while the correct date & time are known
+        // If the log filename does not have a date & time,
+        // open a new log filename with the date & time since we know that now.
+        /*
+        if (filename == "log-nodate") {
+          logSome("Date found at: " + year() + month() + day());
+          logOneLine(" @ " + hour() + minute() + second());
+          logFile.close();
+          filename = "log" + year() + month() + day();
+          logFile = SD.open(filename, FILE_WRITE);
+        } */
+      } else if (timeStatus() == timeNeedsSync) {
+        //If we don't have valid time, check for time every 2 minutes. Otherwise it's normally once an hour.
+        setSyncInterval(120);
+        logSome(" time sync lost, interval set to 120s. ");
+
+      } // END of timeStatus() == timeSet
+
+      // Test with a random wait to timeout the watchdog and see how it handles it.
+//      long randomWatchdogTripper;
+//      randomWatchdogTripper = random(12000);
+//      Serial.print("----**** Randomly waiting to try to trip the watchdog ****----, this many ms: ");
+//      Serial.println(randomWatchdogTripper);
+//      delay(randomWatchdogTripper);
+//      Serial.println("----**** I guess we didn't die this time. ****----");
+      // Test with a random wait to timeout the watchdog and see how it handles it.
+
+      // Check every minute that we know sunrise/sunset for today. If not, calc it.
+      if (day() > sunriseDay) getRiseSet();
+
+
+      /* * * * * * * * * * * * * * * * * *
+	   *  P O W E R   S A V E
+	   *  P O W E R   S A V E
+	   * * * * * * * * * * * * * * * * * */
+      // Turn off / on some peripherals at night & morning
+      int minutesToday = hour() * 60 + minute();
+	  Serial.print("Minute of day is: ");
+	  Serial.print(minutesToday);
+      if ( (minutesToday < sunrise) or (minutesToday > sunset - 60) ) {     // jjj removed 60 to save even more   if ( (minutesToday < sunrise - 60) or (minutesToday > sunset - 60) ) {
+
+        Serial.print(", which is Night time. We will switch to day at minute #");
+		Serial.println(sunrise);     // jjj removed 60 to save even more     Serial.println(sunrise - 60);
+        // We're not between "half an hour before sunrise" and sunset, so turn stuff off.
+        // First set variables and record in eeprom that we're in power save mode.
+        if (!powerSave) {
+          powerSave = true;
+          if (!EEPROM.read(eePowerSave)) EEPROM.update(eePowerSave, true);
+        }
+
+        pinMode(28, OUTPUT);     //             prepares Ubiquiti power control pin
+        digitalWrite(28, LOW);   //             turns Ubiquiti off (on=HIGH / off=low, default=on)
+
+        pinMode(8, OUTPUT);      //             prepares ETH power control pin
+        digitalWrite(8, HIGH);   //             turns ETH shield off (on=low / off=HIGH, default=on)
+
+      } else {
+	    Serial.print(", which is Day time. We will switch to night at minute #");
+		Serial.println(sunset - 60);
+	    // Otherwise, make sure things are TURNED ON
+        if (powerSave) {
+          powerSave = false;
+          if (EEPROM.read(eePowerSave)) EEPROM.update(eePowerSave, false);
+
+		  //if powerSave was set, that means we're transitioning to daytime now.
+		  // Until I figure out how to restart the Ethernet, just force a watchdog timeout with delays.
+		    pinMode(28, OUTPUT);     //             prepares Ubiquiti power control pin
+        digitalWrite(28, HIGH);  //             turns Ubiquiti on (on=HIGH / off=low, default=on)
+
+        pinMode(8, OUTPUT);      //             prepares ETH power control pin
+        digitalWrite(8, LOW);    //             turns ETH shield on (on=low / off=HIGH, default=on)
+
+		  Serial.println(F("   ! ! !   We just switched to DAY. Since we can't reset ETHERNET very well, we are rebooting soon!!! ! ! "));
+		  delay(10000);
+		  delay(20000);
+		  delay(30000);
+        }
+
+		//This below kind of doesn't count, because of the reboot above. We really should never get here.
+		    pinMode(28, OUTPUT);     //             prepares Ubiquiti power control pin
+        digitalWrite(28, HIGH);  //             turns Ubiquiti on (on=HIGH / off=low, default=on)
+
+        pinMode(8, OUTPUT);      //             prepares ETH power control pin
+        digitalWrite(8, LOW);    //             turns ETH shield on (on=low / off=HIGH, default=on)
+
+      } // End of night/day figuring out (for power save)
+
+
+    } // END of ONCE A MINUTE tasks
+
+
+    //Calculate the weather every second, for building averages and stuff. Only print/report it every minute, typically.
+    calcWeather();  // Should be called once a second to build averages and stuff.
+    if (justBooted) {
+      //Once the time is reporting that it's synced and it's been at least 15 secs since boot, start reporting weather.
+      if ((seconds > 15) and (timeStatus() == timeSet)) justBooted = false;
+    } else {
+      if (second() == 0) {
+    //if (true) {  // for STRESS TEST we go once a second.
+        // Once a minute, on zero seconds (by NTP/RTC time, not since boot time), upload the weather.
+        ShowSockStatus();   //DEBUG: print IP Socket status on serial.
+        //printWeather();     //DEBUG: print a slightly different weather string on serial.
+        Serial.println(getWeatherString());
+        uploadWeather();
+      }
+      if ((second() == 25) and (minutes == 55)) {
+        //refresh DHCP once an hour if we're using it. NTP/RTC second() so we don't do it same time as an upload. Runtime minutes so it's after an hour of running.
+        Ethernet.maintain();
+      }
+    }
+
+   /**
+    ** Diagnostic, print the weather string to serial output periodically
+    ** use getWeatherString to save memory and show exactly what gets uploaded,
+    ** or printWeather() to get more diagnostics but not necessarily what's uploaded.
+    **/
+    //if (justBooted) Serial.println(getWeatherString());               // print every second for the first 15 secs after booting.
+    //else if (seconds % 10 == 0) Serial.println(getWeatherString());   // then every 10 seconds
+    if (justBooted and seconds % 3 == 0) printWeather();                // print every second for the first 15 secs after booting.
+    else if (0) { // print the seconds on serial
+      Serial.write(8);
+      if (second() > 9) {
+        Serial.write(8);
+      }
+      Serial.print(second());
+    }
+
+
+    digitalWrite(STAT1, LOW); //Turn off WeatherShield's blue stat LED
+
+  } // END of ONCE A SECOND loop (every 1000ms)
+
+
+
+  // This runs every loop, which is usually 10,000's of times a second.
+  if (!ethStopped) {
+    // if there are incoming bytes available from the server,
+    // read them and dump them to serial up to 250 bytes at a time before main loop()ing:
+    for (byte b=0; b < 250; b++) {
+      if (client.available()) {
+        ethLastMillis = millis();
+        char c = client.read();
+        Serial.print(c);
+      } else {
+        break;
+      }
+    }
+
+    // if the server's disconnected, stop the client:
+    if (!client.connected()) {
+      Serial.println();
+      Serial.println(F("    Arduino Ethernet disconnected."));
+      if (reportWatchdog) {
+        Serial.println(F("  Clearing watchdog EEPROM flag"));
+        reportWatchdog = 0;
+        EEPROM.update(10,0);   //Clear the watchdog-happened bit once we have reason to believe it's been reported.
+      }
+      client.stop();
+      ethStopped = true;
+      ethLastFailureCode = 0;
+      justRestarted = false;     //reset this HERE so it stays "true" until a successful ethernet connection has happened.
+
+    // If it's been TIMEOUT (usually 6) seconds since we last heard something, just close the connection.
+    } else if ((millis() - ethLastMillis) > ETH_TIMEOUT) {
+      Serial.println();
+      logOneLine(F("    Arduino Ethernet TIMEOUT! Closing."));
+      client.stop();
+      ethStopped = true;
+      ethTimeouts++;
+    }
+  } //END if(!ethStopped)
+
+  loopCounter++;
+} // END OF LOOP()
+
+
+void getRiseSet()
+{
+  // Calculate sunrise and sunset, based on Los Angeles. Close enough.
+  // From http://forum.arduino.cc/index.php/topic,66426.msg487457.html#msg487457
+  usTemp = micros();
+  float common = cos( ((month()-1)*30.5+day() + 8 ) / 58.091554);
+  sunrise = 349.5 + 66.5 * common;
+  sunset =  1078.5 - 69.5 * common;
+  if (CheckDST()) {
+    sunrise = sunrise + 60;
+    sunset = sunset + 60;
+  }
+  sunriseDay = day();
+  Serial.println();
+  Serial.print("Sunrise today is  "); Serial.print(sunrise); Serial.println(" minutes after midnight.");
+  Serial.print("Sunset  today is " ); Serial.print(sunset);  Serial.println(" minutes after midnight.");
+  Serial.print("  Took "); Serial.print(micros() - usTemp);  Serial.println("us to calculate.");
+  Serial.println();
+}
+
+boolean CheckDST(){
+/*
+Starts: Second Sunday in March
+Ends: First Sunday in November
+From: http://forum.arduino.cc/index.php?topic=66426.15
+*/
+    //January, february, and december are out.
+    if (month() < 3 || month() > 11) { return false; }
+    //April to October are in
+    if (month() > 3 && month() < 11) { return true; }
+    int previousSunday = day() - weekday();
+    //In march, we are DST if our previous sunday was on or after the 8th.
+    if (month() == 3) { return previousSunday >= 8; }
+    //In november we must be before the first sunday to be dst.
+    //That means the previous sunday must be before the 1st.
+    return previousSunday <= 0;
+}
+
+//Calculates each of the variables that wunderground is expecting
+//Expects to be called once a second, bases some calculations on that.
+void calcWeather()
+{
+    //Calc winddir
+    winddir = get_wind_direction();
+
+    //Calc windspeed
+    //windspeedmph = get_wind_speed(); //This is calculated in the main loop on line 179
+
+    //Calc windgustmph
+    //Calc windgustdir
+    //These are calculated in the main loop
+
+    //Calc windspdmph_avg2m
+/*    float temp = 0;
+    for(int i = 0 ; i < 120 ; i++)
+        temp += windspdavg[i];
+    temp /= 120.0;
+    windspdmph_avg2m = temp;
+*/
+
+    //Calc winddir_avg2m, Wind Direction
+    //You can't just take the average. Google "mean of circular quantities" for more info
+    //We will use the Mitsuta method because it doesn't require trig functions
+    //And because it sounds cool.
+    //Based on: http://abelian.org/vlf/bearings.html
+    //Based on: http://stackoverflow.com/questions/1813483/averaging-angles-again
+    long sum = winddiravg[0];
+    int D = winddiravg[0];
+    for(int i = 1 ; i < WIND_DIR_AVG_SIZE ; i++)
+    {
+        int delta = winddiravg[i] - D;
+
+        if(delta < -180)
+            D += delta + 360;
+        else if(delta > 180)
+            D += delta - 360;
+        else
+            D += delta;
+
+        sum += D;
+    }
+    winddir_avg2m = sum / WIND_DIR_AVG_SIZE;
+    if(winddir_avg2m >= 360) winddir_avg2m -= 360;
+    if(winddir_avg2m < 0) winddir_avg2m += 360;
+
+    //Calc windgustmph_10m
+    //Calc windgustdir_10m
+    //Find the largest windgust in the last 10 minutes
+    windgustmph_10m = 0;
+    windgustdir_10m = 0;
+    //Step through the 10 minutes
+    for(int i = 0; i < 10 ; i++)
+    {
+        if(windgust_10m[i] > windgustmph_10m)
+        {
+            windgustmph_10m = windgust_10m[i];
+            windgustdir_10m = windgustdirection_10m[i];
+        }
+    }
+
+    //Calc windgustmph_5m
+    //Calc windgustdir_5m
+    //Find the largest windgust in the last 10 minutes
+    windgustmph_5m = 0;
+    windgustdir_5m = 0;
+    //Step through the 5 minutes
+    for(int i = 0; i < 5 ; i++)
+    {
+        if(windgust_5m[i] > windgustmph_5m)
+        {
+            windgustmph_5m = windgust_5m[i];
+            windgustdir_5m = windgustdirection_5m[i];
+        }
+    }
+
+    //Calc humidity
+    humidity = myHumidity.readHumidity();
+
+    //Calc tempf from pressure sensor
+    tempf = myPressure.readTempF();
+
+    //Total rainfall for the day is calculated within the interrupt
+    //Calculate amount of rainfall for the last 60 minutes
+    rainin = 0;
+    for(int i = 0 ; i < 60 ; i++)
+        rainin += rainHour[i];
+
+    //Calc pressure but ONLY ONCE A MINUTE, so deltas are meaningful.
+    if (seconds == 59) {
+      oldPressure = pressure;
+      pressure = myPressure.readPressure();
+      if (oldPressure == 0) oldPressure = pressure; //First time, let's not have a "delta" of the current pressure.
+    }
+
+    //Get voltage and current from INA219 sensors
+    float shuntvoltage = 0;
+    float busvoltage = 0;
+    float current_mA = 0;
+    float loadvoltage = 0;
+    float avg_plus = 0;
+    float avg_minus = 0;
+
+    // INA219 AAAAAA
+    shuntvoltage = ina219a.getShuntVoltage_mV();
+    busvoltage = ina219a.getBusVoltage_V();
+    current_mA = ina219a.getCurrent_mA();
+    loadvoltage = busvoltage + (shuntvoltage / 1000);
+
+    avg_plus  = loadvoltage / VOLTAGE_AVG_SIZE;
+    avg_minus = ina219a_volts / VOLTAGE_AVG_SIZE;
+    ina219a_volts = ina219a_volts - avg_minus + avg_plus;
+
+    avg_plus = current_mA / VOLTAGE_AVG_SIZE;
+    avg_minus = ina219a_current / VOLTAGE_AVG_SIZE;
+    ina219a_current = ina219a_current -avg_minus + avg_plus;
+
+    // INA219 BBBBBB
+    shuntvoltage = ina219b.getShuntVoltage_mV();
+    busvoltage = ina219b.getBusVoltage_V();
+    current_mA = ina219b.getCurrent_mA();
+    loadvoltage = busvoltage + (shuntvoltage / 1000);
+
+    avg_plus  = loadvoltage / VOLTAGE_AVG_SIZE;
+    avg_minus = ina219b_volts / VOLTAGE_AVG_SIZE;
+    ina219b_volts = ina219b_volts - avg_minus + avg_plus;
+
+    avg_plus = current_mA / VOLTAGE_AVG_SIZE;
+    avg_minus = ina219b_current / VOLTAGE_AVG_SIZE;
+    ina219b_current = ina219b_current -avg_minus + avg_plus;
+
+
+    /* //Diagnostic printout for INA219 sensor
+    Serial.print("Bus: "); Serial.print(busvoltage); Serial.print(" V, ");
+    Serial.print("Shunt: "); Serial.print(shuntvoltage); Serial.print(" mV, ");
+    Serial.print("Load: "); Serial.print(loadvoltage); Serial.print(" V, ");
+    Serial.print("Current: "); Serial.print(current_mA); Serial.print(" mA, ");
+    Serial.print("Power: "); Serial.print(current_mA / 1000 * loadvoltage); Serial.println(" W"); */
+
+
+    //Calc dewptf
+
+    //Calc light level
+    light_lvl = get_light_level();
+
+    //Calc battery level
+    batt_lvl = get_battery_level();
+    sla_lvl = get_sla_level();
+
+}
+
+//Returns the voltage of the light sensor based on the 3.3V rail
+//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
+float get_light_level()
+{
+    float operatingVoltage = analogRead(REFERENCE_3V3);
+    float lightSensor = analogRead(LIGHT);
+
+    operatingVoltage = 3.3 / operatingVoltage; //The reference voltage is 3.3V
+    lightSensor = operatingVoltage * lightSensor;
+
+    return(lightSensor);
+}
+
+//Returns the voltage of the raw pin based on the 3.3V rail
+//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
+//Battery level is connected to the RAW pin on Arduino and is fed through two 5% resistors:
+//3.9K on the high side (R1), and 1K on the low side (R2)
+float get_battery_level()
+{
+    float operatingVoltage = analogRead(REFERENCE_3V3);
+    float rawVoltage = analogRead(BATT);
+
+    operatingVoltage = 3.30 / operatingVoltage; //The reference voltage is 3.3V
+    rawVoltage = operatingVoltage * rawVoltage; //Convert the 0 to 1023 int to actual voltage on BATT pin
+    rawVoltage *= 4.90; //(3.9k+1k)/1k - multiple BATT voltage by the voltage divider to get actual system voltage
+
+    return(rawVoltage);
+}
+
+float get_sla_level()
+{
+    float operatingVoltage = analogRead(REFERENCE_3V3);
+    float rawVoltage = analogRead(SLA_BATT);
+    sla_raw = rawVoltage;
+
+    //Calculate a rolling average over 10 seconds. Each second we subtract 1/10th of the average, and add the current value.
+    sla_10secRaw -= sla_10secRaw / 10;
+    sla_10secRaw += sla_raw;
+
+    operatingVoltage = 3.30 / operatingVoltage; //The reference voltage is 3.3V
+    rawVoltage = operatingVoltage * rawVoltage; //Convert the 0 to 1023 int to actual voltage on BATT pin
+    //rawVoltage *= 4.90; //(3.9k+1k)/1k - multiple BATT voltage by the voltage divider to get actual system voltage
+    //rawVoltage *= 15.705; //(1m+68k)/68k - multiply SLA_BATT voltage by the voltage divider to get actual battery voltage
+    rawVoltage *= 16.705; //add 1 for "fudge factor".
+    sla_30sec -= sla_30sec / 30.0;
+    sla_30sec += rawVoltage / 30.0;
+
+    return(rawVoltage);
+
+}
+
+//Returns the instataneous wind speed
+float get_wind_speed()
+{
+    float deltaTime = millis() - lastWindCheck; // (ex: 750ms)
+    deltaTime /= 1000.0; //Covert to seconds
+    float windSpeed = (float)windClicks / deltaTime; // (ex, 3 clicks / 0.750s = 4 clicks per second)
+
+    windClicks = 0; //Reset and start watching for new wind
+    lastWindCheck = millis();
+    windSpeed *= 1.492; //4 * 1.492 = 5.968MPH
+
+    return(windSpeed);
+}
+
+//Read the wind direction sensor, return heading in degrees
+int get_wind_direction()
+{
+    unsigned int adc;
+
+    adc = analogRead(WDIR); // get the current reading from the sensor
+    winddirRaw = adc;       // Save the ADC value for troubleshooting.
+
+    // The following table is ADC readings for the wind direction sensor output, sorted from low to high.
+    // Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
+    // Note that these are not in compass degree order! See Weather Meters datasheet for more information.
+
+    if (adc < 380) { strWindDir = "ESE"; return (113); }    // ESE
+    if (adc < 393) { strWindDir = "ENE"; return  (68); }    // ENE
+    if (adc < 414) { strWindDir = "E";   return  (90); }    // E
+    if (adc < 456) { strWindDir = "SSE"; return (158); }    // SSE
+    if (adc < 508) { strWindDir = "SE";  return (135); }    //  SE
+    if (adc < 551) { strWindDir = "SSW"; return (203); }    // SSW
+    if (adc < 615) { strWindDir = "S";   return (180); }    // S
+    if (adc < 680) { strWindDir = "NNE"; return  (23); }    // NNE
+    if (adc < 746) { strWindDir = "NE";  return  (45); }    //  NE
+    if (adc < 801) { strWindDir = "WSW"; return (248); }    // WSW
+    if (adc < 833) { strWindDir = "SW";  return (225); }    //  SW
+    if (adc < 878) { strWindDir = "NNW"; return (338); }    // NNW
+    if (adc < 913) { strWindDir = "N";   return   (0); }    // N
+    if (adc < 940) { strWindDir = "WNW"; return (293); }    // WNW
+    if (adc < 967) { strWindDir = "NW";  return (315); }    //  NW
+    if (adc < 990) { strWindDir = "W";   return (270); }    // W
+    if (adc < 1010) {strWindDir = "W";   return (270); }    // W - Sometimes 270 returns higher than 990.
+    strWindDir = "ERR";
+    return (-10); // error, disconnected?
+}
+
+
+/*-------- NTP code ----------*/
+/*****************************************************************************
+  _______   _____   __  __   ______         __    _   _   _______   _____
+ |__   __| |_   _| |  \/  | |  ____|       / /   | \ | | |__   __| |  __ \
+    | |      | |   | \  / | | |__         / /    |  \| |    | |    | |__) |
+    | |      | |   | |\/| | |  __|       / /     | . ` |    | |    |  ___/
+    | |     _| |_  | |  | | | |____     / /      | |\  |    | |    | |
+    |_|    |_____| |_|  |_| |______|   /_/       |_| \_|    |_|    |_|
+
+ *****************************************************************************
+ */
+
+time_t getNtpTime()
+{
+  // We don't want to send anything during power save times, for now.
+  if (powerSave) {
+    Serial.println("   NTP requested, but NO PACKET SENT due to Power Save mode.");
+    return 0;
+  }
+  if (disableNTP) {
+    Serial.println("   NTP requested, but disabled via \"const bool disableNTP = true;\"");
+    return 0;
+  }
+  Udp.begin(NTPlocalPort); // Moved from setup(), to try stop()ing udp every time.
+  while (Udp.parsePacket() > 0) ; // discard any previously received packets
+  Serial.println(F("Transmit NTP Request"));
+  msNTPrequest = millis();
+  sendNTPpacket(timeServer);
+  //sendNTPpacket(timeServerIP);
+  uint32_t beginWait = millis();
+  // Normal timeout is 1500, but I can't pause that long or weather gets messed up.
+  // FIXME: see about making this check every loop(), for 1500ms before failing, instead of looping here.
+  while (millis() - beginWait < 800) {
+    int size = Udp.parsePacket();
+    if (size >= NTP_PACKET_SIZE) {
+      Serial.print(F("   UDP response received "));
+      Serial.print(millis() - msNTPrequest);
+      Serial.println(F("ms later. Parsing for NTP"));
+      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
+      unsigned long secsSince1900;
+      // convert four bytes starting at location 40 to a long integer
+      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
+      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
+      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
+      secsSince1900 |= (unsigned long)packetBuffer[43];
+      Udp.stop();
+      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
+    }
+  }
+  Serial.print(F("After waiting "));
+  Serial.print(millis() - msNTPrequest);
+  Serial.println(F("ms, No NTP Response :-("));
+  Udp.stop();
+  return 0; // return 0 if unable to get the time
+}
+
+// send an NTP request to the time server at the given address (DNS lookup version, see IPAddress() version below)
+unsigned long sendNTPpacket(char* address)
+{
+  // set all bytes in the buffer to 0
+  memset(packetBuffer, 0, NTP_PACKET_SIZE);
+  // Initialize values needed to form NTP request
+  // (see URL above for details on the packets)
+  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
+  packetBuffer[1] = 0;     // Stratum, or type of clock
+  packetBuffer[2] = 6;     // Polling Interval
+  packetBuffer[3] = 0xEC;  // Peer Clock Precision
+  // 8 bytes of zero for Root Delay & Root Dispersion
+  packetBuffer[12]  = 49;
+  packetBuffer[13]  = 0x4E;
+  packetBuffer[14]  = 49;
+  packetBuffer[15]  = 52;
+
+  // all NTP fields have been given values, now
+  // you can send a packet requesting a timestamp:
+  Serial.print(F("   About to send UDP NTP packet from char* ... "));
+  //wdt_disable();
+  Udp.beginPacket(address, 123); //NTP requests are to port 123
+  Udp.write(packetBuffer, NTP_PACKET_SIZE);
+  Udp.endPacket();
+  //wdt_enable(WDTO_8S);
+  Serial.println(F("sent (probably)."));
+}
+
+// send an NTP request to the time server at the given address (IP, no-dns version)
+unsigned long sendNTPpacket(IPAddress address)
+{
+  // set all bytes in the buffer to 0
+  memset(packetBuffer, 0, NTP_PACKET_SIZE);
+  // Initialize values needed to form NTP request
+  // (see URL above for details on the packets)
+  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
+  packetBuffer[1] = 0;     // Stratum, or type of clock
+  packetBuffer[2] = 6;     // Polling Interval
+  packetBuffer[3] = 0xEC;  // Peer Clock Precision
+  // 8 bytes of zero for Root Delay & Root Dispersion
+  packetBuffer[12]  = 49;
+  packetBuffer[13]  = 0x4E;
+  packetBuffer[14]  = 49;
+  packetBuffer[15]  = 52;
+
+  // all NTP fields have been given values, now
+  // you can send a packet requesting a timestamp:
+  Udp.beginPacket(address, 123); //NTP requests are to port 123
+  Udp.write(packetBuffer, NTP_PACKET_SIZE);
+  Udp.endPacket();
+  Serial.println(F("  UDP NTP packet sent from IPAddress (probably)."));
+}
+
+
+void ShowSockStatus()
+{
+  // CLOSED 0x00 // LISTEN 0x14 // ESTABLISHED 0x17 // FIN_WAIT 0x18 // CLOSE_WAIT 0x1C // UDP 0x22
+  for (int i = 0; i < MAX_SOCK_NUM; i++) {
+    Serial.print("Socket#");
+    Serial.print(i);
+    uint8_t s = W5100.readSnSR(i);
+    ethSockStatus[i] = s;
+    Serial.print(":0x");
+    Serial.print(s,16);
+    Serial.print(" ");
+    Serial.print(W5100.readSnPORT(i));
+    Serial.print(" D:");
+    uint8_t dip[4];
+    W5100.readSnDIPR(i, dip);
+    for (int j=0; j<4; j++) {
+      Serial.print(dip[j],10);
+      if (j<3) Serial.print(".");
+    }
+    Serial.print("(");
+    Serial.print(W5100.readSnDPORT(i));
+    Serial.println(")");
+  }
+}
+
+
+int freeRam () {
+  extern int __heap_start, *__brkval;
+  int v;
+  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
+}
+
+void printDigits(int digits){
+  // utility function adds leading 0
+  if(digits < 10)
+    Serial.print('0');
+  Serial.print(digits);
+}
+
+//Utility function for SD cards
+/*
+ * void printDirectory(File dir, int numTabs) {
+  // Begin at the start of the directory
+  dir.rewindDirectory();
+
+  while(true) {
+     File entry =  dir.openNextFile();
+     if (! entry) {
+       // no more files
+       //Serial.println("**nomorefiles**");
+       break;
+     }
+     for (uint8_t i=0; i<numTabs; i++) {
+       Serial.print('\t');   // we'll have a nice indentation
+     }
+     // Print the 8.3 name
+     Serial.print(entry.name());
+     // Recurse for directories, otherwise print the file size
+     if (entry.isDirectory()) {
+       Serial.println("/");
+       printDirectory(entry, numTabs+1);
+     } else {
+       // files have sizes, directories do not
+       Serial.print("\t\t");
+       Serial.println(entry.size(), DEC);
+     }
+     entry.close();
+   }
+}
+*/
+
+byte uploadWeather()
+{
+    if (powerSave) {
+	  Serial.println("UploadWeather() called, but NO DATA SENT because of Power Save mode.");
+	  return 50;
+	}
+
+	// Connect to CSS website, do a PUT with weather values. Should be called once for every minute of weather data.
+    logSome(F("  uploadWeather called, building string. Bytes free: "));
+    logOneLine(freeRam());
+    byte uploadStatus = 90; //90 = haven't tried stopping the client yet.
+
+    // Preemptively close any open connections, to keep sockets available.
+    client.stop();
+    while (client.available()) { //read return from socket
+      Serial.write(client.read());
+    }
+
+    uploadStatus = 100; //100 = stopped client, but haven't tried connecting.
+
+    String strPut = makeUploadWeatherPut(getWeatherString());
+
+    //Serial.print(F(" before strPut, after strPut: "));
+    //Serial.println(freeRam());
+
+    char charPut[300];
+    for (int i = 0; i < 300; i++) {
+      charPut[i] = 'x';
+    }
+    charPut[299] = '\0';
+    int strPutLength = strPut.length() + 1;
+    Serial.print("strPut len: ");
+    Serial.println(strPutLength);
+    if (strPutLength > 298) strPutLength = 298;
+    strPut.toCharArray(charPut, strPutLength);
+
+    client.setTimeout(600);
+    int clientConnectStatus;
+    clientConnectStatus = client.connect(server, 80);
+    if (clientConnectStatus) {
+      logSome(F("Ether client connected for uploadWeather. Mem: "));
+      logSome(freeRam());
+      logSome(", connect status: ");
+      logOneLine(clientConnectStatus);
+      ethStopped = false;
+
+      // Make an HTTP request:
+    //logSome(strPut);
+    //client.print(strPut);  //Single print so hopefully it goes out in one packet.
+      Serial.write(charPut, strPutLength); //for debugging
+      client.write(charPut, strPutLength); //Better chance of a single packet by using a char[].
+      ethLastMillis = millis();
+      uploadStatus = 0;
+      ethConnFails = 0;
+      ethTimeouts = 0;
+
+    } else {
+      // if you didn't get a connection to the server:
+      logSome(F("connection failed, status: "));
+      logOneLine(clientConnectStatus);
+      ethLastFailureCode = clientConnectStatus;
+      client.stop();
+      ethStopped = true;
+      ethConnFails++;
+      uploadStatus = 200; // connection failed
+    }
+
+    logSome(F("  uploadWeather() finished, free mem: "));
+    logOneLine(freeRam());
+    return uploadStatus;
+}
+
+// Test with building strings, to use for the website request.
+String getWeatherString() {
+    String weatherString = "";
+    int tempc = RTC.temperature();
+
+    // 1: time
+    if (hour() < 10) weatherString += String('0');
+    weatherString += String(hour());
+    weatherString += ":";
+    if (minute() < 10) weatherString += String('0');
+    weatherString += String(minute());
+
+    // 2: date
+    weatherString += String(charComma);
+    weatherString += String(month());
+    weatherString += String(("/"));
+    weatherString += String(day());
+    weatherString += String(("/"));
+    weatherString += String(year());
+
+    // 3: Wind speed, mph, 1 minute average
+    weatherString += String(charComma);
+    if (windSpeedAvg < 9.5) weatherString += String('0');
+    weatherString += String(windSpeedAvg, 2);
+
+    // 4: wind speed, mph, 5 minute max (gust)
+    weatherString += String(charComma);
+    if (windgustmph_5m < 9.5) weatherString += String('0');
+    weatherString += String(windgustmph_5m, 1);
+
+    // 5: wind direction, 1 minute average
+    weatherString += String(charComma);
+    if ((winddir < 10 ) and (winddir >= 0)) weatherString += String("0"); //pad with zeros to keep formatting nicer
+    if ((winddir < 100) and (winddir >= 0)) weatherString += String("0"); //pad with zeros to keep formatting nicer
+    weatherString += String(winddir);
+    //weatherString += String(",windgustdir=");
+    //weatherString += String(windgustdir);
+
+    // 6: temperature, F, outside, instant
+    weatherString += String(charComma);
+    weatherString += String(bme280a.readTempF(), 2);
+// All this was before the BME280
+//    if (tempf < -1000) {
+//      weatherString += String((tempc * 0.45) + 32, 1);
+//    } else {
+//      weatherString += String(tempf, 0);
+//    }
+
+    // 7: humidity, %, outside, instant from bme280a
+    weatherString += String(charComma);
+    weatherString += String(bme280a.readFloatHumidity(), 0);
+// All this was before the BME280
+//    if (humidity < 200) {
+//      //weatherString += String(humidity, 1);
+//    } else {
+//      weatherString += "0";
+//    }
+
+    // 8: Barometric pressure, hPa, outside, instant from bme280a
+    weatherString += String(charComma);
+    weatherString += String(bme280a.readFloatPressure() / 100.0, 2);
+// All this wasbefore the BME280
+//    if (pressure > 0) {
+//      weatherString += String(pressure / 100.0, 1);
+//    } else {
+//      weatherString += "0";
+//    }
+
+    // 9: Barometric pressure delta from previous reading
+    weatherString += String(charComma);
+    //if (pressure >= oldPressure) weatherString += String("+"); //can't have spaces in the URL, but using the + makes columns line up nicer.
+    //weatherString += String((pressure - oldPressure) / 100.0, 2);
+    weatherString += "0";
+
+    // 10: rain so far today
+    //weatherString += String(",rainin=");
+    //weatherString += String(rainin, 2);
+    weatherString += String(charComma);
+//    weatherString += String(0.0, 2);
+    weatherString += String(wxOwner);
+
+    // 11: rain long term? Maybe it's supposed to be last 24h, but it's always zero.
+    //weatherString += String(",dailyrainin=");
+    //weatherString += String(dailyrainin, 2);
+    weatherString += String(charComma);
+//    weatherString += String(0.0, 2);
+    weatherString += String(wxVersion);
+
+    // 11: wind speed, mph, instant (instead of 24h rain)
+    //weatherString += String(charComma);
+    //if (windspeedmph < 10) weatherString += String('0');
+    //weatherString += String(windspeedmph, 1);
+
+    // 12: temperature in the enclosure or 2nd sensor if we get one. In Celcius for Jimmy.
+    weatherString += String(charComma);
+    if (tempc) {
+      weatherString += String(tempc / 4.0, 2); //Dja
+    } else {
+      weatherString += "0";
+    }
+
+    // 13: Current on ina219 sensor A (raw battery @ 12v maybe?)
+    weatherString += String(charComma);
+    weatherString += String(ina219a_current, 2);
+    weatherString += String("mA");
+
+    // 14: Voltage on ina219 sensor A (raw battery @ 12v maybe?)
+    weatherString += String(charComma);
+    weatherString += String(ina219a_volts, 2);
+    weatherString += String("v");
+    //weatherString += String(batt_lvl, 2);
+
+    // 15: Power on ina219 sensor A (watts battery)
+    weatherString += String(charComma);
+    weatherString += String(ina219a_volts * ina219a_current / 1000, 2);
+    weatherString += String("w");
+
+
+    // 16: Current on ina219 sensor B (after buck to 5v maybe?)
+    weatherString += String(charComma);
+    weatherString += String(ina219b_current, 2);
+    weatherString += String("mA");
+
+    // 17: Voltage on ina219 sensor A (after buck to 5v maybe?)
+    weatherString += String(charComma);
+    weatherString += String(ina219b_volts, 2);
+    weatherString += String("v");
+    //weatherString += String(batt_lvl, 2);
+
+    // 18: Power on ina219 sensor A (watts battery)
+    weatherString += String(charComma);
+    weatherString += String(ina219b_volts * ina219b_current / 1000, 2);
+    weatherString += String("w");
+
+
+
+    // 19: light level, referenced to voltage I think
+    weatherString += String(charComma);
+//    weatherString += String(light_lvl, 2);
+    weatherString += "0";
+
+    // 20: run time in days.HH:MM:SS
+    weatherString += String(charComma);
+    weatherString += String(days);
+    weatherString += String(".");
+    if (hours < 10) weatherString += String('0');
+    weatherString += String(hours);
+    weatherString += String(":");
+    if (minutes < 10) weatherString += String('0');
+    weatherString += String(minutes);
+    weatherString += String(":");
+    if (seconds < 10) weatherString += String('0');
+    weatherString += String(seconds);
+
+    /* 18: loop counter. For diagnostics, mostly useless.
+    weatherString += String(charComma);
+    weatherString += String(loopCounter);
+    weatherString += String(charComma);
+    weatherString += String(loopCounter - loopDelta);
+    loopDelta = loopCounter; */
+
+    // 18: print free memory? Interesting...
+//    weatherString += String(charComma);
+//    weatherString += String(freeRam());
+
+    // 21: print raw wind direction ADC reading, to see why 270 degree sometimes comes back as "invalid"
+    if (true) {
+      weatherString += String(charComma);
+      weatherString += String("wd=");
+      weatherString += String(winddirRaw);
+    }
+    if (true) {
+      weatherString += String(charComma);
+      if (strWindDir.length() < 3) weatherString += String("_");
+      if (strWindDir.length() < 2) weatherString += String("_");
+      weatherString += strWindDir;
+    }
+
+    // 22+: Assorted info and error values
+    // Tack on a ,R if we've rebooted to make it easier to spot them
+    if(justRestarted) {
+      weatherString += ",R";
+    }
+
+    // add socket status as 8 hex chars
+    bool reportSockets = false;
+    for (int i = 0; i < MAX_SOCK_NUM; i++) {
+      if (ethSockStatus[i] > 0) reportSockets = true;
+    }
+    if (reportSockets) {
+      weatherString += String(charComma);
+      for (int i = 0; i < MAX_SOCK_NUM; i++) {
+        if (ethSockStatus[i] < 17) weatherString += String("0");
+        weatherString += String(ethSockStatus[i], 16);
+      }
+    }
+
+    // If there have been failures, tack them on:
+    if (ethConnFails) {
+      weatherString += String(F(",EthConnFails="));
+      weatherString += String(ethConnFails);
+    }
+    if (ethTimeouts) {
+      weatherString += String(F(",Timeouts="));
+      weatherString += String(ethTimeouts);
+    }
+    if (ethLastFailureCode) {
+      weatherString += String(F(",failCode="));
+      weatherString += String(ethLastFailureCode);
+    }
+    if(reportWatchdog) {
+      weatherString += ",W-";
+      weatherString += String(year(reportWatchdog));
+      weatherString += String(("/"));
+      weatherString += String(month(reportWatchdog));
+      weatherString += String(("/"));
+      weatherString += String(day(reportWatchdog));
+      weatherString += String("@");
+      weatherString += String(hour(reportWatchdog));
+      weatherString += String((":"));
+      weatherString += String(minute(reportWatchdog));
+    }
+
+    //Serial.println(weatherString);
+	weatherString.replace(" ", "");
+    return weatherString;
+}
+
+//Prints the various variables directly to the port
+//I don't like the way this function is written but Arduino doesn't support floats under sprintf
+void printWeather()
+{
+    //calcWeather(); //Go calc all the various sensors; except now call it directly from loop()
+    timeStatus();             //check the time status now to force a sync if needed.
+
+    // 1: time
+    printDigits(hour());
+    Serial.print((":"));
+    printDigits(minute());
+
+    // 2: date
+    Serial.print(charComma);
+    Serial.print(month());
+    Serial.print(("/"));
+    Serial.print(day());
+    Serial.print(("/"));
+    Serial.print(year());
+
+    // 3: Wind speed, mph, 1 minute average
+    Serial.print(charComma);
+    if (windSpeedAvg < 10) Serial.print('0');
+    Serial.print(windSpeedAvg, 2);
+
+    // 4: wind speed, mph, 5 minute max (gust)
+    Serial.print(charComma);
+    if (windgustmph_5m < 10) Serial.print('0');
+    Serial.print(windgustmph_5m, 1);
+
+    // 5: wind direction, 1 minute average
+    Serial.print(charComma);
+    if (winddir == 0 ) Serial.print(" ");
+    if (winddir < 100) Serial.print(" ");
+    Serial.print(winddir);
+    //Serial.print(",windgustdir=");
+    //Serial.print(windgustdir);
+    //Serial.print(F(",wAvg="));
+    //Serial.print(windspdmph_avg2m, 2);
+
+    // 6: temperature, F, outside, instant
+    Serial.print(charComma);
+    Serial.print(tempf, 1);
+
+    // 7: humidity, %, outside, instant
+    Serial.print(charComma);
+    Serial.print(humidity, 1);
+
+    // 8: Barometric pressure, hPa, outside, instant
+    Serial.print(charComma);
+    Serial.print(pressure / 100.0, 1);
+
+    // 9: Barometric pressure delta from previous reading
+    Serial.print(charComma);
+    if (pressure >= oldPressure) Serial.print(" ");
+    Serial.print((pressure - oldPressure) / 100.0, 2);
+
+    // 10: rain so far today
+    //Serial.print(",rainin=");
+    //Serial.print(rainin, 2);
+    Serial.print(charComma);
+    Serial.print(0.0, 2);
+
+    // 11: rain long term? Maybe it's supposed to be last 24h, but it's always zero.
+    //Serial.print(",dailyrainin=");
+    //Serial.print(dailyrainin, 2);
+    //Serial.print(",");
+    //Serial.print(0.0, 2);
+
+    // 11: wind speed, mph, instant (instead of 24h rain)
+    Serial.print((","));
+    if (windspeedmph < 10) Serial.print('0');
+    Serial.print(windspeedmph, 1);
+
+    // 12: temperature in the enclosure or 2nd sensor if we get one
+    Serial.print(charComma);
+    //Serial.print(tempf, 1); //(FIXME: same as ext sensor since we only have one)
+    Serial.print(RTC.temperature() / 4);
+
+    // 13: current in mA
+    //(FIXME: same as ext sensor since we only have one)
+    Serial.print(charComma);
+    Serial.print(ina219a_current, 2);
+    Serial.print("mA");
+
+    // 14: voltage onboard, ~5v source
+    Serial.print(charComma);
+    Serial.print(batt_lvl, 2);
+
+    // 15: voltage battery, possibly inferred but same as 14 for now.
+    Serial.print(charComma);
+    Serial.print(ina219a_volts,2);
+    //Serial.print(sla_30sec, 2);
+
+    // 16: light level, referenced to voltage I think
+    Serial.print(charComma);
+    Serial.print(light_lvl, 2);
+
+    // 17: run time in days.HH:MM:SS
+    Serial.print(charComma);
+    Serial.print(days);
+    Serial.print(".");
+    printDigits(hours);
+    Serial.print(":");
+    printDigits(minutes);
+    Serial.print(":");
+    printDigits(seconds);
+
+    // 18: Print the RTC date & time. For debugging.
+    time_t rtcTime = RTC.get();
+    Serial.print(charComma);
+    Serial.print("rtc:");
+    Serial.print(year(rtcTime));
+    Serial.print("/");
+    Serial.print(month(rtcTime));
+    Serial.print("/");
+    Serial.print(day(rtcTime));
+    Serial.print(charComma);
+    Serial.print(hour(rtcTime));
+    Serial.print(":");
+    Serial.print(minute(rtcTime));
+    Serial.print(":");
+    Serial.print(second(rtcTime));
+
+
+/*
+    // loop counter. For diagnostics, mostly useless.
+    Serial.print(charComma);
+    Serial.print(loopCounter);
+    Serial.print(charComma);
+    Serial.print(loopCounter - loopDelta);
+    loopDelta = loopCounter;
+
+    // Print the raw 12v input on A12/SLA_BATT_LVL
+    Serial.print(charComma);
+    Serial.print(sla_raw);
+    Serial.print(charComma);
+    Serial.print(sla_lvl, 2);
+    Serial.print(charComma);
+    Serial.print(sla_30sec, 2);
+
+    //Serial.print(charComma);
+    //Serial.print(F("__DATE__ "));    //C++ #DEFINE that expands to the current compile date
+    //Serial.print(F("__TIME__"));     //C++ #DEFINE that expands to the current compile time
+    //Serial.print(String(compile_date));
+*/
+
+    // 19: print free memory? Interesting...
+    Serial.print(charComma);
+    Serial.print(freeRam());
+
+    // 20: print raw ADC reading from wind direction, to troubleshooting why 270 degrees sometimes reads invalid
+    Serial.print(charComma);
+    Serial.print(winddirRaw);
+
+    // 21: print ina219a's voltage, current
+    Serial.print(charComma);
+    Serial.print(ina219a_volts);
+    Serial.print(charComma);
+    Serial.print(ina219a_current);
+
+    // NEWLINE:
+    Serial.println();
+
+}
+


Added example AlarmPolling that tests the new method checkIfAlarm(byte Alarm, bool clearflag)

error: pathspec 'examples/AlarmPolling/AlarmPolling.ino' did not match any file(s) known to git

--- examples/AlarmPolling/AlarmPolling.ino
+++ examples/AlarmPolling/AlarmPolling.ino
@@ -0,0 +1,50 @@
+/*
+AlarmPolilng.ino
+Jacob Nuernberg
+08/22
+
+Example on using DS3231 alarms with polling and test checkIfAlarm()
+
+Tested on:
+- Arduino nano
+
+*/
+
+#include <DS3231.h>
+#include <Wire.h>
+
+// Setup clock
+DS3231 myRTC;
+
+
+void setup() {
+    // Begin I2C communication
+    Wire.begin();
+
+    // Setup alarm one to fire every second
+    myRTC.turnOffAlarm(1);
+    myRTC.setA1Time(0, 0, 0, 0, 0b01111111, false, false, false);
+    myRTC.turnOnAlarm(1);
+    myRTC.checkIfAlarm(1);
+
+    // Use builtin LED to blink
+    pinMode(LED_BUILTIN, OUTPUT);
+    digitalWrite(LED_BUILTIN, HIGH);
+}
+
+void loop() {
+    // static variable to keep track of LED on/off state
+    static byte state = false;
+
+    // if alarm went of, do alarm stuff
+    // first call to checkIFAlarm does not clear alarm flag
+    if (myRTC.checkIfAlarm(1, false)) {
+        state = ~state;
+        digitalWrite(LED_BUILTIN, state);
+        // Clear alarm state
+        myRTC.checkIfAlarm(1, true);
+    }
+
+    // Loop delay to emulate other running code
+    delay(10);
+}


added two universe example

added two universe example

Example has not been tested

error: pathspec 'examples/TwoUniverses/TwoUniverses.ino' did not match any file(s) known to git

--- examples/TwoUniverses/TwoUniverses.ino
+++ examples/TwoUniverses/TwoUniverses.ino
@@ -0,0 +1,117 @@
+/**************************************************************************/
+/*!
+    @file     2Universes.ino
+    @author   Claude Heintz
+    @license  BSD (see LXDMXEthernet.h)
+    @copyright 2015-2016 by Claude Heintz All Rights Reserved
+
+    Example using LXDMXEthernet_Library for output of Art-Net or E1.31 sACN from
+    EEthernet connection to DMX
+
+    Art-Net(TM) Designed by and Copyright Artistic Licence (UK) Ltd
+    sACN E 1.31 is a public standard published by the PLASA technical standards program
+
+    Note:  2 Universes may excede the memory available on ATmega328 boards
+           such as Arduino Uno.
+
+    @section  HISTORY
+
+    v1.00 - First release
+*/
+/**************************************************************************/
+
+#include <Ethernet.h>
+#include <EthernetUDP.h>
+#include <LXDMXEthernet.h>
+#include <LXArtNet.h>
+#include <LXSACN.h>
+
+//  Make choices here about protocol ( set to 1 to activate option )
+
+#define USE_DHCP 0
+#define USE_SACN 0
+
+// dmx protocol interface for parsing packets (created in setup)
+LXDMXEthernet* interface;
+LXDMXEthernet* interfaceUniverse2;
+
+// buffer large enough to contain incoming packet
+uint8_t packetBuffer[SACN_BUFFER_MAX];
+
+//*********************** globals ***********************
+
+// network addresses
+// IP address is defined assuming that the Arduino is connected to
+// a Mac without a router and a self assigned ip
+byte mac[] = { 0x00, 0x08, 0xDC, 0x4C, 0x29, 0x7E }; //00:08:DC Wiznet
+IPAddress ip(169,254,100,100);
+IPAddress gateway(169,254,1,1);
+IPAddress subnet_mask(255,255,0,0);
+
+// An EthernetUDP instance to let us send and receive packets over UDP
+EthernetUDP eUDP;
+
+// sACN uses multicast, Art-Net uses broadcast. Both can be set to unicast (use_multicast = 0)
+uint8_t use_multicast = 0;		//only single multicast address is supported in this example, will not work for 2 universes
+
+
+//*********************** setup ***********************
+
+void setup() {
+  if ( USE_DHCP ) {                                          // Initialize Ethernet
+    Ethernet.begin(mac);                                     // DHCP
+  } else {
+  		Ethernet.begin(mac, ip, gateway, gateway, subnet_mask);   // Static
+  }
+
+  if ( USE_SACN ) {                       // Initialize Interface (defaults to first universe)
+    interface = new LXSACN(&packetBuffer[0]);
+    interfaceUniverse2 = new LXSACN(&packetBuffer[0]);
+    interfaceUniverse2->setUniverse(2);	         // for different universe, change this line and the multicast address below
+  } else {
+    interface = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask(), &packetBuffer[0]);
+    interfaceUniverse2 = new LXArtNet(Ethernet.localIP(), Ethernet.subnetMask(), &packetBuffer[0]);
+    ((LXArtNet*)interfaceUniverse2)->setSubnetUniverse(0, 1);  //for different subnet/universe, change this line
+    use_multicast = 0;
+  }
+
+  if ( use_multicast ) {                  // Start listening for UDP on port
+    eUDP.beginMulticast(IPAddress(239,255,0,1), interface->dmxPort());
+  } else {
+    eUDP.begin(interface->dmxPort());
+  }
+
+  //announce presence via Art-Net Poll Reply (only advertises one universe)
+  if ( ! USE_DHCP ) {
+     ((LXArtNet*)interface)->send_art_poll_reply(eUDP);
+  }
+
+}
+
+/************************************************************************
+
+  The main loop checks for and reads packets from  UDP socket
+  connection.  readDMXPacketContents() returns RESULT_DMX_RECEIVED when a DMX packet is received.
+  If the first universe does not match, try the second
+
+*************************************************************************/
+
+void loop() {
+  uint16_t packetSize = eUDP.parsePacket();
+  if ( packetSize ) {
+  	  packetSize = eUDP.read(packetBuffer, SACN_BUFFER_MAX);
+	  uint8_t read_result = interface->readDMXPacketContents(eUDP, packetSize);
+	  uint8_t read_result2 = 0;
+
+	  if ( read_result == RESULT_DMX_RECEIVED ) {
+	     // edge case test universe 1, slot 1
+	     analogWrite(12,2*interface->getSlot(1));
+	  } else if ( read_result == RESULT_NONE ) {				// if not good dmx first universe (or art poll), try 2nd
+	     read_result2 = interfaceUniverse2->readDMXPacketContents(eUDP, packetSize);
+	     if ( read_result2 == RESULT_DMX_RECEIVED ) {
+	     		// edge case test 2nd universe, slot 512
+		  		analogWrite(14,2*interfaceUniverse2->getSlot(512));
+	     }
+	  }
+	}
+}


CambiosBluetooth

CambiosBluetooth

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/nverdicchio/soa-2017-bicicleta-inteligente.git/'


Initial debounce code working

Initial debounce code working

--- messenger.ino
+++ messenger.ino
@@ -0,0 +1,140 @@
+// This is a demonstration on how to use an input device to trigger changes on your neo pixels.
+// You should wire a momentary push button to connect from ground to a digital IO pin.  When you
+// press the button it will change to a new pixel animation.  Note that you need to press the
+// button once to start the first animation!
+
+#include <Adafruit_NeoPixel.h>
+#include <Bounce2.h>
+
+// Button debouncing see: https://blog.adafruit.com/2009/10/20/example-code-for-multi-button-checker-with-debouncing/
+#define DEBOUNCE 10  // button debouncer, how many ms to debounce, 5+ ms is usually plenty
+
+// Digital IO pin connected to the button.  This will be
+// driven with a pull-up resistor so the switch should
+// pull the pin to ground momentarily.  On a high -> low
+// transition the button press logic will execute.
+
+Bounce bDIT = Bounce();
+Bounce bDAH = Bounce();
+Bounce bBRK = Bounce();
+Bounce bSND = Bounce();
+
+// Buttons for dit and dah should be self-explanitory
+//  brk is used for character break (one tap) word break (two taps)
+//  or sentence break (three taps).
+#define BUTTON_PIN_DIT   12
+#define BUTTON_PIN_DAH   6
+#define BUTTON_PIN_BRK   9
+#define BUTTON_PIN_SND   10
+
+#define PIXEL_PIN    8    // Digital IO pin connected to the NeoPixels.
+
+Adafruit_NeoPixel strip = Adafruit_NeoPixel(1, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
+
+// Arrays for the Morse word and the message
+int morse[6];
+int mi = 0;
+int message[256];
+int msi = 0;
+
+void setup() {
+  pinMode(BUTTON_PIN_DIT, INPUT_PULLUP);
+  pinMode(BUTTON_PIN_DAH, INPUT_PULLUP);
+  pinMode(BUTTON_PIN_BRK, INPUT_PULLUP);
+  pinMode(BUTTON_PIN_SND, INPUT_PULLUP);
+
+  // After setting up the button, setup the object
+  bDIT.attach(BUTTON_PIN_DIT);
+  bDIT.interval(DEBOUNCE);
+  bDAH.attach(BUTTON_PIN_DAH);
+  bDAH.interval(DEBOUNCE);
+  bBRK.attach(BUTTON_PIN_BRK);
+  bBRK.interval(DEBOUNCE);
+  bSND.attach(BUTTON_PIN_SND);
+  bSND.interval(DEBOUNCE);
+
+  strip.begin();
+  strip.show(); // Initialize all pixels to 'off'
+}
+
+void loop() {
+  // Update teh bouncer states
+  bDIT.update();
+  bDAH.update();
+  bBRK.update();
+  bSND.update();
+
+  if (bDIT.fell()) {
+    //morse[mi] = 1;
+    //mi++;
+    Serial.println("dit");
+  } else if (bDAH.fell()) {
+    //morse[mi] = 3;
+    //mi++;
+    Serial.println("dah");
+  } else if (bBRK.fell()) {
+    Serial.println("brk");
+    printMorse();
+    clearMorse();
+    //if (lastPIN == BUTTON_PIN_BRK) {
+      //addCharacter(' ');
+    //} else {
+      //addCharacter(getMorse());
+      //clearMorse();
+    //}
+  } else if (bSND.fell()) {
+    Serial.println("snd");
+      //printMessage();
+      //clearMessage();
+  }
+
+}
+
+void clearMorse() {
+  for(int ii=0; ii<6; ii++) {
+    morse[ii] = 0;
+  }
+  mi = 0;
+}
+
+// Convert the morse code to a character
+void printMorse() {
+  char mm[6];
+  for (int mi=0; mi<6; mi++) {
+    if (morse[mi] == 1) {
+      Serial.print(".");
+    } else if (morse[mi] == 3) {
+      Serial.print("-");
+    } else {
+      Serial.print("x");
+    }
+  }
+  Serial.println("/");
+  return mm;
+}
+
+
+void setColor(int i) {
+  switch(i){
+    case 0: colorWipe(strip.Color(0, 0, 0), 50);  // Black/off
+            break;
+    case 1: colorWipe(strip.Color(255, 0, 0), 50);      // Red
+            break;
+    case 2: colorWipe(strip.Color(0, 255, 0), 50);  // Green
+            break;
+    case 3: colorWipe(strip.Color(0, 0, 255), 50);  // Blue
+            break;
+    case 4: colorWipe(strip.Color(127, 127, 127), 50);  // White
+            break;
+  }
+}
+
+// Fill the dots one after the other with a color
+void colorWipe(uint32_t c, uint8_t wait) {
+  for(uint16_t i=0; i<strip.numPixels(); i++) {
+    strip.setPixelColor(i, c);
+    strip.show();
+    delay(wait);
+  }
+}
+


Update mainscript with include comments

Update mainscript with include comments

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/onordbo/mood-cloud.git/'


ok, we better start with a real start signal …

remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/trinamic/T-Bone.git/'