blog:2022:0514_nervswimpool_schedule_handling

Quick project: Adding support for swimmingpool pump activation schedule [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<pins.length;i++) {
            pins[i].writeSync(Gpio.HIGH);
            pins[i].unexport();
        }
        console.log("Done releasing GPIO pins.")
        server.close(function () {
            console.log('Closed server.');
        });
    });
  • 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 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.
  • ⇒ 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
  • 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
  • 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);
        }
      }
    }
  • 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,
  • ⇒ 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 😁
  • blog/2022/0514_nervswimpool_schedule_handling.txt
  • Last modified: 2022/05/14 13:38
  • by 127.0.0.1