Помогите сделать скрипт, восстанавливающий состояние реле после перезагрузки

Добрый день! Предыстория: сделал у себя дома управление светом через реле (WBIO-DO-R10A-8) и диммеры (WB-MDM3). В целом все устраивает, кроме одного досадного момента: после перезагрузке контроллера, все реле выключаются (это еще терпимо, хотя и неприятно), а вот все диммеры включаются - и это уже “ни в какие ворота”, так как последнее время часто стали отключать свет посреди ночи, и после восстановления питания врубается свет во всем доме. Поискал тут на форуме, рекомендовали сделать сохранение значений реле в persistent storage и восстановление на загрузке скрипта. Сейчас у меня код выглядит примерно так:

// Создание хранилища для сохранения значений реле
var ps = new PersistentStorage("storage_floor2", { global: true });

var relaysList = [
  // список реле, которые нужно сохранять
];

loadSavedValues(relaysList, ps);
subscribeToRelayChanges(relaysList, ps);

function loadSavedValues(relayList, ps) {
  relayList.forEach(function (relayDev) {
    if (ps[relayDev] !== undefined) {
      dev[relayDev] = ps[relayDev]; // восстанавливаем значение из persistentStorage
      log.info("Загружено значение для " + relayDev + ": " + ps[relayDev]);
    }
  });
};

function subscribeToRelayChanges(relayList, ps) {
  relayList.forEach(function (relayDev) {
    defineRule({
      whenChanged: relayDev,
      then: function (newValue) {
        ps[relayDev] = newValue; // сохраняем значение в persistentStorage 
        log.info("Сохранено значение " + newValue + " для " + relayDev);
      }
    });
  });
};

Но восстановления не происходит. Судя по логам, код вызывается (и сохранение и загрузка), но похоже, что восстановление происходит слишком рано, и восстановленное значение впоследствии перезатирается системой при инициализации. Так, все реле остаются выключенными, а диммеры включенными.

Пытался заменить на

setTimeout(function() {
  debugLog("initial load");
  loadSavedValues(relaysList, ps);
}, 0);
  • с тем же успехом :frowning:

Видел предложение поставить большую задержку в setTimeout, но это будет значить, что несколько секунд все люстры в доме будут светиться, ночью такое поведение не устраивает! Что посоветуете?

Я немного еще попытался покопать сам, и так понимаю, что главная проблема в том, что я пытаюсь установить значения на контроллере, тогда как все эти устройства являются внешними (WBIO-DO-R10A-8, на котором я это тестирую, подключен к контроллеру через WB-MIO, а WB-MDM3 - это изначально модбас модуль). И когда контроллер читает свои правила, эти устройства еще не “онлайн”, в логе так и пишется:

сен 15 13:59:26 wb-rules[1948]: INFO: [rule info] Загружено значение для wb-mdm3_123/K1: false
сен 15 13:59:26 wb-rules[1948]: ERROR: [rule error] failed to SetValue for unexisting control wb-mdm3_123/K2: false

То есть мне нужно каким-то образом “подписаться” на событие инициализации устройства в контроллере. К сожалению, я пока не нашел в документации, как это можно сделать “нормальным способом”…

Добрый день.

А что настроено в драйвере wb-mqtt-gpio про “стартовое” состояние?

А чем они включаются? Аппаратно (настройками) или программно?
Это, пожалуй, ключевой вопрос, без ясности по которому бесполезно строить какую-то дополнительную логику.

А, тогда wb-mqtt-gpio отношения к состоянию модуля не имеет, да.

Опять же - если в самих диммерах настроено включение после подачи питания - это не очень полезно. Проверьте настройку. Сейчас проверил - диммер не включается если это не настроено явно после снятия и подачи питания.

  1. По настройке диммеров, я так понимаю, оно добавилось только в прошивке 2.8, но у меня 2.7.1, и автоматическое обновление почему-то выше не обновляет. Как обновиться выше? Вот такие у меня устройства и прошивки на них сейчас (часть устройств, например WB-LED не пока ввел в работу, руки не доходят):

