commit a09fd254621c3e95dcef650c9d83ecd018ecd5a4 Author: Gilles Grandou Date: Thu Apr 21 09:32:39 2022 +0200 initial commit diff --git a/pool.js b/pool.js new file mode 100644 index 0000000..d60fd63 --- /dev/null +++ b/pool.js @@ -0,0 +1,246 @@ +print("[POOL] start"); + +let status = { + temp: null, + temp_max: null, + temp_today: null, + temp_yesterday: null, + + update_time: null, + + disable_temp: null, + + tick_timer: 0, + tick_day: 0, +}; + +// compute duration of filtration for a given max temperature +// duration is returned in float format (1.25 -> 1h 15mn) + +function compute_duration_filt(t) { + if (t < 5) + return 1; + if (t < 10) + return (t/5); // 1 -> 2 + if (t < 12) + return (t-8); // 2 -> 4 + if (t < 16) + return (t/2-2); // 4 -> 6 + if (t < 24) + return (t/4+2); // 6 -> 8 + if (t < 27) + return (t*4/3-24) // 8 -> 12 + if (t < 30) + return (t*4 - 96); // 12 -> 24 + return 24; +} + +// 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; +} + +// update temperature from Sensor + +function update_temp(temp) { + print("[POOL] update_temp", temp); + if (status.disable_temp !== null) { + print("[POOL] updated disabled"); + return; + } + + status.temp = temp; + status.temp_today = Math.max(status.temp_today, temp); + 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); +} + +// 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; +} + +// 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) { + print("[POOL] update_pump", temp, max, time); + + let duration = compute_duration_filt(max); + let schedule = compute_schedule_filt(duration); + + print("[POOL] update_pump - duration:", duration); + print("[POOL] update_pump - schedule:", JSON.stringify(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; + let j = false; + for (let i = 0; i < schedule.length; i++) { + j = !j; + if (time >= schedule[i]) + on = j; + } + + calls.push({method: "Switch.Set", params: {id: 0, on: on}}); + + do_call(calls); +} + +// + +function update_pool() { + Shelly.call ( + "Sys.GetStatus", + {}, + function (result) { + let time = result.time; // "HH:MM" + print("[POOL] timer", 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) + update_pump(status.temp, status.temp_max, t); + } + ); +} + +// receives updated from Pool Sensor + +MQTT.subscribe( + "zigbee2mqtt/Piscine", + function (topic, msg) { + 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") { + print("[POOL] disable temp"); + + 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; + } + ); + } + } +); + + +// Run once per hour +// - update transition today -> yesterday +// - update pump schedule + +Timer.set ( + 3600 * 1000, + true, + function () { + status.tick_timer++; + print("[POOL] timer", status.tick_timer); + + update_pool(); + } +); +