shelly_pool/pool.js

334 lines
7.3 KiB
JavaScript

print("[POOL] start");
let status = {
temp: null,
temp_max: null,
temp_today: null,
temp_yesterday: null,
update_time: null,
update_time_last: 0,
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_pump_skip: 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: ", 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 * 10) / 10;
status.temp_today = Math.max(status.temp_today, status.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);
Shelly.call (
"Sys.GetStatus",
{},
function (result) {
let time = result.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)) {
if ((t - status.update_time_last) > 0.15) { // 9 minutes
update_pump(status.temp, status.temp_max, t);
status.update_time_last = t;
status.update_temp_max_last = status.temp_max;
}
else {
status.tick_pump_skip++;
print("[POOL] to much update_pump, skipped", status.tick_pump_skip);
}
}
else {
print("[POOL] no temp change, skip update_pump");
}
}
);
status.lock_update = false;
}
// Set initial yesterday temp 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);
}
}
)
// 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++;
Shelly.call (
"Sys.GetStatus",
{},
function (result) {
print("[POOL] tick", result.time);
status.time = result.time;
status.uptime = result.uptime;
}
);
}
);