2024-09-16 14:41:34,420 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (101, /dev/ttyRS485-1))
2024-09-16 14:41:35,912 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (111, /dev/ttyRS485-1))
2024-09-16 14:41:37,417 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (13, /dev/ttyRS485-1))
2024-09-16 14:41:38,950 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (123, /dev/ttyRS485-1))
2024-09-16 14:41:40,442 Update skipped: 1.6.3 → 1.6.3 (WB-MIO (88, /dev/ttyRS485-1))
2024-09-16 14:41:41,923 Update skipped: 1.6.3 → 1.6.3 (WB-MIO (129, /dev/ttyRS485-1))
2024-09-16 14:41:43,513 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (100, /dev/ttyRS485-1))
2024-09-16 14:41:45,003 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (96, /dev/ttyRS485-1))
2024-09-16 14:41:46,517 Update skipped: 1.21.3 → 1.21.3 (WB-MR6CU (48, /dev/ttyRS485-2))
2024-09-16 14:41:48,027 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (19, /dev/ttyRS485-2))
2024-09-16 14:41:49,536 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (103, /dev/ttyRS485-2))
2024-09-16 14:41:50,997 Update skipped: 2.7.1 → 2.7.1 (WB-MDM3 (102, /dev/ttyRS485-2))

Сам контроллер вот такое выдает при подключении по SSH:
“Welcome to Wiren Board 7.3.4 (s/n *********), release wb-2310 (as stable)”

  1. можно поподробней, что за настройки wb-mqtt-gpio вы имеете в виду? (у меня есть 1 модуль WBIO-DO-R10A-8 в основном щите, который подключен непосредственно к контроллеру). Не очень понял, как его настраивать.

В целом, я не понимаю логику, по которой все включается/выключается после восстановления питания. Сегодня в пол первого ночи у меня включились почти группы на 7 из 9 модулях mdm3, 2 остались выключенными, а реле WBIO-DO-R10A-8, которые через WB-MIO - выключились, хотя были включены, а группа без WB-MIO - включилась. В общем, логика восстановления очень странная. Никаких других скриптов, кроме того, что я привел выше, которые бы управляли включением/выключением на перезагрузке, у меня нет, кроме него есть только 1 скрипт с энным количество defineRule для кнопок.

С обновлением прошивки mdm3 разобрался, версии выше моей пока в тестинге, а я на стейбле (и без крайней необходимости не хотелось бы на тестинг переходить).

Но оригинальный мой вопрос остается актуальным, вне зависимости от того, как настроены модули: мне нужно научиться восстанавливать состояния реле после перезагрузки контроллера, как минимум, это касается модулей WBIO-DO-R10A-8, 2 из которых у меня подключены через WB-MIO, а 1 подключен непосредственно в контроллер (я кстати собираюсь подключить еще как минимум 3 таких модуля, потому что я планирую сделать на них управление шторами).

Прошивки новее доступны только в testing релизе.

Это очень старый. Актуальный релиз ПО контроллера - 2407.

Раз у вас модули подключены через шлюз - то wb-mqtt-gpio отношения к ним не имеет.

Именно поэтому и надо установить что включает.
Если в прошивке диммеров нет функционала восстановления значений - то проверьте пожалуйста что еще управляет каналами. Скрипты например.

И его тоже.

Вот тут рассматривается: Восстановление состояния каналов WBIO-DO-SSR-8 - #3 от пользователя BrainRoot

  1. Да, прошивку контроллера я уже обновил, но это ничего не изменило, что ожидаемо…
  2. У меня 1 модуль подключен непосредственно к контроллеру. Поэтому я и уточнил про “настройку wb-mqtt-gpio”.
  3. в скрипте есть только некоторое количество правил типа:
