diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 0860a476d..44ca02078 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,6 +1,7 @@ /* 5.12.0l * Release rules up to 511 characters * Prepare for feature release - call on translators to update their language files + * Add timer sunrise and sunset offset (#2378) * * 5.12.0k * Prepare for simple rules of up to 255 characters by enlarging Settings area to now 2048 bytes diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino index e864e0062..c30cd153d 100644 --- a/sonoff/xdrv_09_timers.ino +++ b/sonoff/xdrv_09_timers.ino @@ -175,6 +175,43 @@ void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8 *minute_down = UntergangMinuten; } +void ApplyTimerOffsets(Timer *duskdawn) +{ + uint8_t hour[2]; + uint8_t minute[2]; + Timer stored = (Timer)*duskdawn; + + // replace hours, minutes by sunrise + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + uint8_t mode = (duskdawn->mode -1) &1; + duskdawn->time = (hour[mode] *60) + minute[mode]; + + // apply offsets, check for over- and underflows + uint16_t timeBuffer; + if ((uint16_t)stored.time > 720) { + // negative offset, time after 12:00 + timeBuffer = (uint16_t)stored.time - 720; + // check for underflow + if (timeBuffer > (uint16_t)duskdawn->time) { + timeBuffer = 1440 - (timeBuffer - (uint16_t)duskdawn->time); + duskdawn->days = duskdawn->days >> 1; + duskdawn->days = duskdawn->days |= (stored.days << 6); + } else { + timeBuffer = (uint16_t)duskdawn->time - timeBuffer; + } + } else { + // positive offset + timeBuffer = (uint16_t)duskdawn->time + (uint16_t)stored.time; + // check for overflow + if (timeBuffer > 1440) { + timeBuffer -= 1440; + duskdawn->days = duskdawn->days << 1; + duskdawn->days = duskdawn->days |= (stored.days >> 6); + } + } + duskdawn->time = timeBuffer; +} + String GetSun(byte dawn) { char stime[6]; @@ -212,23 +249,25 @@ void TimerEverySecond() for (byte i = 0; i < MAX_TIMERS; i++) { if (Settings.timer[i].device >= devices_present) Settings.timer[i].data = 0; // Reset timer due to change in devices present - uint16_t set_time = Settings.timer[i].time; + Timer xtimer = Settings.timer[i]; + uint16_t set_time = xtimer.time; #ifdef USE_SUNRISE - if ((1 == Settings.timer[i].mode) || (2 == Settings.timer[i].mode)) { // Sunrise or Sunset - set_time = GetSunMinutes(Settings.timer[i].mode -1); + if ((1 == xtimer.mode) || (2 == xtimer.mode)) { // Sunrise or Sunset + ApplyTimerOffsets(&xtimer); + set_time = xtimer.time; } #endif - if (Settings.timer[i].arm) { + if (xtimer.arm) { if (time == set_time) { - if (Settings.timer[i].days & days) { - Settings.timer[i].arm = Settings.timer[i].repeat; + if (xtimer.days & days) { + Settings.timer[i].arm = xtimer.repeat; #ifdef USE_RULES - if (3 == Settings.timer[i].power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands + if (3 == xtimer.power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1); RulesProcess(); } else #endif // USE_RULES - ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power); + ExecuteCommandPower(xtimer.device +1, xtimer.power); } } } @@ -241,17 +280,22 @@ void PrepShowTimer(uint8_t index) { char days[8] = { 0 }; - index--; + Timer xtimer = Settings.timer[index -1]; + for (byte i = 0; i < 7; i++) { uint8_t mask = 1 << i; - snprintf(days, sizeof(days), "%s%d", days, ((Settings.timer[index].days & mask) > 0)); + snprintf(days, sizeof(days), "%s%d", days, ((xtimer.days & mask) > 0)); } #ifdef USE_SUNRISE + int16_t hour = xtimer.time / 60; + if ((1 == xtimer.mode) || (2 == xtimer.mode)) { // Sunrise or Sunset + if (hour > 11) hour = (hour -12) * -1; + } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"), - mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].mode, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power); + mqtt_data, index, xtimer.arm, xtimer.mode, hour, xtimer.time % 60, days, xtimer.repeat, xtimer.device +1, xtimer.power); #else snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_ACTION "\":%d}"), - mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power); + mqtt_data, index, xtimer.arm, xtimer.time / 60, xtimer.time % 60, days, xtimer.repeat, xtimer.device +1, xtimer.power); #endif // USE_SUNRISE } @@ -298,18 +342,20 @@ boolean TimerCommand() #endif if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) { uint16_t itime = 0; - uint8_t value = 0; + int8_t value = 0; char time_str[10]; snprintf(time_str, sizeof(time_str), root[parm_uc]); const char *substr = strtok(time_str, ":"); if (substr != NULL) { value = atoi(substr); + if (value < 0) value = abs(value) +12; // Allow entering timer offset from -11:59 to -00:01 converted to 12:01 to 23:59 if (value > 23) value = 23; itime = value * 60; substr = strtok(NULL, ":"); if (substr != NULL) { value = atoi(substr); + if (value < 0) value = 0; if (value > 59) value = 59; itime += value; } @@ -417,25 +463,50 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM = "function gt(){" // Set hours and minutas according to mode "var m,p,q;" "m=qs('input[name=\"rd\"]:checked').value;" // Get mode - "if(m==0){p=pt[ct]&0x7FF;}" // Schedule time + "if(m==0){p=pt[ct]&0x7FF;so(0);}" // Schedule time, hide offset span "if(m==1){p=pt[" STR(MAX_TIMERS) "];}" // Sunrise "if(m==2){p=pt[" STR(MAX_TIMERS +1) "];}" // Sunset "q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours "q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes + "if((m==1)||(m==2)){" // Sunrise or sunset is set + "p=pt[ct]&0x7FF;" // Load stored time for offset calculation + "q=Math.floor(p/60);" // Parse hours + "if(q>=12){q-=12;qs('#odr').selectedIndex=1;}" // Negative offset + "else{qs('#odr').selectedIndex=0;}" + "if(q<10){q='0'+q;}qs('#oho').value=q;" // Set offset hours + "q=p%60;if(q<10){q='0'+q;}qs('#omi').value=q;" // Set offset minutes + "so(1);" // Show offset span + "}" + "}" + "function so(b){" // Hide or show offset items + "if(b==1){qs('#ofs').style='';}" + "else{qs('#ofs').style='display:none;';}" "}" #endif "function st(){" // Save parameters to hidden area - "var i,n,p,s;" - "s=0;" + "var i,l,m,n,p,s;" + "m=0;s=0;" "n=1<<30;if(eb('a0').checked){s|=n;}" // Get arm "n=1<<29;if(eb('r0').checked){s|=n;}" // Get repeat "for(i=0;i<7;i++){n=1<<(16+i);if(eb('w'+i).checked){s|=n;}}" // Get weekdays #ifdef USE_SUNRISE + "m=qs('input[name=\"rd\"]:checked').value;" // Check mode "s|=(qs('input[name=\"rd\"]:checked').value<<11);" // Get mode #endif "s|=(eb('p1').value<<27);" // Get power "s|=(qs('#d1').selectedIndex<<23);" // Get device - "s|=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;" // Get time + +// "s|=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;" // Get time + + "if(m==0){s|=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;}" // Get time +#ifdef USE_SUNRISE + "if((m==1)||(m==2)){" + "l=((qs('#oho').selectedIndex*60)+qs('#omi').selectedIndex);" // Buffer offset time + "if(qs('#odr').selectedIndex>0){l+=720;}" // If negative offset, add 12h to given offset time + "s|=l&0x7FF;" // Save offset instead of time + "}" +#endif + "pt[ct]=s;" "eb('t0').value=pt.join();" // Save parameters from array to hidden area "}" @@ -468,6 +539,13 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM = "eb('bt').innerHTML=s;" // Create tabs "o=qs('#ho');for(i=0;i<=23;i++){ce((i<10)?('0'+i):i,o);}" // Create hours select options "o=qs('#mi');for(i=0;i<=59;i++){ce((i<10)?('0'+i):i,o);}" // Create minutes select options + +#ifdef USE_SUNRISE // NEW: Create offset options (+/- up to 11h, 59m) + "o=qs('#odr');ce('+',o);ce('-',o);" // Create offset direction select options + "o=qs('#oho');for(i=0;i<=11;i++){ce((i<10)?('0'+i):i,o);}" // Create offset hours select options + "o=qs('#omi');for(i=0;i<=59;i++){ce((i<10)?('0'+i):i,o);}" // Create offset minutes select options +#endif + "o=qs('#d1');for(i=0;i<}1;i++){ce(i+1,o);}" // Create devices "var a='" D_DAY3LIST "';" "s='';for(i=0;i<7;i++){s+=\"\"+a.substring(i*3,(i*3)+3)+\"\"}" @@ -503,15 +581,22 @@ const char HTTP_FORM_TIMER1[] PROGMEM = "" D_TIMER_REPEAT "" "
" "
" -// "Time " #ifdef USE_SUNRISE "
" "" D_TIMER_TIME " " - "" - " " D_HOUR_MINUTE_SEPARATOR " " - "
" + "" + " " D_HOUR_MINUTE_SEPARATOR " " + "
" "" D_SUNRISE "
" "" D_SUNSET "
" + "
" "
" #else "" D_TIMER_TIME " " @@ -573,7 +658,7 @@ void TimerSaveSettings() p++; // Skip comma if (timer.time < 1440) { #ifdef USE_SUNRISE - if ((1 == timer.mode) || (2 == timer.mode)) timer.time = Settings.timer[i].time; // Do not save time on Sunrise or Sunset +// if ((1 == timer.mode) || (2 == timer.mode)) timer.time = Settings.timer[i].time; // Do not save time on Sunrise or Sunset #endif Settings.timer[i].data = timer.data; }