Движок правил wb-rules: различия между версиями

Нет описания правки
Метка: visualeditor
Строка 659: Строка 659:


</syntaxhighlight>
</syntaxhighlight>
=== Сложные правила с расписаниями ===
Обьект - продуктовый магазин. Различные системы магазина управляются по обратной связи от датчиков температуры и с учётом расписания работы магазина.
Для расписаний использются не cron-правила, а обёртка над ними. Обёртка включает и выключает правила, которые, в отличие от cron-правил, выполняются постоянно, будучи включенными.
Например мы хотим, чтобы освещение было включено с 10 утра до 17 вечера. Обёртка (libschedule) будет выполнять правило "включить освещением" раз в минуту с 10 утра до 17 вечера.
Это значит, что даже если контроллер работает с перерывами и пропустил время перехода между расписаниями (10 утра), то контроллер всё равно включит отвещение при первой возможности.
lib_schedules.js:
<syntaxhighlight lang="ecmascript">
global.__proto__.Schedules = {};
(function(Schedules) { // замыкание
  function todayAt(now, hours, minutes) {
    var date = new Date(now);
    // i.e. "today, at HH:MM". All dates are in UTC!
    date.setHours(hours);
    date.setMinutes(minutes);
    return date;
  }
  function checkScheduleInterval(now, start_time, end_time) {
    var start_date = todayAt(now, start_time[0], start_time[1]);
    var end_date = todayAt(now, end_time[0], end_time[1]);
    log("checkScheduleInterval {} {} {}".format(now, start_date, end_date));
    if (end_date >= start_date) {
      if ((now >= start_date) && (now < end_date)) {
        return true;
      }
    } else {
      // end date is less than start date,
      // assuming they belong to a different days (e.g. today and tomorrow)
      // option 1: what if it's now the day of "end" date?
      // in this case the following is enough:
      if (now < end_date) {
        return true;
      }
      // well, that seems not to be the case. ok,
      // option 2: it's the day of "start" date:
      if (now >= start_date) {
        return true;
      }
    }
    return false;
  }
  function checkSchedule(schedule, now) {
    if (now == undefined) {
      now = new Date();
    }
    for (var i = 0; i < schedule.intervals.length; ++i) {
      var item = schedule.intervals[i];
      if (checkScheduleInterval(now, item[0], item[1])) {
        log("found matching schedule interval at {}".format(item));
        return true;
      }
    }
    return false;
  }
 
  function updateSingleScheduleDevStatus(schedule) {
    log("updateSingleScheduleDevStatus {}".format(schedule.name));
    dev["_schedules"][schedule.name] = checkSchedule(schedule);
  };
  function addScheduleDevCronTasks(schedule) {
    for (var i = 0; i < schedule.intervals.length; ++i) {
      var interval = schedule.intervals[i];
      for (var j = 0; j < 2; ++j) { // either start or end of the interval
        var hours = interval[j][0];
        var minutes = interval[j][1];
        log("cron at " + "0 " + minutes + " " + hours + " * * *");
        defineRule("_schedule_dev_{}_{}_{}".format(schedule.name, i, j), {
          when: cron("0 " + minutes + " " + hours + " * * *"),
          then: function () {
            log("_schedule_dev_ {}_{}_{}".format(schedule.name, i, j));
            updateSingleScheduleDevStatus(schedule);
          }
        });
      }
    }   
  }
  function addScheduleAutoUpdCronTask(schedule) {
    defineRule("_schedule_auto_upd_{}".format(schedule.name), {
      when: cron("@every " + schedule.autoUpdate),
      then: function() {
        dev._schedules[schedule.name] = dev._schedules[schedule.name];
      }
    });
  }
  var _schedules = {};
  Schedules.registerSchedule = function(schedule) {
    _schedules[schedule.name] = schedule;
  };
  Schedules.initSchedules = function() {
    var params = {
      title: "Schedule Status",
      cells: {}
    };
    for (var schedule_name in _schedules) {
      if (_schedules.hasOwnProperty(schedule_name)) {
        var schedule = _schedules[schedule_name];
        params.cells[schedule_name] = {type: "switch", value: false, readonly: true};
      }
    };
    defineVirtualDevice("_schedules", params);
    for (var schedule_name in _schedules) {
      if (_schedules.hasOwnProperty(schedule_name)) {
        var schedule = _schedules[schedule_name];
        // setup cron tasks which updates the schedule dev status at schedule
        //  interval beginings and ends
        addScheduleDevCronTasks(schedule);
        // if needed, setup periodic task to trigger rules which use this schedule
        if (schedule.autoUpdate) {
          addScheduleAutoUpdCronTask(schedule);
        }
        // set schedule dev status as soon as possible at startup
        (function(schedule) {
          setTimeout(function() {
            updateSingleScheduleDevStatus(schedule);
          }, 1);
        })(schedule);
      };
    };
   
  };
})(Schedules);
</syntaxhighlight>
Пример правил, с использованием Schedules:
<syntaxhighlight lang="ecmascript">
(function() { // замыкание
  defineAlias("countersTemperature", "wb-msw2_30/Temperature");
  defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature");
  defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1");
  defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3");
  Schedules.registerSchedule({
    "name" : "signboard", // вывеска
    "autoUpdate" : "1m",
    "intervals" : [
      [ [12, 30], [20, 30] ],  // в UTC, 15:30 - 23:30 MSK
      [ [3, 30], [5, 20] ],  // в UTC, 6:30 - 8:20 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "ext_working_hours_15m",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 45], [20, 15] ],  // всё ещё UTC, 7:45 - 23:15 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "working_hours",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [5, 0], [19, 0] ],  // всё ещё UTC, 8:00 - 22:00 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "working_hours_15m",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 45], [19, 15] ],  // всё ещё UTC, 7:45 - 22:15 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "frontshop_lighting",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 20], [20, 45] ],  // всё ещё UTC, 7:20 -23:45 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "heaters_schedule",
    "intervals" : [
      [ [4, 0], [17, 0] ],  // всё ещё UTC, 07:00 - 20:00 MSK дневной режим
    ]
  });
  Schedules.initSchedules();
  // Вывеска и фасадное освещение
  defineRule("signboardOnOff", {
    when: function() {
      return dev._schedules.signboard || true;
    },
    then: function (newValue, devName, cellName) {
      log("signboardOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.signboard; //
     
      dev["wb-mr6c_80/K2"] = !on;
      dev["wb-mr6c_80/K1"] = !on;
      dev["wb-mr6c_80/K3"] = !on;
    }
  });
  // Освещение торгового зала
  defineRule("lightingFrontshopOnOff", {
    when: function() {
      return dev._schedules.frontshop_lighting || true;
    },
    then: function (newValue, devName, cellName) {
      log("lightingFrontshopOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-gpio/EXT1_R3A1"] = ! dev._schedules.frontshop_lighting; //инвертированный контактор
    }
  });
  // Вентиляция подсобного помещения
  defineRule("ventBackstoreOnOff", {
    when: function() {
      return dev._schedules.ext_working_hours_15m || true;
    },
    then: function (newValue, devName, cellName) {
      log("ventBackstoreOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.ext_working_hours_15m;
      dev["wb-mr6c_81/K1"] = ! on; //инвертированный контактор
      dev["wb-mr6c_81/K5"] = ! on; //инвертированный контактор
    }
  });
  // Освещение холодильных горок
  defineRule("lightingCoolingshelfsOnOff", {
    when: function() {
      return dev._schedules.frontshop_lighting || true;
    },
    then: function (newValue, devName, cellName) {
      log("lightingCoolingshelfsOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.working_hours_15m;
      // освещение в горках через нормально-закрытые реле (инвертировано)
      dev["wb-mrm2-old_60/Relay 1"] = !on;
      dev["wb-mrm2-old_61/Relay 1"] = !on;
      dev["wb-mrm2-old_62/Relay 1"] = !on;
      dev["wb-mrm2-old_63/Relay 1"] = !on;
      dev["wb-mrm2-old_64/Relay 1"] = !on;
      dev["wb-mrm2-old_65/Relay 1"] = !on;
      dev["wb-mrm2-old_66/Relay 1"] = !on;
      dev["wb-mrm2-old_67/Relay 1"] = !on;
    }
  });
  //Брендовые холодильники (пиво, лимонады)
  defineRule("powerBrandFridgesOnOff", {
    when: function() {
      return dev._schedules.working_hours || true;
    },
    then: function (newValue, devName, cellName) {
      log("powerBrandFridgesOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.working_hours;
     
      dev["wb-gpio/EXT1_R3A5"] = !on; // инвертировано
    }
  });
  // ========= Котлы и приточная вентиляция ТЗ  ===========
  // обратная связь по температуре овощной зоны
  // днём работает позиционный регулятор
  defineRule("heatersDayOff", {
    when: function() {
      return (dev._schedules.heaters_schedule) && (vegetablesTemperature > 17.0);
    },
    then: function (newValue, devName, cellName) {
      log("heatersDayOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !false; // инвертировано
    }
  });
  defineRule("heatersDayOn", {
    when: function() {
      return (dev._schedules.heaters_schedule) && (vegetablesTemperature < 16.7);
    },
    then: function (newValue, devName, cellName) {
      log("heatersDayOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !true; // инвертировано
    }
  });
  // ночью работает позиционный регулятор
  defineRule("heatersNightOff", {
    when: function() {
      return (!dev._schedules.heaters_schedule) && (vegetablesTemperature > 11.6);
    },
    then: function (newValue, devName, cellName) {
      log("heatersNightOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !false; // инвертировано
    }
  });
  defineRule("heatersNightOn", {
    when: function() {
      return (!dev._schedules.heaters_schedule) && (vegetablesTemperature < 11.3);
    },
    then: function (newValue, devName, cellName) {
      log("heatersNightOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !true; // инвертировано
    }
  });
  // приточная и вытяжная вентиляция принудительно выключены
 
  defineRule("ventFrontshopAlwaysOff", {
    when: cron("@every 1m"),
    then: function() {
  dev["wb-gpio/EXT1_R3A3"] = !false;
  dev["wb-gpio/EXT1_R3A4"] = !false;
    }
  });
 
 
  // ==================  Кассовая зона =================
  // в кассовой зоне в рабочее время температура поддерживается кондиционерами (позиционный регулятор)
  defineRule("countersACOn", {
    when: function() {
      return (dev._schedules.working_hours_15m) && (countersTemperature < 17.7);
    },
    then: function (newValue, devName, cellName) {
      log("countersACOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_75/Play from ROM7"] = true; // кондиционер кассовой зоны на нагрев
    }
  });
  // в нерабочее время кондиционер выключен
  defineRule("countersACOff", {
    when: function() {
      return (!dev._schedules.working_hours_15m) || (countersTemperature > 18.0);
    },
    then: function (newValue, devName, cellName) {
      log("countersACOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_75/Play from ROM2"] = true; // кондиционер кассовой зоны выключить
    }
  });
  // =============== Овощная зона ==============
  // Охлаждение овощей кондиционером только при температуре воздуха выше 18.5C 
  defineRule("acVegOn", {
    when: function() {
      return vegetablesTemperature >= 18.5
    },
    then: function (newValue, devName, cellName) {
      log("acVegOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_76/Play from ROM3"] = true; // Охлаждение +18
    }
  });
  defineRule("acVegOff", {
    when: function() {
      return vegetablesTemperature < 17.8
    },
    then: function (newValue, devName, cellName) {
      log("acVegOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_76/Play from ROM2"] = true; // выключить
    }
  });
})()
</syntaxhighlight>