defineRule({
  whenChanged: "wb-mdm3_123/Input 6 Single Press Counter", // ВК203.1 Детская 2 выкл главн (свет)
  then: function (newValue, devName, cellName) {
    dev["wb-mdm3_123/K2"] = !dev["wb-mdm3_123/K2"]; // Детская 2 свет
  }
});

Вспомнил еще, что у меня ранее стоял Node-RED (тестил подключение Алисы через него). Сейчас, с обновлением контроллера, я кажется его снес :slight_smile: Возможно, это он упарывался?

  1. Вернемся к начальной теме, так как она актуальна хотя бы для WBIO реле, которые включены через BW-MIO:

Вот тут рассматривается: Восстановление состояния каналов WBIO-DO-SSR-8 - #3 от пользователя BrainRoot

Тут используется таймер на 2 секунды, это не выглядит надежным решением. Нельзя ли подписаться на какие-то события, которые бы означали, что устройство стало готовым у использованию?
У меня появилась идея что я могу опрашивать готовность устройства в defineRule с условием через when. Набросал что-то вроде такого (это не работает, потому что я выбрал не тот способ узнать, готов ли объект):

// Определение списка реле для этажа 2
var relaysList = [
  // F2 dimmer rooms
  "wb-mdm3_123/K1", // СВ-200 Детская 1 свет главный
  "wb-mdm3_123/K2", // СВ-203 Детская 2 свет главный
  "wb-mdm3_123/K3", // СВ-Л-3 Холл свет главный
  // ну и еще энное количество топиков, которые я хочу восстанавливать...
}

function isConnected(devName) {
  // Проверяем статус подключения по Modbus
  // Идея была в том, что если в девайсах у топика появится мета, значит девайс онлайн
  // Проблема в том, что отсутствие меты неотличимо от отсутствия ошибки, так что это не работает
  if ((dev[devName+"#error"] == null))
  {
    setTimeout(function(){log(devName+" status online: " + dev[devName+"#error"])}, 0);
    return true;
  }

  setTimeout(function(){log(devName+" status offline: " + dev[devName+"#error"])}, 0);
  return false;
}

var devsToReady = relaysList.slice();

var connectedWatcher = defineRule("modbusDeviceReady", {
  when: function() {
    for(var i = devsToReady.length - 1; i > 0; i = i - 1) {
      var relayDev = devsToReady[i];
      if (isConnected(relayDev)) {
        return true;
      }
    }
    return false;
  },
  then: function (newValue) {
    for(var i = devsToReady.length - 1; i > 0; --i) {
      var relayDev = devsToReady[i];
      if (isConnected(relayDev)) {
        setTimeout(function() {
          log.info("---- connected "+relayDev+" ----");
          var value = ps[relayDev] ? true : false;
          dev[relayDev] = value;
          log("Загружено значение для " + relayDev + ": " + ps[relayDev]);
        }, 0);
        devsToReady.splice(i, 1);
      }
    }

    if (devsToReady.length == 0) {
      log.info("---- all devices connected ----");
      disableRule(connectedWatcher);
    } else {
      log("More " + devsToReady.length + " to init..");
    }
  }
});

Если в кратце, я делаю 1 правило, которое в условии в цикле проверяет “онлайн” статус топиков в списке, и, если появляется кто-то онлайн, срабатывает функция, которая инициализирует появившееся онлайн топик и удаляет его из списка.
К сожалению я совсем не силен в JS, а тем более wb-rules вносит свои нюансы, и я не знаю, как правильно определить, онлайн-ли топик, или еще нет?

А зачем столько заморочек,
нужно просто обеспечить контроллеру и модулям бесперебойное питание хотя бы часов на 8-10. И всё… и не будет свет мигать по ночам.
Только ленивый человек мог придумать трактор.

Спасибо за совет, ИБП запланировано, но будет позже. Резервация питания никак не отменяет необходимость правильного восстановления работы после аварийной перезагрузки.

