====== Quick project: Adding support for swimmingpool pump activation schedule [nodejs] ====== {{tag>dev nodejs}} Hi everyone, here is a very quick post on the update I'm adding on my "NervSwimPool" project, which is a simple NodeJS project I built to get remote access to our swimmingpool devices (pump, robot, light, etc). I could explain this further to give a bit more context for this article, but, I feel lazy to do it, because I already lost some good time restoring my network access to the raspberry pi device hosting the program (stupid old/invalid network gateway setting of course). => So let's just get straight into the problem. ====== ====== ===== Main app entry point ===== * Oaky, so looking into the NervSwimPool project repository, I now see that I have a main (single) **src/index.ts** file, ending with this content: // add router in the Express app. app.use("/", router); // start the Express server let server = app.listen(port, () => { console.log(`server started at http://localhost:${port}`); }); process.on('SIGINT', _ => { console.log("process killed: releasing GPIO pins.") for (var i = 0; i * So we start a **server** at that location, all good. But what I need now is to also periodically read a config file, and then check if I should activate the pool pump at the current time or not. * **Side Note**: just found [[https://www.vultr.com/docs/how-to-create-a-restful-api-using-python-and-fastapi-3398/?utm_source=google-emea&utm_medium=paidmedia&obility_id=17096555207&utm_adgroup=&utm_campaign=&utm_term=&utm_content=&gclid=Cj0KCQjwpv2TBhDoARIsALBnVnkqmxdQ4kxAha7Z13cDj7wQG8On1d9-9MKu6qz-Va6Kk9ljKPRWI28aAuOwEALw_wcB|this page]] on the building of a REST API in python: if I had to redo this project again that's how I would do it I think. ===== Adding support for schedule handling ===== * => To handle this automatic schedule I added the following section in the swimpool.json file: "pump_schedule": [ { "start": "10:00", "end": "12:00" }, { "start": "00:00", "end": "03:00" } ] * And then I added a function to handle those schedule entries: function process_pump_schedule() { // Should read the contend of our config file: console.log("Processing pump schedule..."); let cfg = getConfig(); let ranges: any = cfg["pump_schedule"]; // Get the current time: const cur_date = new Date(); const cur_hour = cur_date.getHours(); const cur_min = cur_date.getMinutes(); const cur_ts = cur_hour * 60 + cur_min; let pinIdx: number = pinMap["pool_pump"]; for (let range of ranges) { // Check if we just crossed the "start" time: let parts = range["start"].split(":"); let start_ts = parts[0] * 60 + parts[1]; if (cur_ts > start_ts && cur_ts - 60 < start_ts) { // Should start the pump here: console.log("Enabling pump at " + range["start"]); pins[pinIdx].writeSync(Gpio.LOW); } parts = range["end"].split(":"); let end_ts = parts[0] * 60 + parts[1]; if (cur_ts > end_ts && cur_ts - 60 < end_ts) { // Should stop the pump here: console.log("Disabling pump at " + range["end"]); pins[pinIdx].writeSync(Gpio.HIGH); } } } * Finally, calling this function every minute: var interval_id = setInterval(process_pump_schedule, 60000); * => Let's now restart the server: * First I kill the existing node process: $ ps aux | grep node * Then I run the process manually with: $ sudo /home/ubuntu/swimmingpool/scripts/start_pool_server.sh ===== Fixing the timezone ===== * But the timezone on that system is not correct: ubuntu@ubuntu:~$ date Sat May 14 11:45:14 UTC 2022 ubuntu@ubuntu:~$ timedatectl Local time: Sat 2022-05-14 11:45:46 UTC Universal time: Sat 2022-05-14 11:45:46 UTC RTC time: n/a Time zone: Etc/UTC (UTC, +0000) System clock synchronized: yes NTP service: active RTC in local TZ: no * I found this page on how to set the timezone on ubuntu: https://linuxize.com/post/how-to-set-or-change-timezone-on-ubuntu-20-04/ * So now setting the correct timezone: $ timedatectl list-timezones $ timedatectl set-timezone Europe/Paris * **OK**, now we are good: ubuntu@ubuntu:~$ timedatectl Local time: Sat 2022-05-14 13:49:13 CEST Universal time: Sat 2022-05-14 11:49:13 UTC RTC time: n/a Time zone: Europe/Paris (CEST, +0200) System clock synchronized: yes NTP service: active RTC in local TZ: no ubuntu@ubuntu:~$ date Sat May 14 13:49:52 CEST 2022 ===== Updated pump schedule function ===== * After some additional tweaking, here is the final version of the ''process_pump_schedule'' function I got: function process_pump_schedule() { // Should read the contend of our config file: let cfg = getConfig(); let ranges: any = cfg["pump_schedule"]; // Get the current time: const cur_date = new Date(); const cur_hour = cur_date.getHours(); const cur_min = cur_date.getMinutes(); const cur_ts = cur_hour * 60 + cur_min; // console.log("Processing pump schedule at " + cur_hour + ":" + cur_min); // console.log("Current timestamp: " + cur_ts); let pinIdx: number = pinMap["pool_pump"]; for (let range of ranges) { // Check if we just crossed the "start" time: let parts = range["start"].split(":"); // console.log("parts: " + JSON.stringify(parts)); let start_ts = Number(parts[0]) * 60 + Number(parts[1]); // console.log("Checking start timestamp: " + start_ts); if ( cur_ts >= start_ts && cur_ts - 2 < start_ts && pins[pinIdx].readSync() == Gpio.HIGH ) { // Should start the pump here: console.log(cur_date.toLocaleString() + ": Enabling pool pump."); pins[pinIdx].writeSync(Gpio.LOW); } parts = range["end"].split(":"); // console.log("parts: " + JSON.stringify(parts)); let end_ts = Number(parts[0]) * 60 + Number(parts[1]); // console.log("Checking end timestamp: " + end_ts); if ( cur_ts >= end_ts && cur_ts - 2 < end_ts && pins[pinIdx].readSync() == Gpio.LOW ) { // Should stop the pump here: console.log(cur_date.toLocaleString() + ": Disabling pool pump."); pins[pinIdx].writeSync(Gpio.HIGH); } } } ===== Auto-disable pool robot ===== * While I'm at it, I think I should also add support to automatically disable to pool robot after some time of cleaning (like 2hours maybe): Let's add support for that. * To achieve this I updated the **/handle** endpoint as follow: var robot_timeout: any = null; function stop_robot() { let pinIdx: number = pinMap["pool_robot"]; if (pins[pinIdx].readSync() == Gpio.LOW) { const cur_date = new Date(); console.log( cur_date.toLocaleString() + ": Automatic stop of robot cleaning session." ); } } router.post("/handle", (request, response) => { //code to perform particular action. // We start with reading our configuration file first here: let cfg = getConfig(); let auth = request.body.auth; let user = getAuthenticatedUser(auth, cfg); if (user == null) { console.log(`Cannot authenticate user with auth: ${auth}`); response.json({ status: "Error", message: "Cannot authenticate user." }); return; } //To access POST variable use req.body()methods. // console.log("Received body: "); // console.log(request.body); // response.json({ result: 'OK', name: request.body.username }); let act: string = request.body.action; let tgt: string = request.body.target; // Inverted pin values below: let relayVal = act == "enable" ? Gpio.LOW : Gpio.HIGH; let pinIdx: number = pinMap[tgt]; console.log(`Setting pin ${pinIdx} to ${relayVal}`); pins[pinIdx].writeSync(relayVal); if (tgt == "pool_robot") { // We are activating/deasctivating the robot, so we should cancel the timeout if any: if (robot_timeout != null) { // console.log("Cancelling pool robot timeout callback."); clearTimeout(robot_timeout); robot_timeout = null; } // If we jsut enabled the robot, start the timeout: if (act == "enable") { // Duration below given in minutes: let dur = cfg["robot_cleaning_duration"] * 60 * 1000; robot_timeout = setTimeout(stop_robot, dur); } } // Send the state: sendStatus(cfg, response); }); * And of course I added the required entry in the config file: "robot_cleaning_duration": 120, ===== Conclusion ===== * => And this is it for this time, now I just need to monitor the pool and the log file for a few days ;-) * But right now, what I desperately need is a short nap lol. I can't keep my eyes opened anymore 😁