2626
2727// //////// Configuration consts //////////
2828
29- // available clock functions, and unique IDs (between 0 and 199 )
29+ // available clock functions, and unique IDs (between 0 and 200 )
3030const byte fnIsTime = 0 ;
3131const byte fnIsDate = 1 ;
3232const byte fnIsAlarm = 2 ;
3333const byte fnIsTimer = 3 ;
3434const byte fnIsDayCount = 4 ;
3535const byte fnIsTemp = 5 ;
36+ const byte fnIsCleaner = 6 ;
3637// functions enabled in this clock, in their display order. Only fnIsTime is required
37- const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsDayCount, fnIsTemp};
38+ const byte fnsEnabled[] = {fnIsTime, fnIsDate, fnIsDayCount, fnIsTemp, fnIsCleaner };
3839
3940// These are the RLB board connections to Arduino analog input pins.
4041// S1/PL13 = Reset
@@ -79,9 +80,11 @@ const byte alarmRadio = 0;
7980// When timer is running, output will stay on until timer runs down.
8081const byte alarmDur = 3 ;
8182
83+ const byte displaySize = 4 ; // 4 or 6 - causes small differences in display of timer, etc
84+
8285// How long (in ms) are the button hold durations?
8386const word btnShortHold = 1000 ; // for setting the displayed feataure
84- const word btnLongHold = 3000 ; // for for entering SETUP menu
87+ const word btnLongHold = 3000 ; // for for entering options menu
8588const byte velThreshold = 100 ; // ms
8689// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1).
8790// Recommend ~100 for rotaries. If you want to use this feature with buttons, extend to ~400.
@@ -93,10 +96,10 @@ const byte dayCountYearLoc = 3; //and 4 (word)
9396const byte dayCountMonthLoc = 5 ; // byte
9497const byte dayCountDateLoc = 6 ; // byte
9598
96- // EEPROM locations and default values for SETUP options menu
97- // Option number/fnSet are 1-index, so arrays are padded to be 1-index too, for coding convenience. TODO change this
99+ // EEPROM locations and default values for options menu
100+ // Option numbers are 1-index, so arrays are padded to be 1-index too, for coding convenience. TODO change this
98101// Most vals (default, min, max) are 1-byte. In case of two-byte (max-min>255), high byte is loc, low byte is loc+1.
99- // SETUP options are offset to loc 16, to reserve 16 bytes of space for other set values per above
102+ // options are offset to loc 16, to reserve 16 bytes of space for other set values per above
100103// Option number: - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
101104const byte optsLoc[] = {0 ,16 ,17 ,18 ,19 ,20 ,21 ,22 ,23 ,24 , 25 ,27 , 28 , 30 ,32 ,33 ,34 , 35 , 37 }; // EEPROM locs
102105const word optsDef[] = {0 , 2 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,500 , 0 ,1320 , 360 , 0 , 1 , 5 , 480 ,1020 };
@@ -119,9 +122,9 @@ byte btnCurHeld = 0; //Button hold thresholds: 0=none, 1=unused, 2=short, 3=long
119122unsigned long inputLast = 0 ; // When a button was last pressed
120123unsigned long inputLast2 = 0 ; // Second-to-last of above
121124
122- const byte fnSetup = 255 ; // function //TODO pagify menu by using fnSetup 200- 255 for 56 options, and fnSet for the pages
125+ const byte fnOpts = 201 ; // fn values from here to 255 correspond to options in the options menu
123126byte fn = fnIsTime; // currently displayed fn, as above
124- byte fnSet = 0 ; // whether this function is currently being set, and which option/page it's on
127+ byte fnSetPg = 0 ; // whether this function is currently being set, and which option/page it's on
125128word fnSetVal; // the value currently being set, if any - unsigned int 0-65535
126129word fnSetValMin; // min possible - unsigned int
127130word fnSetValMax; // max possible - unsigned int
@@ -138,7 +141,7 @@ byte displayNext[6] = {15,15,15,15,15,15}; //Internal representation of display.
138141// //////// Main code control //////////
139142
140143void setup (){
141- Serial.begin (57600 );
144+ Serial.begin (9600 );
142145 Wire.begin ();
143146 initOutputs ();
144147 initInputs ();
@@ -256,14 +259,22 @@ void ctrlEvt(byte ctrl, byte evt){
256259 // But we can handle short and long holds and releases for the sel ctrls (always buttons).
257260 // TODO needs alt handling
258261
259- if (fn != fnSetup) { // normal fn running/setting (not in setup menu)
260-
261- if (evt==3 && ctrl==mainSel) { // mainSel long hold: enter SETUP menu
262- btnStop (); fn = fnSetup; startOpt (1 ); return ;
262+ if (fn < fnOpts) { // normal fn running/setting (not in options menu)
263+
264+ if (evt==3 && ctrl==mainSel) { // mainSel long hold: enter options menu
265+ // Serial.println("running/setting: mainSel long hold: enter options menu"); Serial.println();
266+ btnStop ();
267+ fn = fnOpts;
268+ clearSet (); // don't need updateDisplay() here because this calls updateRTC with force=true
269+ return ;
263270 }
264271
265- if (!fnSet) { // fn running
266- if (evt==2 && ctrl==mainSel) { // sel hold: enter fnSet
272+ if (!fnSetPg) { // fn running
273+ // Serial.print("fn ");
274+ // Serial.print(fn,DEC);
275+ // Serial.println(" running");
276+ if (evt==2 && ctrl==mainSel) { // sel hold: enter setting mode
277+ // Serial.println(" mainSel hold: enter setting");
267278 switch (fn){
268279 case fnIsTime: // set mins
269280 startSet ((tod.hour ()*60 )+tod.minute (),0 ,1439 ,1 ); break ;
@@ -281,39 +292,45 @@ void ctrlEvt(byte ctrl, byte evt){
281292 break ; // nothing - or is this where we do the calibration? TODO
282293 default : break ;
283294 }
295+ // showSitch();
284296 return ;
285297 }
286298 else if ((ctrl==mainSel && evt==0 ) || ((ctrl==mainAdjUp || ctrl==mainAdjDn) && evt==1 )) { // sel release or adj press - switch fn, depending on config
299+ // Serial.println(" sel release or adj press: switch fn");
287300 // -1 = nothing, -2 = cycle through functions, other = go to specific function (see fn)
288- // we can't handle sel press here because, if attempting to enter fnSet , it would switch the fn first
301+ // we can't handle sel press here because, if attempting to enter setting mode , it would switch the fn first
289302 bool fnChgd = false ;
290303 if (ctrl==mainSel && mainSelFn!=-1 ) {
304+ // Serial.println(" mainSel release: go to next fn in the cycle");
291305 fnChgd = true ;
292306 if (mainSelFn==-2 ) fnScroll (1 ); // Go to next fn in the cycle
293307 else fn = mainSelFn;
294308 }
295309 else if ((ctrl==mainAdjUp || ctrl==mainAdjDn) && mainAdjFn!=-1 ) {
310+ // Serial.println(" mainAdj press: go to prev/next fn in the cycle");
296311 fnChgd = true ;
297312 if (mainAdjFn==-2 ) fnScroll (ctrl==mainAdjUp?1 :-1 ); // Go to next or previous fn in the cycle
298313 else fn = mainAdjFn;
299314 }
300315 if (fnChgd){
301316 switch (fn){
302- // "ticking" ones
303- case fnIsTime: case fnIsDate: case fnIsTimer: case fnIsDayCount:
304- checkRTC (true ); break ;
305317 // static ones
306318 case fnIsAlarm: case fnIsTemp:
307319 updateDisplay (); break ;
308- default : break ;
320+ // "ticking" ones
321+ default : checkRTC (true ); break ;
309322 }
310323 }
311324 }
312325 } // end fn running
313326
314327 else { // fn setting
328+ // Serial.print("fn ");
329+ // Serial.print(fn,DEC);
330+ // Serial.println(" setting");
315331 if (evt==1 ) { // we respond only to press evts during fn setting
316- if (ctrl==mainSel) { // mainSel push: go to next option or save and exit fnSet
332+ if (ctrl==mainSel) { // mainSel push: go to next option or save and exit setting mode
333+ // Serial.println(" mainSel push: go to next option or save and exit setting mode");
317334 btnStop (); // not waiting for mainSelHold, so can stop listening here
318335 // We will set ds3231 time parts directly
319336 // con: potential for very rare clock rollover while setting; pro: can set date separate from time
@@ -324,7 +341,7 @@ void ctrlEvt(byte ctrl, byte evt){
324341 ds3231.setSecond (0 ); // will reset to exactly 0? TODO confirm
325342 clearSet (); break ;
326343 case fnIsDate: // save in RTC
327- switch (fnSet ){
344+ switch (fnSetPg ){
328345 case 1 : // save year, set month
329346 fnSetValDate[0 ]=fnSetVal;
330347 startSet (fnSetValDate[1 ],1 ,12 ,2 ); break ;
@@ -344,7 +361,7 @@ void ctrlEvt(byte ctrl, byte evt){
344361 timerTime = fnSetVal;
345362 break ;
346363 case fnIsDayCount: // set like date, save in eeprom like finishOpt
347- switch (fnSet ){
364+ switch (fnSetPg ){
348365 case 1 : // save year, set month
349366 writeEEPROM (dayCountYearLoc,fnSetVal,true );
350367 startSet (readEEPROM (dayCountMonthLoc,false ),1 ,12 ,2 ); break ;
@@ -364,42 +381,83 @@ void ctrlEvt(byte ctrl, byte evt){
364381 } // end mainSel push
365382 if (ctrl==mainAdjUp) doSet (inputLast-inputLast2<velThreshold ? 10 : 1 );
366383 if (ctrl==mainAdjDn) doSet (inputLast-inputLast2<velThreshold ? -10 : -1 );
384+ // showSitch();
367385 } // end if evt==1
368386 } // end fn setting
369-
387+
370388 } // end normal fn running/setting
371- else { // setup menu setting - to/from EEPROM
389+
390+ else { // options menu setting - to/from EEPROM
391+ // Serial.print("opt ");
392+ // Serial.print(fn,DEC);
372393
373- if (ctrl==mainSel) {
374- if (evt==1 ) { // TODO could consider making it a release, so it doesn't go to the next option before leaving
375- finishOpt ();
376- if (fnSet==sizeof (optsLoc)-1 ) { // that was the last one – rotate back around
377- startOpt (1 ); return ;
378- } else {
379- startOpt (fnSet+1 ); return ;
380- }
381- }
382- if (evt==2 ) { // exit setup
383- btnStop (); fn = fnIsTime; clearSet (); /* debugEEPROM();*/ return ; // exit setup
384- // setCaches();
385- }
394+ if (evt==2 && ctrl==mainSel) { // mainSel short hold: exit options menu
395+ // Serial.println(" mainSel short hold: exit options menu");
396+ btnStop ();
397+ // if we're setting a value, writes setting val to EEPROM if needed
398+ if (fnSetPg) writeEEPROM (optsLoc[fnSetPg],fnSetVal,optsMax[fnSetPg]-optsMin[fnSetPg]>255 ?true :false );
399+ fn = fnIsTime;
400+ clearSet ();
401+ return ;
402+ // showSitch();
386403 }
387- if (ctrl==mainAdjUp && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? 10 : 1 );
388- if (ctrl==mainAdjDn && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? -10 : -1 );
389404
390- } // end setup menu setting
405+ if (!fnSetPg){ // viewing option number
406+ // Serial.println(" viewing option number");
407+ if (ctrl==mainSel && evt==0 ) { // mainSel release: enter option value setting
408+ // Serial.println(" mainSel release: enter option value setting");
409+ byte n = fn-fnOpts+1 ; // For a given options menu option (1-index), read from EEPROM and call startSet
410+ startSet (readEEPROM (optsLoc[n],optsMax[n]-optsMin[n]>255 ?true :false ),optsMin[n],optsMax[n],n);
411+ }
412+ if (ctrl==mainAdjUp && evt==1 ) fnOptScroll (1 ); // next one up or cycle to beginning
413+ if (ctrl==mainAdjDn && evt==1 ) fnOptScroll (-1 ); // next one down or cycle to end?
414+ updateDisplay ();
415+ // showSitch();
416+ } // end viewing option number
417+ else { // setting option value
418+ // Serial.println(" setting option value");
419+ if (ctrl==mainSel && evt==0 ) { // mainSel release: save and exit option value setting
420+ // Writes setting val to EEPROM if needed
421+ writeEEPROM (optsLoc[fnSetPg],fnSetVal,optsMax[fnSetPg]-optsMin[fnSetPg]>255 ?true :false );
422+ clearSet ();
423+ }
424+ if (ctrl==mainAdjUp && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? 10 : 1 );
425+ if (ctrl==mainAdjDn && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? -10 : -1 );
426+ updateDisplay ();
427+ } // end setting option value
428+ } // end options menu setting
429+ // Serial.println(" ");
430+
391431} // end ctrlEvt
392432
433+ void showSitch (){
434+ Serial.print (" --- fn=" );
435+ Serial.print (fn,DEC);
436+ Serial.print (" , fnSetPg=" );
437+ Serial.print (fnSetPg,DEC);
438+ Serial.print (" , fnSetVal=" );
439+ Serial.print (fnSetVal,DEC);
440+ Serial.print (" ---" );
441+ Serial.println ();
442+ Serial.println ();
443+ }
444+
393445void fnScroll (char dir){
394446 // Switch to the next (1) or previous (-1) fn in fnsEnabled
395447 byte pos;
396448 byte posLast = sizeof (fnsEnabled)-1 ;
397449 if (dir==1 ) for (pos=0 ; pos<=posLast; pos++) if (fnsEnabled[pos]==fn) { fn = (pos==posLast?0 :fnsEnabled[pos+1 ]); break ; }
398450 if (dir==-1 ) for (pos=posLast; pos>=0 ; pos--) if (fnsEnabled[pos]==fn) { fn = (pos==0 ?posLast:fnsEnabled[pos-1 ]); break ; }
399451}
452+ void fnOptScroll (char dir){
453+ // Switch to the next options fn between min (fnOpts) and max (fnOpts+sizeof(optsLoc)-1) (inclusive)
454+ byte posLast = fnOpts+sizeof (optsLoc)-1 ;
455+ if (dir==1 ) fn = (fn==posLast? fnOpts: fn+1 );
456+ if (dir==-1 ) fn = (fn==fnOpts? posLast: fn-1 );
457+ }
400458
401459void startSet (word n, word m, word x, byte p){ // Enter set state at page p, and start setting a value
402- fnSetVal=n; fnSetValMin=m; fnSetValMax=x; fnSetValVel=(x-m>30 ?1 :0 ); fnSet =p;
460+ fnSetVal=n; fnSetValMin=m; fnSetValMax=x; fnSetValVel=(x-m>30 ?1 :0 ); fnSetPg =p;
403461 updateDisplay ();
404462}
405463void doSet (int delta){
@@ -414,7 +472,7 @@ void doSetHold(){
414472 // TODO integrate this with checkInputs?
415473 if (doSetHoldLast+250 <millis ()) {
416474 doSetHoldLast = millis ();
417- if (fnSet !=0 && ((mainAdjType==1 && (btnCur==mainAdjUp || btnCur==mainAdjDn)) || (altAdjType==1 && (btnCur==altAdjUp || btnCur==altAdjDn))) ){ // if we're setting, and this is an adj input for which the type is button
475+ if (fnSetPg !=0 && ((mainAdjType==1 && (btnCur==mainAdjUp || btnCur==mainAdjDn)) || (altAdjType==1 && (btnCur==altAdjUp || btnCur==altAdjDn))) ){ // if we're setting, and this is an adj input for which the type is button
418476 bool dir = (btnCur==mainAdjUp || btnCur==altAdjUp ? 1 : 0 );
419477 // If short hold, or long hold but high velocity isn't supported, use low velocity (delta=1)
420478 if (btnCurHeld==2 || (btnCurHeld==3 && fnSetValVel==false )) doSet (dir?1 :-1 );
@@ -425,14 +483,7 @@ void doSetHold(){
425483}
426484void clearSet (){ // Exit set state
427485 startSet (0 ,0 ,0 ,0 );
428- checkRTC (true ); // force an update to tod before updateDisplay()
429- }
430-
431- void startOpt (byte n){ // For a given setup menu option (1-index), reads from EEPROM and calls startSet
432- startSet (readEEPROM (optsLoc[n],optsMax[n]-optsMin[n]>255 ?true :false ),optsMin[n],optsMax[n],n);
433- }
434- void finishOpt (){ // Writes fnSet val to EEPROM if needed
435- writeEEPROM (optsLoc[fnSet],fnSetVal,optsMax[fnSet]-optsMin[fnSet]>255 ?true :false );
486+ checkRTC (true ); // force an update to tod and updateDisplay()
436487}
437488
438489// EEPROM values are exclusively bytes (0-255) or words (unsigned ints, 0-65535)
@@ -448,14 +499,14 @@ void initEEPROM(){
448499 ds3231.setHour (0 );
449500 ds3231.setMinute (0 );
450501 ds3231.setSecond (0 );
451- // Set the default values that aren't part of the SETUP menu
502+ // Set the default values that aren't part of the options menu
452503 // writeEEPROM(alarmTimeLoc,420,true); //7am - word
453504 // writeEEPROM(alarmOnLoc,enableSoftAlarmSwitch==0?1:0,false); //off, or on if no software switch spec'd
454505 writeEEPROM (dayCountYearLoc,2018 ,true );
455506 writeEEPROM (dayCountMonthLoc,1 ,false );
456507 writeEEPROM (dayCountDateLoc,1 ,false );
457- // then the SETUP menu defaults
458- for (byte i=1 ; i<=sizeof (optsLoc)-1 ; i++) writeEEPROM (optsLoc[i],optsDef[i],optsMax[i]-optsMin[i]>255 ?true :false ); // setup menu
508+ // then the options menu defaults
509+ for (byte i=1 ; i<=sizeof (optsLoc)-1 ; i++) writeEEPROM (optsLoc[i],optsDef[i],optsMax[i]-optsMin[i]>255 ?true :false ); // options menu
459510}
460511word readEEPROM (int loc, bool isWord){
461512 if (isWord) {
@@ -510,8 +561,18 @@ void checkRTC(bool force){
510561 // Checks for new time-of-day second; decrements timer; checks for timed events;
511562 // updates display for running time or date.
512563 // Check for timeouts based on millis
513- if (fnSet && pollLast-inputLast>120000 ) { fnSet = 0 ; fn = fnIsTime; force=true ; } // setting timeout
514- else if (!fnSet && fn!=fnIsTime && fn!=fnIsDayCount && !(fn==fnIsTimer && (timerTime>0 || alarmSoundStart!=0 )) && pollLast>inputLast+5000 ) { fnSet = 0 ; fn = fnIsTime; force=true ; } // temporarily-displayed fn timeout back to fnIsTime
564+
565+ // Timeout to reset display
566+ if (pollLast > inputLast){ // don't bother if the last input (which may have called checkRTC) was more recent than poll
567+ // Option/setting timeout: if we're in the options menu, or we're setting a value
568+ if (fnSetPg || fn>=fnOpts){
569+ if (pollLast-inputLast>120000 ) { fnSetPg = 0 ; fn = fnIsTime; force=true ; } // Time out after 2 mins
570+ }
571+ // Temporary-display mode timeout: if we're *not* in a permanent one (time, day counter, or running timer)
572+ else if (fn!=fnIsTime && fn!=fnIsCleaner && fn!=fnIsDayCount && !(fn==fnIsTimer && (timerTime>0 || alarmSoundStart!=0 ))){
573+ if (pollLast>inputLast+5000 ) { fnSetPg = 0 ; fn = fnIsTime; force=true ; }
574+ }
575+ }
515576 // Update things based on RTC
516577 tod = rtc.now ();
517578 // toddow = ds3231.getDoW();
@@ -524,7 +585,7 @@ void checkRTC(bool force){
524585 // trip minutely date at :30 TODO
525586 // trip digit cycle TODO
526587
527- if (fnSet ==0 ) updateDisplay ();
588+ if (fnSetPg ==0 ) updateDisplay ();
528589
529590 } // end if force or new second
530591} // end checkRTC()
@@ -534,17 +595,21 @@ void checkRTC(bool force){
534595void updateDisplay (){
535596 // Run as needed to update display when the value being shown on it has changed
536597 // This formats the new value and puts it in displayNext[] for cycleDisplay() to pick up
537- if (fnSet) { // setting
538- // little tubes:
539- if (fn==fnSetup) editDisplay (fnSet, 4 , 5 , false ); // setup menu: current option key
540- else blankDisplay (4 , 5 ); // fn setting: blank
598+ if (fnSetPg) { // setting value
599+ // //little tubes:
600+ // if(fn==fnOpts) editDisplay(fnSetPg, 4, 5, false); //options menu: current option key
601+ // else //fn setting: blank
602+ blankDisplay (4 , 5 );
541603 // big tubes:
542604 if (fnSetValMax==1439 ) { // value is a time of day
543605 editDisplay (fnSetVal/60 , 0 , 1 , EEPROM.read (optsLoc[4 ])); // hours with leading zero
544606 editDisplay (fnSetVal%60 , 2 , 3 , true );
545607 } else editDisplay (fnSetVal, 0 , 3 , false ); // some other type of value
546608 }
547- else { // fn running
609+ else if (fn >= fnOpts){ // options menu, but not setting a value
610+ editDisplay (fn-fnOpts+1 ,0 ,1 ,0 ); // display option number on hour tubes
611+ blankDisplay (2 ,5 );
612+ } else { // fn running
548613 switch (fn){
549614 case fnIsTime:
550615 byte hr; hr = tod.hour ();
@@ -587,6 +652,13 @@ void updateDisplay(){
587652 editDisplay (abs (temp)/100 ,1 ,3 ,(temp<0 ?true :false )); // leading zeros if negative
588653 editDisplay (abs (temp)%100 ,4 ,5 ,true );
589654 break ;
655+ case fnIsCleaner:
656+ editDisplay (tod.second (),0 ,0 ,true );
657+ editDisplay (tod.second (),1 ,1 ,true );
658+ editDisplay (tod.second (),2 ,2 ,true );
659+ editDisplay (tod.second (),3 ,3 ,true );
660+ editDisplay (tod.second (),4 ,4 ,true );
661+ editDisplay (tod.second (),5 ,5 ,true );
590662 default : break ;
591663 }// end switch
592664 }
@@ -639,7 +711,7 @@ void initOutputs() {
639711
640712void cycleDisplay (){
641713 bool dim = 0 ;// (opts[2]>0?true:false); //Under normal circumstances, dim constantly if the time is right
642- if (fnSet >0 ) { // but if we're setting, dim for every other 500ms since we started setting
714+ if (fnSetPg >0 ) { // but if we're setting, dim for every other 500ms since we started setting
643715 if (setStartLast==0 ) setStartLast = millis ();
644716 dim = 1 -(((millis ()-setStartLast)/500 )%2 );
645717 } else {
0 commit comments