Вообще то с ИБП начинают развертывание системы.
А идея с восстановлением выглядит примерно так:
“А я встану, погляжу, хорошо ли я лежу”
Для того чтоб данные которые вы планируете восстаналивать были актуальными, регулярность сохранения должна быть достаточно частой. Это, мало того, что загрузит всю систему бесполезной работой, но и достаточно быстро убьёт ваше устройство хранения. Оно вам надо? Если поберечь устройство хранения и сохранять только какие-то дефолтные установки - может быть… но не факт что это вас устроит.

Повторяю, ИБП планируется, но позже. Резервирование питания рассматривается мной как дополнительная защита оборудования от аварийных выключений, а не как “волшебная таблетка”, которая спасет меня от всех бед. Свет могут вырубить на длительное время, контроллер может “перегреться”, да тупо и ИБП может “сдохнуть” - ничто не работает вечно. Система жизнеобеспечения в доме (та ее часть, которая завязана на wirenboard) должна восстанавливаться в рабочее состояние самостоятельно, насколько это вообще возможно.

На этом предлагаю обсуждение резервирования питания тут остановить: я очень уважаю вашу точку зрения, но давайте, все же, вернемся к теме моего вопроса?

Что касается предложения сделать задержку, я о ней, конечно, и сам думал. Но хоть 2 секунды, хоть 20 - во первых, никто не гарантирует, что после очередного обновления время инициализации не изменится. Во вторых, ждать лишние секунды нет желания - если сделать слишком большой “задел”, свет сперва во всем доме включится, и потом выключится. Хотелось бы привести данный интервал к возможному минимуму, при этом не рискуя, что вся эта груда костылей развалится от малейшего “чиха”.

Определите пожалуйста что именно включает каналы. Начинать надо с этого. Проверьте отключив, например, все правила и интеграции.

Попробовал вот такой вариант, для заведомо кривого имени оно возвращает false:

// Проверка статуса подключения устройства (devName)
function isConnected(devName) {
  try {
    // Считываем значение из dev[...] и сразу приводим к строке, чтобы при ошибке получить исключение
    var v = String(dev[devName]);
    //setTimeout(function(){log(devName+" status ONLINE")}, 0);
    return true;
  } catch (err) {
    // Исключение покажет, что данное устройство отсутствует
    //setTimeout(function(){log(devName+" status OFFLINE")}, 0);
    return false;
  }
}

Но в логе есть ощущение, что все равно слишком рано вызывается - как будто токены создаются до того, как девайс будет инициализирован… Зато нашел интересную вещь в логах:

Вот сработал мой инициализатор (до обесточивания системы я специально убедился, что реле включено):

сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] ----------------------------------------- connected wb-mio-gpio_88:2/K8 -----------------------------------------
сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Загружено значение для wb-mio-gpio_88:2/K8: true

вот подписка на изменения:

сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] subscribeToRelayChanges wb-mio-gpio_88:2/K8

а вот wb-mqtt-serial наконец-то инициализировал девайс… и зачем-то прописал в него false??

сен 17 19:26:51 wirenboard-AT5QCYO wb-mqtt-serial[1546]: INFO: [serial device] device modbus_io:88:2 is connected
сен 17 19:26:51 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Сохранено значение для wb-mio-gpio_88:2/K8: false

А вот логи для диммера в ванной (специально там выключил свет):

сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] ----------------------------------------- connected wb-mdm3_96/K1 -----------------------------------------
сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Загружено значение для wb-mdm3_96/K1: false

сен 17 19:26:44 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] subscribeToRelayChanges wb-mdm3_96/K1

сен 17 19:26:47 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] --> wb-mdm3_96/Input 1 Single Press Counter
сен 17 19:26:47 wirenboard-AT5QCYO wb-mqtt-serial[1546]: INFO: [mqtt] subscription succeeded (message id 681)
сен 17 19:26:47 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Сохранено значение для wb-mdm3_96/K1: false
сен 17 19:26:51 wirenboard-AT5QCYO wb-mqtt-serial[1546]: INFO: [serial device] device modbus:96 is connected
сен 17 19:26:51 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] --> wb-mdm3_96/Input 1 Single Press Counter
сен 17 19:26:51 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Сохранено значение для wb-mdm3_96/K1: true

