Skip to content

Commit 02de84a

Browse files
committed
Persistent storage for wifi credentials
1 parent 3d4f675 commit 02de84a

File tree

5 files changed

+68
-47
lines changed

5 files changed

+68
-47
lines changed

README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ To see your clock’s software version, hold **Select** briefly while powering u
1414

1515
**A universal digital clock codebase for Arduino,** maintained by [Luke](https://theclockspot.com).
1616

17-
* Time of day with automatic DST change and chimes.
18-
* Perpetual calendar with day counter and local sunrise/sunset times.
19-
* Daily alarm with snooze and automatic weekday/weekend skipping.
20-
* Chronograph and timer with reset/interval options.
21-
* Simple control via three/four buttons, a rotary encoder, and/or an [IMU](https://en.wikipedia.org/wiki/Inertial_measurement_unit) (tilt control).
22-
* Signals via piezo beeper, switch (e.g. appliance timer), and/or pulse (e.g. bell ringer).
23-
* Supports Nixie displays of two SN74141 chips driving four/six tubes, with anti-cathode poisoning.
24-
* Supports LED displays of three/four MAX7219 chips (via SPI) driving 8x8 LED matrices ([example](https://www.amazon.com/HiLetgo-MAX7219-Arduino-Microcontroller-Display/dp/B07FFV537V/)).
25-
* Supports scheduled display dim/shutoff and switchable backlighting with optional PWM fade.
17+
* **Time of day** with automatic DST change and chimes.
18+
* **Perpetual calendar** with day counter and local sunrise/sunset times.
19+
* **Alarm** with snooze and automatic weekday/weekend skipping.
20+
* **Chronograph and timer** with reset/interval options.
21+
* Runs on both classic Arduino Nano (AVR) and Nano 33 IoT (SAMD21).
22+
* Supports **web-based config and NTP sync** over Wi-Fi on Nano 33 IoT.
23+
* **Simple control** via three/four buttons, a rotary encoder, and/or Nano 33 IoT’s [IMU](https://en.wikipedia.org/wiki/Inertial_measurement_unit) (tilt control).
24+
* **Signals** via piezo beeper, switch (e.g. appliance timer), and/or pulse (e.g. bell ringer).
25+
* Supports **Nixie displays** of two SN74141 chips driving four/six tubes, with anti-cathode poisoning.
26+
* Supports **LED displays** of three/four MAX7219 chips (via SPI) driving 8x8 LED matrices ([example](https://www.amazon.com/HiLetgo-MAX7219-Arduino-Microcontroller-Display/dp/B07FFV537V/)).
27+
* Scheduled nightly/weekly **display dim/shutoff** and switchable backlighting with optional PWM fade.
2628
* Timekeeping can be internal, or based on a DS3231 RTC (via I2C) for reliability/accuracy.
27-
* Supports both AVR and SAMD Arduinos (tested on Arduino Nano and Nano 33 IoT).
28-
* On Nano 33 IoT, supports web-based config and NTP sync over Wi-Fi.
29-
* User settings kept in persistent storage, but also mirrored in RAM in case of EEPROM/flash failure.
29+
* Settings stored persistently in case of power loss, and mirrored in RAM in case of EEPROM/flash failure.
3030

3131
Written to support [RLB Designs’](http://rlb-designs.com/) Universal Nixie Driver Board (UNDB):
3232

@@ -38,13 +38,13 @@ Written to support [RLB Designs’](http://rlb-designs.com/) Universal Nixie Dri
3838

3939
# Configuration, compilation, and upload
4040

41-
Various options, such as enabled functionality, RTC, display, I/O pins, timeouts, and control behaviors, are specified in a config file. This allows you to maintain configs for multiple clock hardware profiles, and simply include the relevant config at the top of the `.ino` before compiling. Several [example configs](https://github.com/clockspot/arduino-clock/tree/master/arduino-clock/configs) are provided, and [`~sample.h`](https://github.com/clockspot/arduino-clock/blob/master/arduino-clock/configs/%7Esample.h) includes all possible options with detailed comments.
41+
Various options, such as enabled functionality, RTC, display, I/O pins, timeouts, and control behaviors, are specified in a config file. This allows you to maintain configs for multiple clock hardware profiles, and simply include the relevant config at the top of `arduino-nixie.h` before compiling. Several [example configs](https://github.com/clockspot/arduino-clock/tree/master/arduino-clock/configs) are provided, and [`~sample.h`](https://github.com/clockspot/arduino-clock/blob/master/arduino-clock/configs/%7Esample.h) includes all possible options with detailed comments.
4242

43-
You may also wish to adjust the defaults for the clock’s user-configurable values to best suit its intended use. Some of these are specified in the config; others are in the main code (`optsDef[]` for [settings](https://github.com/clockspot/arduino-clock/blob/master/INSTRUCTIONS.md#settings-menu) and `initEEPROM()` for other values).
43+
You may also wish to adjust the defaults for the clock’s user-configurable values to best suit its intended use, in case the user performs a hard reset. Some of these are specified in the config; others, for now, are hardcoded in `arduino-nixie.ino` (`optsDef[]` for [settings](https://github.com/clockspot/arduino-clock/blob/master/INSTRUCTIONS.md#settings-menu) and `initEEPROM()` for other values).
4444

4545
I use the Arduino IDE to compile and upload, due to the use of various Arduino and Arduino-oriented libraries. Make sure the relevant libraries are installed in the Library Manager, per the config in use.
4646

47-
* EEPROM (Arduino) for AVR Arduinos (e.g. original Nano)
47+
* EEPROM (Arduino) for AVR Arduinos (e.g. classic Nano)
4848
* SPI (Arduino) and [LedControl](http://wayoda.github.io/LedControl) for MAX7219-based displays
4949
* [Encoder](https://github.com/PaulStoffregen/Encoder) if rotary encoder is used for Up/Down inputs
5050
* Arduino_LSM6DS3 (Arduino) if using Nano 33 IoT’s IMU for inputs
@@ -57,4 +57,4 @@ I use the Arduino IDE to compile and upload, due to the use of various Arduino a
5757
Before compiling and uploading, you will need to select the correct board, port, and (for AVR) processor in the IDE’s Tools menu.
5858

5959
* If your Arduino does not appear as a port option, you may have a clone that requires [drivers for the CH340 chipset](https://sparks.gogo.co.nz/ch340.html).
60-
* If upload fails for an ATMega328P Arduino (e.g. original Nano), try selecting/unselecting “Old Bootloader” in the processor menu.
60+
* If upload fails for an ATMega328P Arduino (e.g. classic Nano), try selecting/unselecting “Old Bootloader” in the processor menu.

TODO.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
* wi-fi credential save fails if keys are part of the string?
1010
* DST calc may behave unpredictably between 1–2am on fallback day
1111
* Redo NTP on startup if it failed (networkStartWifi())
12-
* Handle 2038+ epochs
12+
* Make [2036-ready](https://en.wikipedia.org/wiki/Year_2038_problem#Network_Time_Protocol_timestamps)
1313
* Notice when a leap second is coming and handle it
1414
* When setting page is used to set day counter and date, and the month changes, set date max. For 2/29 it should just do 3/1 probably.
1515
* Weather support
16+
* Stop using strings? There is plenty of RAM available on SAMD I think
1617
* Input: support other IMU orientations
1718
* When day counter is set to count up from 12/31, override to display 365/366 on that date
1819
* Bitmask to enable/disable features?

arduino-nixie/arduino-nixie.ino

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,6 @@ bool initEEPROM(bool hard){
749749
if(hard || readEEPROM(6,false)<1 || readEEPROM(6,false)>31) changed += writeEEPROM(6,31,false,false); //6: ...31st. (This gives the day of the year)
750750
if(hard) changed += writeEEPROM(7,0,false,false); //7: Alt function preset
751751
//8: TODO functions/pages enabled (bitmask)
752-
if(hard || readEEPROM(9,false)>1) changed += writeEEPROM(9,1,false,false); //9: NTP sync on
753752
if(hard) changed += writeEEPROM(15,0,false,false); //15: last known DST on flag - clear on hard reset (to match the reset RTC/auto DST/anti-poisoning settings to trigger midnight tubes as a tube test)
754753
if(networkSupported()){
755754
if(hard){ //everything in here needs no range testing
@@ -762,10 +761,11 @@ bool initEEPROM(bool hard){
762761
changed += writeEEPROM(54, 27,false,false);
763762
//Serial.print(readEEPROM(51,false),DEC); Serial.print(F(".")); Serial.print(readEEPROM(52,false),DEC); Serial.print(F(".")); Serial.print(readEEPROM(53,false),DEC); Serial.print(F(".")); Serial.println(readEEPROM(54,false),DEC);
764763
//55-86 Wi-Fi SSID (32 bytes)
765-
//TODO
766764
//87-150 Wi-Fi WPA passphrase/key or WEP key (64 bytes)
767-
//TODO
765+
for(byte i=0; i<96; i++) changed += writeEEPROM(55+i,0,false,false); //Clear out the old values (32+64+1)
768766
}
767+
//9 NTP sync enabled
768+
if(hard || readEEPROM(9,false)>1) changed += writeEEPROM(9,0,false,false);
769769
//151 Wi-Fi WEP key index
770770
if(hard || readEEPROM(151,false)>3) changed += writeEEPROM(151,0,false,false);
771771
} //end network supported
@@ -930,7 +930,7 @@ void checkRTC(bool force){
930930
}
931931

932932
//NTP cue at :59:00
933-
if(rtcGetMinute()==59 && networkSupported() && readEEPROM(9,false)){
933+
if(rtcGetMinute()==59 && networkSupported()){
934934
if(rtcGetSecond()==0) cueNTP();
935935
if(rtcGetSecond()==30 && ntpSyncAgo()>=30000) cueNTP(); //if at first you don't succeed...
936936
}

arduino-nixie/network.cpp

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
//Needs to be able to save to persistent storage
1717
#include "storage.h"
1818

19-
String wssid = "Riley";
20-
String wpass = "5802301644"; //wpa pass or wep key
19+
//Volatile vars that back up the wifi creds in EEPROM
20+
// 55-86 Wi-Fi SSID (32 bytes)
21+
// 87-150 Wi-Fi WPA passphrase/key or WEP key (64 bytes)
22+
// 151 Wi-Fi WEP key index (1 byte)
23+
//If the EEPROM ssid/pass are a full 32/64 chars, there won't be room for the termination character '\0', we'll add that when reading
24+
//TODO consider making these char arrays - but the memory usage on SAMD isn't bad as-is
25+
String wssid = "";
26+
String wpass = ""; //wpa pass or wep key
2127
byte wki = 0; //wep key index - 0 if using wpa
2228

2329
unsigned int localPort = 2390; // local port to listen for UDP packets
@@ -39,7 +45,15 @@ void initNetwork(){
3945
//Check status of wifi module up front
4046
//if(WiFi.status()==WL_NO_MODULE){ Serial.println(F("Communication with WiFi module failed!")); while(true); }
4147
//else if(WiFi.firmwareVersion()<WIFI_FIRMWARE_LATEST_VERSION) Serial.println(F("Please upgrade the firmware"));
42-
networkStartWiFi();
48+
//Get wifi credentials out of EEPROM - see wssid/wpass definitions above
49+
//Read until a termination character is reached
50+
for(byte i=0; i<32; i++){ if(readEEPROM(55+i,false)=='\0') break; wssid.concat((char)(readEEPROM(55+i,false))); } //Read in the SSID
51+
for(byte i=0; i<64; i++){ if(readEEPROM(87+i,false)=='\0') break; wpass.concat((char)(readEEPROM(87+i,false))); } //Read in the pass
52+
wki = readEEPROM(151,false); //Read in the wki
53+
Serial.print(F("wssid=")); Serial.println(wssid);
54+
Serial.print(F("wpass=")); Serial.println(wpass);
55+
Serial.print(F("wki=")); Serial.println(wki);
56+
networkStartWiFi();
4357
}
4458
void cycleNetwork(){
4559
checkClients();
@@ -130,7 +144,7 @@ unsigned long ntpSyncAgo(){
130144

131145
void cueNTP(){
132146
// We don't want to let other code startNTP() directly since it's normally asynchronous, and that other code may delay the time until we can check the result. Exception is forced call from admin page, which calls startNTP() synchronously.
133-
ntpCued = true;
147+
if(readEEPROM(9,false)) ntpCued = true;
134148
}
135149

136150
int startNTP(bool synchronous){ //Called at intervals to check for ntp time
@@ -422,7 +436,7 @@ void checkClients(){
422436

423437
client.print(F("<li id='ntpserverli' style='display: ")); if(readEEPROM(9,false)==0) client.print(F("none")); else client.print(F("block")); client.print(F(";'><label>NTP server</label><input type='text' id='ntpip' onchange='promptsave(\"ntpip\")' onkeyup='promptsave(\"ntpip\")' onblur='unpromptsave(\"ntpip\"); save(this)' value='")); client.print(readEEPROM(51,false),DEC); client.print(F(".")); client.print(readEEPROM(52,false),DEC); client.print(F(".")); client.print(readEEPROM(53,false),DEC); client.print(F(".")); client.print(readEEPROM(54,false),DEC); client.print(F("' />")); client.print(F(" <a id='ntpipsave' href='#' onclick='return false' style='display: none;'>save</a><br/><span class='explain'><a href='https://en.wikipedia.org/wiki/IPv4#Addressing' target='_blank'>IPv4</a> address, e.g. one of <a href='https://tf.nist.gov/tf-cgi/servers.cgi' target='_blank'>NIST's time servers</a></span></li>"));
424438

425-
client.print(F("<li><label>Current time</label><input type='number' id='todh' onchange='promptsave(\"tod\")' onkeyup='promptsave(\"tod\")' onblur='unpromptsave(\"tod\"); savetod(\"tod\")' min='0' max='23' step='1' value='")); client.print(rtcGetHour(),DEC); client.print(F("' />&nbsp;:&nbsp;<input type='number' id='todm' onchange='promptsave(\"tod\")' onkeyup='promptsave(\"tod\")' onblur='unpromptsave(\"tod\"); savetod(\"tod\")' min='0' max='59' step='1' value='")); client.print(rtcGetMinute(),DEC); client.print(F("' /><input type='hidden' id='tod' /> <a id='todsave' href='#' onclick='return false' style='display: none;'>save</a><br/><span class='explain'>24-hour format. Seconds will reset to 0 when saved.</span></li>"));
439+
client.print(F("<li><label>Current time</label><input type='number' id='curtodh' onchange='promptsave(\"curtod\")' onkeyup='promptsave(\"curtod\")' onblur='unpromptsave(\"curtod\"); savetod(\"curtod\")' min='0' max='23' step='1' value='")); client.print(rtcGetHour(),DEC); client.print(F("' />&nbsp;:&nbsp;<input type='number' id='curtodm' onchange='promptsave(\"curtod\")' onkeyup='promptsave(\"curtod\")' onblur='unpromptsave(\"curtod\"); savetod(\"curtod\")' min='0' max='59' step='1' value='")); client.print(rtcGetMinute(),DEC); client.print(F("' /><input type='hidden' id='curtod' /> <a id='curtodsave' href='#' onclick='return false' style='display: none;'>save</a><br/><span class='explain'>24-hour format. Seconds will reset to 0 when saved.</span></li>"));
426440

427441
client.print(F("<li><label>Time format</label><select id='b16' onchange='save(this)'>")); for(char i=1; i<=2; i++){ client.print(F("<option value='")); client.print(i,DEC); client.print(F("'")); if(readEEPROM(16,false)==i) client.print(F(" selected")); client.print(F(">")); switch(i){
428442
case 1: client.print(F("12-hour")); break;
@@ -760,7 +774,7 @@ void checkClients(){
760774
//client.print(F(""));
761775
} //end get
762776
else { //requestType==2 - handle what was POSTed
763-
String clientReturn = "ok"; //To return an error, set it here. An empty response generates "Error".
777+
bool clientReturn = false; //Mark true when sending an error. If none, "ok" is sent at end. If nothing sent (crash), client displays generic error.
764778
//client.print(currentLine);
765779
//syncfreq=hr
766780
//syncfreq=min
@@ -775,13 +789,12 @@ void checkClients(){
775789
wpass = currentLine.substring(startPos,endPos);
776790
startPos = endPos+5;
777791
wki = currentLine.substring(startPos).toInt();
778-
// client.print(F(" wssid="));
779-
// client.print(wssid);
780-
// client.print(F(" wpass="));
781-
// client.print(wpass);
782-
// client.print(F(" wki="));
783-
// client.print(wki);
784-
// client.println();
792+
//Persistent storage - see wssid/wpass definitions above
793+
for(byte i=0; i<97; i++) writeEEPROM(55+i,0,false,false); //Clear out the old values (32+64+1)
794+
for(byte i=0; i<wssid.length(); i++) writeEEPROM(55+i,wssid[i],false,false); //Write in the SSID
795+
for(byte i=0; i<wpass.length(); i++) writeEEPROM(87+i,wpass[i],false,false); //Write in the pass
796+
writeEEPROM(151,wki,false,false); //Write in the wki
797+
commitEEPROM(); //commit all the above
785798
requestType = 3; //triggers an admin restart after the client is closed, below
786799
} else if(currentLine.startsWith(F("ntpip"))){
787800
//e.g. ntpip=192.168.1.255
@@ -792,22 +805,24 @@ void checkClients(){
792805
ntpip[i] = octet; startPos = endPos+1;
793806
if(ntpip[i]!=octet){ parseOK = false; break; }
794807
}
795-
if(!parseOK) clientReturn = "Error: invalid format";
808+
if(!parseOK) { clientReturn = true; client.print(F("Error: invalid format")); }
796809
else for(byte i=0; i<4; i++) writeEEPROM(51+i,ntpip[i],false);
797810
//Serial.print(F("IP should be ")); Serial.print(ntpip[0],DEC); Serial.print(F(".")); Serial.print(ntpip[1],DEC); Serial.print(F(".")); Serial.print(ntpip[2],DEC); Serial.print(F(".")); Serial.println(ntpip[3],DEC);
798811
//Serial.print(F("IP saved as ")); Serial.print(readEEPROM(51,false),DEC); Serial.print(F(".")); Serial.print(readEEPROM(52,false),DEC); Serial.print(F(".")); Serial.print(readEEPROM(53,false),DEC); Serial.print(F(".")); Serial.println(readEEPROM(54,false),DEC);
799812
} else if(currentLine.startsWith(F("syncnow"))){
813+
//TODO this doesn't seem to return properly if the wifi was changed after the clock was booted - it syncs, but just hangs
800814
int ntpCode = startNTP(true);
801815
switch(ntpCode){
802-
case -1: clientReturn = "Error: no Wi-Fi credentials."; break;
803-
case -2: clientReturn = "Error: not connected to Wi-Fi."; break;
804-
case -3: clientReturn = "Error: NTP response pending. Please try again shortly."; break; //should never see this one on the web since it's synchronous and the client blocks
805-
case -4: clientReturn = "Error: too many sync requests in the last "; clientReturn += (NTP_MINFREQ/1000); clientReturn += " seconds. Please try again shortly."; break;
806-
case -5: clientReturn = "Error: no NTP response received. Please confirm server."; break;
807-
case 0: clientReturn = "synced"; break;
808-
default: clientReturn = "Error: unhandled NTP code"; break;
816+
case -1: client.print(F("Error: no Wi-Fi credentials.")); break;
817+
case -2: client.print(F("Error: not connected to Wi-Fi.")); break;
818+
case -3: client.print(F("Error: NTP response pending. Please try again shortly.")); break; //should never see this one on the web since it's synchronous and the client blocks
819+
case -4: client.print(F("Error: too many sync requests in the last ")); client.print(NTP_MINFREQ/1000,DEC); client.print(F(" seconds. Please try again shortly.")); break;
820+
case -5: client.print(F("Error: no NTP response received. Please confirm server.")); break;
821+
case 0: client.print(F("synced")); break;
822+
default: client.print(F("Error: unhandled NTP code")); break;
809823
}
810-
} else if(currentLine.startsWith(F("tod"))){
824+
clientReturn = true;
825+
} else if(currentLine.startsWith(F("curtod"))){
811826
int tod = currentLine.substring(7).toInt();
812827
rtcSetTime(tod/60,tod%60,0);
813828
ntpSyncLast = 0;
@@ -876,7 +891,7 @@ void checkClients(){
876891
}
877892
}
878893
updateDisplay();
879-
client.print(clientReturn);
894+
if(!clientReturn) client.print(F("ok"));
880895
} //end post
881896
} //end if requestType
882897

arduino-nixie/storage.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#define STORAGE_SPACE 152 //number of bytes
1515
byte storageBytes[STORAGE_SPACE]; //the volatile array of bytes
16+
#define WRITE_TO_EEPROM 1 //1 for production
1617

1718
void initStorage(){
1819
//If this is SAMD, write starting values if unused
@@ -22,8 +23,10 @@ void initStorage(){
2223
Serial.print(F("16=")); Serial.println(EEPROM.read(16));
2324
if(!EEPROM.isValid() || EEPROM.read(16)==0 || EEPROM.read(16)==255){ //invalid eeprom, wipe it out
2425
for(byte i=0; i<STORAGE_SPACE; i++) EEPROM.update(i,0);
25-
EEPROM.commit();
26-
Serial.println(F("WARNING: FLASH EEPROM COMMIT per init"));
26+
if(WRITE_TO_EEPROM){
27+
EEPROM.commit();
28+
Serial.println(F("WARNING: FLASH EEPROM COMMIT per init"));
29+
}
2730
}
2831
#endif
2932
//Read from real persistent storage into storageBytes
@@ -56,7 +59,7 @@ bool writeEEPROM(int loc, int val, bool isInt, bool commit){
5659
EEPROM.update(loc,val);
5760
}
5861
#ifdef FLASH_AS_EEPROM
59-
if(commit){
62+
if(commit && WRITE_TO_EEPROM){
6063
EEPROM.commit(); //bad!! See TODO in storage.h
6164
Serial.println(F("WARNING: FLASH EEPROM COMMIT per write"));
6265
}
@@ -65,7 +68,9 @@ bool writeEEPROM(int loc, int val, bool isInt, bool commit){
6568
}
6669
void commitEEPROM(){
6770
#ifdef FLASH_AS_EEPROM
71+
if(WRITE_TO_EEPROM){
6872
EEPROM.commit(); //bad!! See TODO in storage.h
6973
Serial.println(F("WARNING: FLASH EEPROM COMMIT by request"));
74+
}
7075
#endif
7176
}

0 commit comments

Comments
 (0)