#include <SPI.h> #include <WiFi101.h> #include <WiFiUdp.h> #include <Time.h> #include <Timezone.h> #define DEBUG 1 #if DEBUG #define LOG(X) Serial.print(X); #define LOGLN(X) Serial.println(X); #else #define LOG(X) #define LOGLN(X) #endif // High voltage system enable #define HV_EN 6 // Wifi control #define WIFI_SS 9 #define WIFI_IRQ 16 #define WIFI_RESET 15 #define WIFI_EN 14 // Shift register control #define N_SER 19 #define N_SCK 2 #define N_RCK 4 #define N_SCL 3 // How long between NTP re-sync's (millis) #define NTP_SYNC_INTERVAL 600000 // How long between NTP retrys (millis) #define NTP_RESEND_INTERVAL 2000 // How long to wait for wifi to connect #define WIFI_RECONNECT_INTERVAL 10000 // How long between display updates (millis) #define DISPLAY_UPDATE_INTERVAL 100 #define TIME_SERVER "time.nist.gov" #define NTP_PORT 123 #define LOCAL_PORT 2390 // Wifi details #define MAX_SSID_LEN 64 boolean ssidLoaded = false; uint8_t EEMEM_SSID_LEN EEMEM = 0; uint8_t EEMEM_PASS_LEN EEMEM = 0; char EEMEM_SSID[MAX_SSID_LEN] EEMEM = "your_ssid_here"; char EEMEM_PASS[MAX_SSID_LEN] EEMEM = "your_password_here"; char ssid[MAX_SSID_LEN] = {0}; char pass[MAX_SSID_LEN] = {0}; // WIFI101 internals WiFiUDP Udp; int wifiStatus = WL_IDLE_STATUS; unsigned long lastWifiBeginTime = WIFI_RECONNECT_INTERVAL; const int NTP_PACKET_SIZE = 48; byte packetBuffer[NTP_PACKET_SIZE]; // Timezone rules TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; //UTC - 4 hours TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; //UTC - 5 hours Timezone usEastern(usEDT, usEST); // State unsigned long millis_offset = 0; unsigned long lastNtpPacketSent = NTP_RESEND_INTERVAL; unsigned long lastNtpSyncSuccess = NTP_SYNC_INTERVAL; unsigned long lastDisplayUpdate = 0; /** * Set pinmodes, initialize WiFi chipset, enable the high voltage system */ void setup() { // Initialize serial Serial.begin(9600); //// Set up pins // High voltage shutdown pinMode(HV_EN, OUTPUT); // Wifi pinMode(WIFI_SS, OUTPUT); pinMode(WIFI_IRQ, OUTPUT); pinMode(WIFI_RESET, OUTPUT); pinMode(WIFI_EN, OUTPUT); // Shift register pinMode(N_SER, OUTPUT); pinMode(N_SCK, OUTPUT); pinMode(N_RCK, OUTPUT); pinMode(N_SCL, OUTPUT); digitalWrite(N_SCL, HIGH); //// Start wifi digitalWrite(WIFI_EN, HIGH); delay(250); WiFi.setPins(WIFI_SS, WIFI_IRQ, WIFI_RESET, -1); // Turn on display write595Time(0, 0, 0); digitalWrite(HV_EN, HIGH); } /** * On each loop, there are three things we want to check: * - Whether we need to re-sync with the NTP server * - Whether there is a pending UDB datagram to handle * - Whether it's time to update the display */ void loop() { // Ensure that the wifi remains connected ensureWifi(); // If it's been more than NTP_SYNC_INTERVAL since we last updated our time // offset, then issue a new NTP sync request. if (WiFi.status() == WL_CONNECTED && millis() - lastNtpSyncSuccess > NTP_SYNC_INTERVAL && millis() - lastNtpPacketSent > NTP_RESEND_INTERVAL) { LOGLN("Sending new NTP packet"); sendNTPpacket(); lastNtpPacketSent = millis(); } // If we have a pending NTP response datagram, handle it if (Udp.parsePacket()) { LOGLN("Got NTP response"); parseNtpResponse(); } // If it's been too long since our last refresh of the display, trigger one if (millis() - lastDisplayUpdate > DISPLAY_UPDATE_INTERVAL) { lastDisplayUpdate = millis(); updateDisplay(); } while (Serial.available()) { handleSerial(); } } #define READY 1 #define READ_SSID 2 #define READ_PASS 3 uint8_t serialState = READY; char serialBuffer[96]; uint8_t cmdBufferIndex = 0; void handleSerial() { // Read the next character int c = Serial.read(); // If we read a -1, there's no data available if (c < 0) { return; } Serial.print(((char)c)); // Handle the byte depending on our current state switch(serialState) { case READY: // Add the char to the command buffer serialBuffer[cmdBufferIndex++] = c; // If the buffer has 4 characters, check if it's a valid command if (cmdBufferIndex == 5) { if (!strncmp("ssid ", serialBuffer, 5)) { serialState = READ_SSID; } else if (!strncmp("pass ", serialBuffer, 5)) { serialState = READ_PASS; } else { serialState = READY; Serial.println("Invalid cmd"); } cmdBufferIndex = 0; } break; case READ_PASS: if (c == '\n' || c == '\r') { serialBuffer[cmdBufferIndex++] = 0; setWifiPass(serialBuffer, cmdBufferIndex); Serial.print("Set Wifi pass to "); Serial.println(serialBuffer); cmdBufferIndex = 0; serialState = READY; } serialBuffer[cmdBufferIndex++] = c; break; case READ_SSID: if (c == '\n' || c == '\r') { serialBuffer[cmdBufferIndex++] = 0; setWifiSsid(serialBuffer, cmdBufferIndex); Serial.print("Set Wifi ssid to "); Serial.println(serialBuffer); cmdBufferIndex = 0; serialState = READY; } serialBuffer[cmdBufferIndex++] = c; break; } } void setWifiSsid(char *newSSID, uint8_t len) { eeprom_write_block(newSSID, EEMEM_SSID, len); eeprom_write_byte(&EEMEM_SSID_LEN, len); } void setWifiPass(char *newPass, uint8_t len) { eeprom_write_block(newPass, EEMEM_PASS, len); eeprom_write_byte(&EEMEM_PASS_LEN, len); } /** * Update our stored offset of the timezone-local epoch time against our * internal millisecond counter. */ void updateOffset(unsigned long localTime) { millis_offset = (localTime - (millis() / 1000)); } /** * Initialize the WiFi chipset, * internal millisecond counter. */ void ensureWifi() { // If the wifi is connected, and we thought it was connected, do nothing if (WiFi.status() == WL_CONNECTED && wifiStatus == WL_CONNECTED) { return; } // Check if we have loaded the SSID details from EEPROM if (!ssidLoaded) { uint8_t ssidLen = eeprom_read_byte(&EEMEM_SSID_LEN); LOG("SSid length: "); LOGLN(ssidLen); uint8_t passLen = eeprom_read_byte(&EEMEM_PASS_LEN); LOG("Pass length: "); LOGLN(passLen); eeprom_read_block(ssid, EEMEM_SSID, ssidLen); eeprom_read_block(pass, EEMEM_PASS, passLen); LOG("Loaded wifi details "); LOG(ssid); LOG(" / "); LOGLN(pass); ssidLoaded = true; } // If we're not connected, and it's been long enough since the last attempt, // re-begin the wifi connection. if (millis() - lastWifiBeginTime > WIFI_RECONNECT_INTERVAL) { lastWifiBeginTime = millis(); LOG("(Re)Connecting to WiFi"); wifiStatus = WiFi.begin(ssid, pass); } // If the wifi is connected but we didn't think it was, then we must just have // connected, so start a UDP socket up. if (wifiStatus == WL_CONNECTED || WiFi.status() == WL_CONNECTED) { LOG("Wifi connected"); wifiStatus = WL_CONNECTED; Udp.begin(LOCAL_PORT); return; } } /** * Parse the response datagram from the NTP timeserver */ void parseNtpResponse() { // Read the response packet into the buffer Udp.read(packetBuffer, NTP_PACKET_SIZE); // The info we care about, the NTP timestamp, begins at byte 40 in the // packet and is 4 bytes long. Grab those bytes and convert them to an // unsigned long. unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); // NTP counts seconds since 1900, not seconds since 1970. unsigned long secsSince1900 = highWord << 16 | lowWord; // So to convert to UNIX epoch, just need to subtract 70 years in seconds const unsigned long seventyYears = 2208988800UL; unsigned long epoch = secsSince1900 - seventyYears; // Now, convert that epoch to our local timezone unsigned long localTime = usEastern.toLocal(epoch); LOG("Updated time offset: "); LOGLN(localTime); // And update our millisecond offset updateOffset(localTime); lastNtpSyncSuccess = millis(); } /** * Issue an NTP sync request to our timeserver. * This involves building up a packet by hand - reference to RFC5905 is key * https://tools.ietf.org/html/rfc5905#section-7.3 */ unsigned long sendNTPpacket() { // Zero out our packet buffer memset(packetBuffer, 0, NTP_PACKET_SIZE); // Our first byte contains the Leap Indicator, Version Number and Mode. packetBuffer[0] = ( // Leap indicator is set to 3 (clock not sync'd) 0x03 << 6 | // Version number is 4, the current version of NTP 0x04 << 3 | // Our mode is 3, client 0x03 << 0 ); // Next is stratum, in this case unspecified packetBuffer[1] = 0x00; // Polling interval - the maximum interbal between successive messages. // This is a funky variable; it's in log2 seconds. // 6 is the recommended lower bound, so we'll use that packetBuffer[2] = 0x06; // The precision of our own clock. // This is also in log2 seconds (signed). So to specify microseconds, that's // 1 / 1,000,000 seconds. log_2(1000000) is 20 (well, 19,9), so for one // onemill...
neo666x