ПС, кстати, wb-mqtt-serial[1546]: INFO: [serial device] device modbus:96 is connected - вот на этот ивент мне бы как-то подписаться!

Вот полный скрипт и лог загрузки (перед выключением питания специально включил реле “wb-mio-gpio_88:2/K8”, чтобы последить, как оно выключается)
floor2.js (15,9 КБ)
logs.txt (266,3 КБ)

Долго писал свой ответ и не увидел Вашего вопроса :slight_smile: Насколько я понял, источников изменений 2: подписка на “Single Press Counter” инпута может вызвать калбек, который переключил состояние реле. А еще wb-mqtt-serial, кажется, самостоятельно их выключает, когда речь идет про modbus_io:88:2 (то есть WBIO-DO-R10A-8, подключенный через WB-MIO). Выше прикрепил лог, возможно, вы поймете из него больше…

Устройство создано. Точнее - топик создан. wb-mqtt-serial сначала создает топик, публикуя в него null а затем читает значение из регистра и записывает его в топик, что логично. Если устройство не отвечает (чтение неудачно) то значение и останется null.
Движок форума не дает работать сjs файлами - поэтому я не могу посмотреть как именно анализируете топик но предполагаю что проверяете на истину.
Так значение null != false, но и null != true.
При этом возможна

Ваш ответ не отображается целиком, к сожалению :frowning:

поэтому я не могу посмотреть как именно анализируете топик

Я так понимаю, вы спрашиваете про это? Пишу в лог, как есть, как пришло мне из калбека:

    defineRule({
      whenChanged: devName,
      then: function (newValue) {
        ps[devName] = newValue;
        log("Сохранено значение для "+devName+": "+newValue);
      }
    });

Мне кажется, я стал понимать, почему у меня такая непонятная логика после включения…

  1. Если wb-mqtt-serial при подключении устройства читает из него значение и пишет в mqtt топик, то он просто считывает то, чем девайс проинициализировался - в данном случае видимо, false. Получается, что моя инициализация произошла слишком рано, и тот true, который я туда записал, пошел “мимо кассы”, был перезаписал сервисом wb-mqtt-serial. Если так, то это объясняет, почему реле на модулях WBIO у меня всегда выключаются на втором этаже.

  2. А вот вторая проблема, судя по всему, проявляется в том, что примерно тем же способом вызываются калбеки при подписке на Single Press Counter - по этому событию включаются/выключаются почти все группы освещения, вроде такого:

// ВК207.1 Спальня выкл главн (свет)
defineRule({
  whenChanged: "wb-mdm3_101/Input 1 Single Press Counter", // ВК207.1 Спальня выкл главн (свет)
  then: function (newValue, devName, cellName) {
    dev["wb-mdm3_101/K1"] = !dev["wb-mdm3_101/K1"]; // СВ207 Спальня свет
  }
});

Соответственно, когда wb-mqtt-serial подключает устройство, он обновляет также счетчик кликов - а я на него уже подписан и получается, как будто на все выключатели в моем доме нажали.

сен 17 19:26:51 wirenboard-AT5QCYO wb-mqtt-serial[1546]: INFO: [serial device] device modbus:96 is connected
сен 17 19:26:51 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] --> wb-mdm3_96/Input 1 Single Press Counter
сен 17 19:26:51 wirenboard-AT5QCYO wb-rules[1807]: INFO: [rule info] Сохранено значение для wb-mdm3_96/K1: true

В итоге имеем недетерминированную логику включения/выключения, зависящую от того, в каком состоянии были реле и в каком порядке “нажаты” кнопки - но ночью у меня почти наверняка включится почти весь свет в доме.

