26
26
27
27
// //////// Configuration consts //////////
28
28
29
- // available clock functions, and unique IDs (between 0 and 199 )
29
+ // available clock functions, and unique IDs (between 0 and 200 )
30
30
const byte fnIsTime = 0 ;
31
31
const byte fnIsDate = 1 ;
32
32
const byte fnIsAlarm = 2 ;
33
33
const byte fnIsTimer = 3 ;
34
34
const byte fnIsDayCount = 4 ;
35
35
const byte fnIsTemp = 5 ;
36
+ const byte fnIsCleaner = 6 ;
36
37
// 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 };
38
39
39
40
// These are the RLB board connections to Arduino analog input pins.
40
41
// S1/PL13 = Reset
@@ -79,9 +80,11 @@ const byte alarmRadio = 0;
79
80
// When timer is running, output will stay on until timer runs down.
80
81
const byte alarmDur = 3 ;
81
82
83
+ const byte displaySize = 4 ; // 4 or 6 - causes small differences in display of timer, etc
84
+
82
85
// How long (in ms) are the button hold durations?
83
86
const 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
85
88
const byte velThreshold = 100 ; // ms
86
89
// When an adj up/down input (btn or rot) follows another in less than this time, value will change more (10 vs 1).
87
90
// 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)
93
96
const byte dayCountMonthLoc = 5 ; // byte
94
97
const byte dayCountDateLoc = 6 ; // byte
95
98
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
98
101
// 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
100
103
// Option number: - 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
101
104
const byte optsLoc[] = {0 ,16 ,17 ,18 ,19 ,20 ,21 ,22 ,23 ,24 , 25 ,27 , 28 , 30 ,32 ,33 ,34 , 35 , 37 }; // EEPROM locs
102
105
const 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
119
122
unsigned long inputLast = 0 ; // When a button was last pressed
120
123
unsigned long inputLast2 = 0 ; // Second-to-last of above
121
124
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
123
126
byte 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
125
128
word fnSetVal; // the value currently being set, if any - unsigned int 0-65535
126
129
word fnSetValMin; // min possible - unsigned int
127
130
word fnSetValMax; // max possible - unsigned int
@@ -138,7 +141,7 @@ byte displayNext[6] = {15,15,15,15,15,15}; //Internal representation of display.
138
141
// //////// Main code control //////////
139
142
140
143
void setup (){
141
- Serial.begin (57600 );
144
+ Serial.begin (9600 );
142
145
Wire.begin ();
143
146
initOutputs ();
144
147
initInputs ();
@@ -256,14 +259,22 @@ void ctrlEvt(byte ctrl, byte evt){
256
259
// But we can handle short and long holds and releases for the sel ctrls (always buttons).
257
260
// TODO needs alt handling
258
261
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 ;
263
270
}
264
271
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");
267
278
switch (fn){
268
279
case fnIsTime: // set mins
269
280
startSet ((tod.hour ()*60 )+tod.minute (),0 ,1439 ,1 ); break ;
@@ -281,39 +292,45 @@ void ctrlEvt(byte ctrl, byte evt){
281
292
break ; // nothing - or is this where we do the calibration? TODO
282
293
default : break ;
283
294
}
295
+ // showSitch();
284
296
return ;
285
297
}
286
298
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");
287
300
// -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
289
302
bool fnChgd = false ;
290
303
if (ctrl==mainSel && mainSelFn!=-1 ) {
304
+ // Serial.println(" mainSel release: go to next fn in the cycle");
291
305
fnChgd = true ;
292
306
if (mainSelFn==-2 ) fnScroll (1 ); // Go to next fn in the cycle
293
307
else fn = mainSelFn;
294
308
}
295
309
else if ((ctrl==mainAdjUp || ctrl==mainAdjDn) && mainAdjFn!=-1 ) {
310
+ // Serial.println(" mainAdj press: go to prev/next fn in the cycle");
296
311
fnChgd = true ;
297
312
if (mainAdjFn==-2 ) fnScroll (ctrl==mainAdjUp?1 :-1 ); // Go to next or previous fn in the cycle
298
313
else fn = mainAdjFn;
299
314
}
300
315
if (fnChgd){
301
316
switch (fn){
302
- // "ticking" ones
303
- case fnIsTime: case fnIsDate: case fnIsTimer: case fnIsDayCount:
304
- checkRTC (true ); break ;
305
317
// static ones
306
318
case fnIsAlarm: case fnIsTemp:
307
319
updateDisplay (); break ;
308
- default : break ;
320
+ // "ticking" ones
321
+ default : checkRTC (true ); break ;
309
322
}
310
323
}
311
324
}
312
325
} // end fn running
313
326
314
327
else { // fn setting
328
+ // Serial.print("fn ");
329
+ // Serial.print(fn,DEC);
330
+ // Serial.println(" setting");
315
331
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");
317
334
btnStop (); // not waiting for mainSelHold, so can stop listening here
318
335
// We will set ds3231 time parts directly
319
336
// 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){
324
341
ds3231.setSecond (0 ); // will reset to exactly 0? TODO confirm
325
342
clearSet (); break ;
326
343
case fnIsDate: // save in RTC
327
- switch (fnSet ){
344
+ switch (fnSetPg ){
328
345
case 1 : // save year, set month
329
346
fnSetValDate[0 ]=fnSetVal;
330
347
startSet (fnSetValDate[1 ],1 ,12 ,2 ); break ;
@@ -344,7 +361,7 @@ void ctrlEvt(byte ctrl, byte evt){
344
361
timerTime = fnSetVal;
345
362
break ;
346
363
case fnIsDayCount: // set like date, save in eeprom like finishOpt
347
- switch (fnSet ){
364
+ switch (fnSetPg ){
348
365
case 1 : // save year, set month
349
366
writeEEPROM (dayCountYearLoc,fnSetVal,true );
350
367
startSet (readEEPROM (dayCountMonthLoc,false ),1 ,12 ,2 ); break ;
@@ -364,42 +381,83 @@ void ctrlEvt(byte ctrl, byte evt){
364
381
} // end mainSel push
365
382
if (ctrl==mainAdjUp) doSet (inputLast-inputLast2<velThreshold ? 10 : 1 );
366
383
if (ctrl==mainAdjDn) doSet (inputLast-inputLast2<velThreshold ? -10 : -1 );
384
+ // showSitch();
367
385
} // end if evt==1
368
386
} // end fn setting
369
-
387
+
370
388
} // 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);
372
393
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();
386
403
}
387
- if (ctrl==mainAdjUp && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? 10 : 1 );
388
- if (ctrl==mainAdjDn && evt==1 ) doSet (inputLast-inputLast2<velThreshold ? -10 : -1 );
389
404
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
+
391
431
} // end ctrlEvt
392
432
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
+
393
445
void fnScroll (char dir){
394
446
// Switch to the next (1) or previous (-1) fn in fnsEnabled
395
447
byte pos;
396
448
byte posLast = sizeof (fnsEnabled)-1 ;
397
449
if (dir==1 ) for (pos=0 ; pos<=posLast; pos++) if (fnsEnabled[pos]==fn) { fn = (pos==posLast?0 :fnsEnabled[pos+1 ]); break ; }
398
450
if (dir==-1 ) for (pos=posLast; pos>=0 ; pos--) if (fnsEnabled[pos]==fn) { fn = (pos==0 ?posLast:fnsEnabled[pos-1 ]); break ; }
399
451
}
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
+ }
400
458
401
459
void 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;
403
461
updateDisplay ();
404
462
}
405
463
void doSet (int delta){
@@ -414,7 +472,7 @@ void doSetHold(){
414
472
// TODO integrate this with checkInputs?
415
473
if (doSetHoldLast+250 <millis ()) {
416
474
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
418
476
bool dir = (btnCur==mainAdjUp || btnCur==altAdjUp ? 1 : 0 );
419
477
// If short hold, or long hold but high velocity isn't supported, use low velocity (delta=1)
420
478
if (btnCurHeld==2 || (btnCurHeld==3 && fnSetValVel==false )) doSet (dir?1 :-1 );
@@ -425,14 +483,7 @@ void doSetHold(){
425
483
}
426
484
void clearSet (){ // Exit set state
427
485
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()
436
487
}
437
488
438
489
// EEPROM values are exclusively bytes (0-255) or words (unsigned ints, 0-65535)
@@ -448,14 +499,14 @@ void initEEPROM(){
448
499
ds3231.setHour (0 );
449
500
ds3231.setMinute (0 );
450
501
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
452
503
// writeEEPROM(alarmTimeLoc,420,true); //7am - word
453
504
// writeEEPROM(alarmOnLoc,enableSoftAlarmSwitch==0?1:0,false); //off, or on if no software switch spec'd
454
505
writeEEPROM (dayCountYearLoc,2018 ,true );
455
506
writeEEPROM (dayCountMonthLoc,1 ,false );
456
507
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
459
510
}
460
511
word readEEPROM (int loc, bool isWord){
461
512
if (isWord) {
@@ -510,8 +561,18 @@ void checkRTC(bool force){
510
561
// Checks for new time-of-day second; decrements timer; checks for timed events;
511
562
// updates display for running time or date.
512
563
// 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
+ }
515
576
// Update things based on RTC
516
577
tod = rtc.now ();
517
578
// toddow = ds3231.getDoW();
@@ -524,7 +585,7 @@ void checkRTC(bool force){
524
585
// trip minutely date at :30 TODO
525
586
// trip digit cycle TODO
526
587
527
- if (fnSet ==0 ) updateDisplay ();
588
+ if (fnSetPg ==0 ) updateDisplay ();
528
589
529
590
} // end if force or new second
530
591
} // end checkRTC()
@@ -534,17 +595,21 @@ void checkRTC(bool force){
534
595
void updateDisplay (){
535
596
// Run as needed to update display when the value being shown on it has changed
536
597
// 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 );
541
603
// big tubes:
542
604
if (fnSetValMax==1439 ) { // value is a time of day
543
605
editDisplay (fnSetVal/60 , 0 , 1 , EEPROM.read (optsLoc[4 ])); // hours with leading zero
544
606
editDisplay (fnSetVal%60 , 2 , 3 , true );
545
607
} else editDisplay (fnSetVal, 0 , 3 , false ); // some other type of value
546
608
}
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
548
613
switch (fn){
549
614
case fnIsTime:
550
615
byte hr; hr = tod.hour ();
@@ -587,6 +652,13 @@ void updateDisplay(){
587
652
editDisplay (abs (temp)/100 ,1 ,3 ,(temp<0 ?true :false )); // leading zeros if negative
588
653
editDisplay (abs (temp)%100 ,4 ,5 ,true );
589
654
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 );
590
662
default : break ;
591
663
}// end switch
592
664
}
@@ -639,7 +711,7 @@ void initOutputs() {
639
711
640
712
void cycleDisplay (){
641
713
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
643
715
if (setStartLast==0 ) setStartLast = millis ();
644
716
dim = 1 -(((millis ()-setStartLast)/500 )%2 );
645
717
} else {
0 commit comments