Gilles Grandou
2014350bc7
* filtration time is now fixed inside each range, instead of trying to follow linear curve * don't bother being super accurate, that's too much over-engineering after all ;)
352 lines
7.6 KiB
JavaScript
352 lines
7.6 KiB
JavaScript
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, 2.0 ], // at 5°C, 2 hours of filtering
|
|
[ 8, 3.0 ],
|
|
[ 10, 4.0 ],
|
|
[ 11, 5.0 ],
|
|
[ 12, 6.0 ],
|
|
[ 14, 7.0 ],
|
|
[ 16, 8.0 ],
|
|
[ 18, 9.0 ],
|
|
[ 20, 10.0 ],
|
|
[ 22, 11.0 ],
|
|
[ 24, 12.0 ],
|
|
[ 25, 14.0 ],
|
|
[ 26, 17.0 ],
|
|
[ 27, 20.0 ],
|
|
[ 28, 22.0 ],
|
|
[ 29, 23.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 start = 17 - d/2;
|
|
let stop = 17 + d/2;
|
|
|
|
while (stop >= 24)
|
|
stop = stop - 24;
|
|
|
|
if (d < 24) {
|
|
let s = [ start, stop ];
|
|
}
|
|
else {
|
|
let s = [ start ];
|
|
}
|
|
|
|
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<b && time >= 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, msg);
|
|
if (msg == "") {
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
);
|
|
|