diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index c441078ef..44ca02078 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,9 @@ -/* 5.12.0k +/* 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 * Change Timer parameter name from Power to Action * Add commands Publish, Rule, RuleTimer and Event. See Wiki about Rule restriction, usage and examples diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 7e6bc2c97..54b151ecc 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -25,7 +25,7 @@ - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ -#define VERSION 0x050C000B // 5.12.0k +#define VERSION 0x050C000C // 5.12.0l // Location specific includes #include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0) diff --git a/sonoff/support.ino b/sonoff/support.ino index 6b17f0b75..e200d0240 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -1593,10 +1593,10 @@ void AddLog_P(byte loglevel, const char *formatP, const char *formatP2) AddLog(loglevel); } -void AddLogSerial(byte loglevel, uint8_t *buffer, byte count) +void AddLogSerial(byte loglevel, uint8_t *buffer, int count) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_SERIAL D_RECEIVED)); - for (byte i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, *(buffer++)); } AddLog(loglevel); diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino index 9af486d38..db8864e44 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; } diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index 61742f0b6..690b5a934 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -40,10 +40,10 @@ * on rules#timer=1 do color 080800 endon * on mqtt#connected do color 000010 endon on mqtt#disconnected do color 001010 endon on time#initialized do color 001000 endon on time#set do backlog color 000810;ruletimer1 10 endon on rules#timer=1 do color 080800 endon * on event#anyname do color 100000 endon - * on event#anyname do color %eventvalue% endon + * on event#anyname do color %value% endon * on power1#state=1 do color 001000 endon - * on button1#state do publish cmnd/ring2/power %eventvalue% endon on button2#state do publish cmnd/strip1/power %eventvalue% endon - * on switch1#state do power2 %eventvalue% endon + * on button1#state do publish cmnd/ring2/power %value% endon on button2#state do publish cmnd/strip1/power %value% endon + * on switch1#state do power2 %value% endon * * Notes: * Spaces after , around and before are mandatory @@ -185,7 +185,7 @@ bool RulesRuleMatch(String &event, String &rule) if (!root[rule_task][rule_name].success()) return false; // No value but rule_name is ok - rules_event_value = str_value; // Prepare %eventvalue% + rules_event_value = str_value; // Prepare %value% // Step 3: Compare rule (value) if (str_value) { @@ -260,7 +260,7 @@ bool RulesProcess() rules_event_value = ""; String event = event_saved; if (RulesRuleMatch(event, event_trigger)) { - commands.replace(F("%eventvalue%"), rules_event_value); + commands.replace(F("%value%"), rules_event_value); char command[commands.length() +1]; snprintf(command, sizeof(command), commands.c_str());