… Единственное, что не вписывается странно, что для длинных нажатий в логе нет, хотя подписки на них есть - на них я пока повесил только выключения всех групп освещения в комнате / этаже, что-то вроде такого:

defineRule({
  whenChanged: "wb-mdm3_96/Input 1 Long Press Counter", // ВК205.2 Холл выкл главный (ванная 1)
  then: function (newValue, devName, cellName) {
    dev["wb-mdm3_123/K1"] = !dev["wb-mdm3_123/K3"]; // Детская 1 свет
    dev["wb-mdm3_123/K2"] = !dev["wb-mdm3_123/K3"]; // Детская 2 свет
    dev["wb-mdm3_101/K1"] = !dev["wb-mdm3_123/K3"]; // Спальня свет
    dev["wb-mdm3_96/K1"] = !dev["wb-mdm3_123/K3"]; // Ванная свет
    dev["wb-mdm3_96/K2"] = !dev["wb-mdm3_123/K2"]; // Ванная мал свет
    dev["wb-mdm3_96/K3"] = !dev["wb-mdm3_123/K3"]; // Гардероб свет
    dev["wb-mdm3_123/K3"] = !dev["wb-mdm3_123/K3"]; // Холл свет
  }
});

(Тут я привел старый код, без логирования, сейчас он немного сложнее, так как я переделал на управление сценариями, но смысл остался тот же, в логе это бы выглядело как для Single Press Counter, только Long. Полный скрипт этого этажа я уже скидывал выше, если интересно).

Да, верно.
До перезапуска /devices/wb-mr6cv3_93/controls/Input 6 counter 27
Вот, для примера как выглядят изменения топика счетчика:

mosquitto_sub -v -t "/devices/wb-mr6cv3_93/controls/Input 6 counter"
/devices/wb-mr6cv3_93/controls/Input 6 counter (null)
/devices/wb-mr6cv3_93/controls/Input 6 counter 0

Тут видно что null публикуется при создании топика. А уже значение - после первого (успешного) чтения.

Поэтому у себя я запускаю правила работающие со значениями устройств через минуту после загрузки.

А можно пример реализации задержки запуска правил?
Я помню, что Вы отвечали мне на этот вопрос, но не могу найти ответ.

Самое простое - обернуть вызов defineRule в SetTimeout:

SetTimeout(defineRule({
    whenChanged: ...
    then: ...
  }), 20000);

где 20000 - 20 секунд (время в милисекундах)

Тут видно что null публикуется при создании топика. А уже значение - после первого (успешного) чтения.

Насколько я могу видеть в своем логе, это происходит немного иначе: топик создается не со значением null, а с неким (дефолтным?) значением, то-есть 0 или false, а зависимости от типа (допускаю, что это поисходит в 2 этапа, но примерно в одно время). А, когда wb-mqtt-serial подключает устройство и читает из него данные, топик инициализируется повторно.

По крайней мере, я попробовал сравнивать значение топика с null, и это не дало эффекта:

// Проверка статуса подключения устройства (devName)
function isConnected(devName) {
  try {
    // Считываем значение из dev[...] и сразу приводим к строке, чтобы при ошибке получить исключение
    var v = String(dev[devName]);
    if (dev[devName] == null)
    {
      setTimeout(function(){log(devName+" status OFFLINE (null)")}, 0);
      return false;
    }
    setTimeout(function(){log(devName+" status ONLINE, value: " + v)}, 0);
    return true;
  } catch (err) {
    // Исключение покажет, что данное устройство отсутствует
    setTimeout(function(){log(devName+" status OFFLINE")}, 0);
    return false;
  }
}

К сожалению, с первого же раза, как только wb-rules выполняет скрипт, у топика уже появляется значение отличное от null.
сен 19 12:06:03 wirenboard-AT5QCYO wb-rules[1812]: INFO: [rule info] wb-mdm3_123/K1 status ONLINE, value: false