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

Добрый день! Предыстория: сделал у себя дома управление светом через реле (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. И всё… и не будет свет мигать по ночам.
Только ленивый человек мог придумать трактор.

1 лайк

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

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

1 лайк

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

2 лайка

Тут видно что 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