Skip to content

Отключение света через пару-тройку часов

За годы наблюдений я пришёл к умозаключению, что никакой осветительный прибор дома не должен работать более 2 (или 4 или 6 или 8 — в зависимости от назначения комнаты) часов. Поэтому везде стараюсь на как можно более низком уровне написать автоматическое выключение по таймеру. Для zigbee-устройств наиболее низким уровнем является скрипты SLS-хаба. Для этих устройств уже есть готовые примеры автовыключения света, но они используют однократный таймер, из-за чего один скрипт не может одновременно контролировать несколько выключателей. Для обхода этого ограничения использован таймер Cron.

Описание

Данный скрипт вызывается или правилом SB Rule у свойства state выключателя, или же по таймеру, который он же сам установил. Описание параметров и формата вызова см. в листинге. В принципе, достаточно одного параметра — таймаута в часах: LightTimeOut.lua,2 Повторный вызов осуществляется самим скриптом (т.е. рекурсивно) уже с тремя параметрами.

Для работы скрипта требуется SLS с прошивкой как минимум 2024.01.22d4. При разработке хотелось все делать одним скриптом: в т.ч. один скрипт на все выключатели в доме. Получилось).

Нюансы:

  • управление любым количеством выключателей одним файлом,
  • автоотключение обрабатывается этим же файлом через рекурсивный вызов,
  • отключение по таймеру, для настройки которого не требуется править скрипт,
  • при сработке выключателя во время работы таймера, последний не продлевается, а добавляется новый (поэтому не балуйтесь со светом),
  • список /api/scripts со временем заполняется большим количеством деактивированных crontab-задач; для очистки списка, видимо, хорошо бы автоматически перезагружать хаб раз в неделю, например.

Листинг

lua
-- LightTimeOut.lua --
-- Взято с https://slsys.io/docs/lua_doc/luaMainDoorLight , модифицировано под задачи mxid://@azata:gazizova.net 
-- Пример правил Simple Bind: LightTimeOut.lua,2|sw|sportzal
-- или можно только с одним параметром: LightTimeOut.lua,2
-- разбираю аргументы 
local arr = explode("|", Event.Param)
local delaytime = arr[1] -- время задержки выключения в часах или строка cron (для timer)
local caller = arr[2] -- кто вызвал: sw = switch (по умолчанию); timer = таймер
local dstDevice = arr[3] -- исполняющее устройство (реле/розетка/лампа), прописать ieeeAddr или nwkAddr или Friendly name
if (dstDevice == nil) then -- третий агрумент можно не указывать, если управляем тем же самым устройством, что вызывает скрипт
  dstDevice = Event.FriendlyName
end
arr = nil -- cleanup
local scriptName = (explode(".", Event.Name))[1] -- имя данного скрипта
-- проверяю кто вызвал (а-ля switch-case)
if (caller == "sw") or (caller == nil) then -- сам выключатель - надо запустить таймер, если свет был включён
  if (Event.State.Value == "ON") then 
    local gmt = 5 -- часовой пояс
    local time = os.time() + delaytime * 3600 + gmt * 3600;
    local t1 = math.modf(time/60);
--    local sec  = time - t1*60;
    local time = t1;
    local t1 = math.modf(time/60);
    local min  = time - t1*60;
    if (min == 0) then -- чтобы не удалять постоянно прописанные в crontab строчки, в которых я ставлю нулевые минуты, добавляю одну минутку
      min = 1
    end
    local time = t1;
    local t1 = math.modf(time/24);
    local hour = time - t1*24;
    local crontab = min .. " " .. hour .. " * * *"
    local param = crontab .. "|timer|" .. dstDevice -- меняю целевое воздействие на OFF
    scripts.addCron(scriptName, crontab, param)
  end
elseif (caller == "timer") then -- вернулся по окончанию таймера
    zigbee.set(dstDevice, "state", "OFF") -- выключить свет
--    zigbee.set(Event.FriendlyName, Event.State.Name, "OFF") -- выключить -- так не работает, при втором вызове теряется исходный Event
-- удаление задачи планировщика - на самом деле происходит не удаление, а деактивация этой задачи
    scripts.removeCron(scriptName, delaytime)
end
-- LightTimeOut.lua --
-- Взято с https://slsys.io/docs/lua_doc/luaMainDoorLight , модифицировано под задачи mxid://@azata:gazizova.net 
-- Пример правил Simple Bind: LightTimeOut.lua,2|sw|sportzal
-- или можно только с одним параметром: LightTimeOut.lua,2
-- разбираю аргументы 
local arr = explode("|", Event.Param)
local delaytime = arr[1] -- время задержки выключения в часах или строка cron (для timer)
local caller = arr[2] -- кто вызвал: sw = switch (по умолчанию); timer = таймер
local dstDevice = arr[3] -- исполняющее устройство (реле/розетка/лампа), прописать ieeeAddr или nwkAddr или Friendly name
if (dstDevice == nil) then -- третий агрумент можно не указывать, если управляем тем же самым устройством, что вызывает скрипт
  dstDevice = Event.FriendlyName
end
arr = nil -- cleanup
local scriptName = (explode(".", Event.Name))[1] -- имя данного скрипта
-- проверяю кто вызвал (а-ля switch-case)
if (caller == "sw") or (caller == nil) then -- сам выключатель - надо запустить таймер, если свет был включён
  if (Event.State.Value == "ON") then 
    local gmt = 5 -- часовой пояс
    local time = os.time() + delaytime * 3600 + gmt * 3600;
    local t1 = math.modf(time/60);
--    local sec  = time - t1*60;
    local time = t1;
    local t1 = math.modf(time/60);
    local min  = time - t1*60;
    if (min == 0) then -- чтобы не удалять постоянно прописанные в crontab строчки, в которых я ставлю нулевые минуты, добавляю одну минутку
      min = 1
    end
    local time = t1;
    local t1 = math.modf(time/24);
    local hour = time - t1*24;
    local crontab = min .. " " .. hour .. " * * *"
    local param = crontab .. "|timer|" .. dstDevice -- меняю целевое воздействие на OFF
    scripts.addCron(scriptName, crontab, param)
  end
elseif (caller == "timer") then -- вернулся по окончанию таймера
    zigbee.set(dstDevice, "state", "OFF") -- выключить свет
--    zigbee.set(Event.FriendlyName, Event.State.Name, "OFF") -- выключить -- так не работает, при втором вызове теряется исходный Event
-- удаление задачи планировщика - на самом деле происходит не удаление, а деактивация этой задачи
    scripts.removeCron(scriptName, delaytime)
end