|
|
|
@ -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+=\"<input style='width:5%;' id='w\"+i+\"' name='w\"+i+\"' type='checkbox'><b>\"+a.substring(i*3,(i*3)+3)+\"</b>\"}"
|
|
|
|
@ -503,15 +581,22 @@ const char HTTP_FORM_TIMER1[] PROGMEM =
|
|
|
|
|
"<input style='width:5%;' id='r0' name='r0' type='checkbox'><b>" D_TIMER_REPEAT "</b>"
|
|
|
|
|
"</div><br/>"
|
|
|
|
|
"<div>"
|
|
|
|
|
// "<b>Time</b> <input type='time' style='width:25%;' id='s1' name='s1' value='00:00' pattern='[0-9]{2}:[0-9]{2}'>"
|
|
|
|
|
#ifdef USE_SUNRISE
|
|
|
|
|
"<fieldset style='width:299px;margin:auto;text-align:left;border:0;'>"
|
|
|
|
|
"<input id='b0' name='rd' type='radio' value='0' onclick='gt();'><b>" D_TIMER_TIME "</b> "
|
|
|
|
|
"<span><select style='width:60px;' id='ho' name='ho' onclick='eb(\"b0\").checked=1;'></select></span>"
|
|
|
|
|
"<span><select style='width:60px;' id='ho' name='ho' onclick='eb(\"b0\").checked=1;so(0);'></select></span>"
|
|
|
|
|
" " D_HOUR_MINUTE_SEPARATOR " "
|
|
|
|
|
"<span><select style='width:60px;' id='mi' name='mi' onclick='eb(\"b0\").checked=1;'></select></span><br/>"
|
|
|
|
|
"<span><select style='width:60px;' id='mi' name='mi' onclick='eb(\"b0\").checked=1;so(0);'></select></span><br/>"
|
|
|
|
|
"<input id='b1' name='rd' type='radio' value='1' onclick='gt();'><b>" D_SUNRISE "</b><br/>"
|
|
|
|
|
"<input id='b2' name='rd' type='radio' value='2' onclick='gt();'><b>" D_SUNSET "</b><br/>"
|
|
|
|
|
"<span id='ofs' style='display:none;'>"
|
|
|
|
|
" "
|
|
|
|
|
"<span><select style='width:46px;' id='odr' name='odr'></select></span>"
|
|
|
|
|
" "
|
|
|
|
|
"<span><select style='width:60px;' id='oho' name='oho'></select></span>"
|
|
|
|
|
" " D_HOUR_MINUTE_SEPARATOR " "
|
|
|
|
|
"<span><select style='width:60px;' id='omi' name='omi'></select></span>"
|
|
|
|
|
"</span><br/>"
|
|
|
|
|
"</fieldset>"
|
|
|
|
|
#else
|
|
|
|
|
"<b>" D_TIMER_TIME "</b> "
|
|
|
|
@ -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;
|
|
|
|
|
}
|
|
|
|
|