print("[POOL] start"); let status = { temp: null, temp_max: null, temp_today: null, temp_yesterday: null, update_time: null, update_temp_max_last: null, disable_temp: null, lock_update: false, duration: null, schedule: null, time: null, uptime: null, tick: 0, tick_mqtt: 0, tick_temp: 0, tick_lock: 0, tick_pump: 0, tick_day: 0, }; // temperature->duration chart let filt_time = [ [ 5, 1.0 ], // at 5°C, 1 hour of filtering [ 10, 2.0 ], [ 12, 4.0 ], [ 16, 6.0 ], [ 24, 8.0 ], [ 27, 12.0 ], [ 30, 24.0 ] ]; // compute filtration time for a given max temperature // duration is returned in float format (1.25 -> 1h 15mn) function compute_filtration_time(t) { let len = filt_time.length; if (t < filt_time[0][0]) return filt_time[0][1]; for (let i = 0; i < len-1; i++) { if (t >= filt_time[i][0] && t < filt_time[i+1][0]) { // linear interpolation between two points return filt_time[i][1] + (filt_time[i+1][1] - filt_time[i][1]) / (filt_time[i+1][0] - filt_time[i][0]) * (t - filt_time[i][0]); } } return filt_time[len-1][1]; } // compute the pump schedule for a given duration // returns an array of start/stop times in float // [ start1, stop1, start2, stop2, ... ] function compute_schedule_filt(d) { let s = null; if (d < 2) { s = [ 9, 9+d ]; } else if (d < 8) { s = [ 9, 10, 14, 14+d-1 ]; } else if (d < 11) { s = [ 9, 11, 14, 14+d-2 ]; } else if (d < 14) { s = [ 9, 9+d-9, 14, 23]; } else if (d < 15) { s = [ 9, 9+d ]; } else if (d < 18) { s = [ 24-d, 0 ]; } else if (d < 24) { s = [ 6, d-18 ]; } else { s = [ 6 ]; } return s; } // convert a time to a crontab-like timespec function time_to_timespec(t) { let h = Math.floor(t); let m = Math.floor((t-h)*60); let ts = "0 " + JSON.stringify(m) + " " + JSON.stringify(h) + " * * SUN,MON,TUE,WED,THU,FRI,SAT"; return ts; } // new day, update status function update_new_day() { status.tick_day++; print("[POOL] update_new_day", status.tick_day); status.temp_yesterday = status.temp_today; status.temp_today = null; Shelly.call( "KVS.Set", { key: "pool.temp_yesterday", value: status.temp_yesterday }, function (result) { print("[POOL] KVS set temp_yesterday: ", JSON.stringify(result)); } ); Shelly.call( "KVS.Set", { key: "pool.temp_today", value: null }, function (result) { print("[POOL] KVS set temp_today: ", JSON.stringify(result)); } ); } // call a chain of API calls function do_call(calls) { if (calls.length === 0) { print("[POOL] call: done."); return; } let m = calls[0].method; let p = calls[0].params; calls.splice(0, 1); print("[POOL] call:", m, JSON.stringify(p)); Shelly.call( m, p, function (r, errc, errm, _calls) { do_call(_calls); }, calls ); } // compute & configure pump schedule // TODO force on when duration==24 // TODO force off when duration==0 // TODO freeze mode if cur < 1 function update_pump(temp, max, time) { status.tick_pump++; print("[POOL] update_pump", status.tick_pump, "- temp:", temp, "max:", max, "time:", time); let duration = compute_filtration_time(max); let schedule = compute_schedule_filt(duration); print("[POOL] update_pump - duration:", duration); print("[POOL] update_pump - schedule:", JSON.stringify(schedule)); status.duration = duration; status.schedule = schedule; let calls = []; calls.push({method: "Schedule.DeleteAll", params: null}); let on = true; for (let i = 0; i < schedule.length; i++) { let ts = time_to_timespec(schedule[i]); let p = { id: i+1, enable: true, timespec: ts, calls: [{ method: "Switch.Set", params: { id: 0, on: on } }] }; calls.push({method: "Schedule.Create", params: p}); on = !on; } // compute the current switch state according to the schedule let on = false; if (schedule.length === 1) { on = true; } else { for (let i = 0; i < schedule.length; i+=2) { let a=schedule[i]; let b=schedule[i+1]; if (a= a && time < b) { on = true; } else if (a>b && (time >= a || time < b)) { on = true; } } } calls.push({method: "Switch.Set", params: {id: 0, on: on}}); do_call(calls); } // update temperature from Sensor // - update max temp // - retrieve current time // - switch to new day // - do a pump update if the last one didn't happen too close and if max temp has changed function update_temp(temp) { status.tick_temp++; print("[POOL] update_temp", status.tick_temp, temp); if (status.disable_temp !== null) { print("[POOL] update disabled"); return; } if (status.lock_update) { print("[POOL] update_temp locked"); return; } status.lock_update = true; status.temp = Math.round(temp * 2) / 2; if (status.temp > status.temp_today || status.temp_today === null) { status.temp_today = status.temp; Shelly.call ( "KVS.Set", { key: "pool.temp_today", value: status.temp_today }, function (result) { print("[POOL] KVS set temp_today: ", JSON.stringify(result)); } ); } status.temp_max = Math.max(status.temp_today, status.temp_yesterday); print("[POOL] update_temp - max:", status.temp_max, "today:", status.temp_today, "yesterday:", status.temp_yesterday); let time = Shelly.getComponentStatus("sys").time; // "HH:MM" print("[POOL] time", time); // compute current time in float format (12h45 -> 12.75) let t = JSON.parse(time.slice(0,2)) + JSON.parse(time.slice(3,5)) / 60; if (t < status.update_time) update_new_day(); status.update_time = t; if ((status.temp_max !== null) && (status.temp_max !== status.update_temp_max_last)) { update_pump(status.temp, status.temp_max, t); status.update_temp_max_last = status.temp_max; } else { print("[POOL] no temp change, skip update_pump"); } status.lock_update = false; } // Set initial temps from KVS Shelly.call ( "KVS.Get", { key: "pool.temp_yesterday" }, function (result) { if (result) { status.temp_yesterday = result.value; print("[POOL] Restore from KVS: temp_yesterday:", status.temp_yesterday); } } ) Shelly.call ( "KVS.Get", { key: "pool.temp_today" }, function (result) { if (result) { status.temp_today = result.value; print("[POOL] Restore from KVS: temp_today:", status.temp_today); if (status.temp_today !== null) update_temp(status.temp_today); } } ) // receives update from Pool Sensor // - trigger all temperature and pump updates MQTT.subscribe( "zigbee2mqtt/Piscine", function (topic, msg) { status.tick_mqtt++; print("[POOL] mqtt", topic); let obj = JSON.parse(msg); if (obj.temperature === undefined) { return; } update_temp(obj.temperature); } ) // after a Switch Event, disable temp update // during 10 minutes Shelly.addEventHandler( function (data) { if (data.info.event === "toggle") { status.tick_lock++; print("[POOL] disable temp", status.tick_lock); if (status.disable_temp !== null) Timer.clear(status.disable_temp); status.disable_temp = Timer.set( 600 * 1000, false, function () { print("[POOL] re-enable temp"); status.disable_temp = null; } ); } } ); // Debug... Timer.set( 60 * 1000, true, function() { status.tick++; let result = Shelly.getComponentStatus("sys"); print("[POOL] tick", result.time); status.time = result.time; status.uptime = result.uptime; } );