Итак, мой умный дом готов, слушается голоса, управляет климатом, зарядкой аккумулятора на даче.
Более того, умные устройства стоят теперь как на даче, так и дома, в городе. Причем из-за особенностей совместимости экосистем с Яндексом часть устройств дома (RGB ленты) управляются через сервер на Majordomo (дача).
И вот тут возникает ряд логичных вопросов:
· где должен стоять сервер – дома или на даче?
· Потерей управления какими устройствами жертвовать при обрыве связи между домом и дачей?
· Как не грузить GSM канал до дачи передачей графиков в HTML верстке сайта?
Легко догадаться, что ответом является резервирование:
1. Серверы должны быть и там и там
2. Серверы должны уметь управлять всеми устройствами
3. Серверы должны иметь полный набор данных
Так как датчики общаются с сервером в основном через протокол MQTT, MQTT брокер так же становится точкой отказа.
Резервирование сервера
Начнем с MQTT брокера. Если не считать таких сообщений, как LWT («последняя воля устройства») и Retain (хранимых на сервере), большинство сообщений передаются одномоментно и только тем, кто в данный момент подключен к брокеру. То есть «отправил — забыл».
К счастью, в последних версиях mosquitto сервера есть режим бриджа – вы просто задаете адрес второго брокера и топики, которые нужно дублировать, направления дублирования. В моем случае вполне пригодился вариант «копировать все в обе стороны». Вот как это делается в raspbian/armbian – добавляем в /etc/mosquito/mosquito.conf:
#connection bridge-01 connection bridge-01 address mqtt.mydomain.ru:1883 topic # out 0 topic # in 0
Вуаля, оба брокера содержат одни и те же данные, локальные устройства могут общаться с локальным брокером в локальной сети (без задержек и прочей чепухи ненадежных каналов).
Дальше, сами серверы Majordomo. Я сделал второй сервер на базе Orange pi one plus (1Gb RAM) – стоит в 2 раза дешевле Raspberry Pi4, для вспомогательного сервера — то, что надо. Но серверы должны уметь делать одно и то же, но не делать этого одновременно (в большинстве случаев это не страшно, ну поступит 2 команды на включение зарядника – не страшно, но некоторые вещи лучше дважды не делать, например, не поворачивать солнечные панели по часам).
Так как для корректной работы с датчиками и исполнительными устройствами я использую MQTT, логично отслеживать работоспособность удаленного сервера через тот же MQTT. Для этого я создал отдельный класс, в котором есть 2 статуса (для отображения) и 2 времени – для локального сервера и для удаленного, а также адрес активного брокера и собственный адрес сервера. Раз в 10 секунд выполняется проверка системного цикла MQTT – время последнего запуска (ThisComputer.cycle_mqttRun). Это время сравнивается с текущим (time()). Если прошло больше 10 секунд – паникуем, то есть понимаем, что локальный сервер не дружит с MQTT брокером и показываем это в интерфейсе. Так же сравниваем время последнего запуска MQTT цикла на удаленном сервере (приходит через MQTT). Если прошло больше 20 секунд, а локальный цикл в порядке – понимаем, что удаленный сервер больше не управляет устройствами. Проверяем еще один параметр, передаваемый через MQTT – имя активного брокера. Если это не локальный, то надо переключать на себя:
$val=getGlobal("ThisComputer.cycle_mqttRun"); $locval=time()-$val; $this->setProperty("LocValue",$val); $this->setProperty("LocDeltaT",$locval); if($locval>10) $locstate=1; else $locstate=0; $tmp=$this->getProperty("Status"); if(is_null($tmp)) $tmp=10; if($tmp!=$locstate) $this->setProperty("Status",$locstate); $remval=time()-$this->getProperty("RemValue"); $newstate=($remval<20)?0:1; $this->setProperty("RemStatus",$newstate); $ot = $this->object_title; $currBroker=$this->getProperty("MQTT_broker"); $sA=$this->getProperty("selfAddress"); if($sA!=$currBroker) $this->setProperty("isController",0); setTimeOut($ot . "_checkCycle",'callMethod("'.$ot.'.checkCycle");',10); if( (!$locstate&&($newstate||($this->getProperty("LinkedRoom")=="Energoblok")))&& ($sA!=$currBroker) )// remote failed local good or local is good and is not local server { debMes('Switch to '.$this->getProperty("selfAddress"),0); $cnt=0; for($i=40;$i<90;$i++) { if(ping('192.168.3.'.number_format($i,0))) { getURL('http://192.168.3.'.number_format($i,0).'/cm?cmnd=MqttHost%20'.$this->getProperty("selfAddress")); debMEs('http://192.168.3.'.number_format($i,0).' is online',0); $cnt++; $this->setProperty("LocValue",time()); } } if($cnt>10) { $this->setProperty("MQTT_broker",$this->getProperty("selfAddress")); $this->setProperty("isController",1); } }
У меня Tasmota устройства (IP в диапазоне c 192.168.3.40 по 192.168.3.90), им можно передать обычным URL запросом новый адрес MQTT сервера. Вот только запросы надо посылать синхронные, а главное – не забывать между ними обновлять MQTT свойство для удаленного сервера. Иначе получится замкнутый цикл – начали переключаться, больше 10 секунд не сообщаем удаленному серверу, что мы живы. Тот начинает переключение на себя и тоже замирает. Не делайте так 🙂
Повышаем надежность самого сервера
Операционная система и БД хранятся на карте памяти. Есть карты класса А1 и даже А2, но через год постоянной нагрузки такая карта с большой вероятностью загнется. Кроме того, штатный код пишет в базу кучу ненужного, а каждое чтение/запись свойства любого объекта – это обращение к БД. У меня было порядка 1200 обращений к БД в секунду.
Карту можно спасти, если базу держать в оперативной памяти. К счастью, разработчики Majordomo сделали прошивку для Raspberry сразу с опцией БД в памяти, а для прочих платформ есть скрипт для переноса БД в память (но памяти должно быть не менее 1Гб, на orange pi zero c 512Мб у меня не взлетело — база весит порядка 300Мб и столько же надо дополнительно для дампов бэкапов). Да, теперь в любой момент просто так перезагружать систему нельзя, нужно выполнить скриптик, иначе данные за последние полчаса потеряются (но база всегда бэкапится в рабочем состоянии!). Зато скорость работы БД и долговечность карты памяти – просто великолепны.
Остался последний штрих – снизить нагрузку на БД, убрав лишние запросы. Решение простое:
-
Обновиться до последней версии (буквально пару недель назад обновили интерфейс, убрав лишние обращения к БД и переписав на java скрипты)
-
Обращаться к локальным или глобальным свойствам только один раз и использовать переменные в памяти (смотрите на пример кода цикла проверки – getProperty\setProperty там использованы только по одному разу).
Еще пример оптимизации скриптов – чтобы и обращений лишних не было, и чтобы срабатывала автоматическая зависимость от переменной, например:
if((($temp2Floor=getGlobal("sTemp2Floor.value"))<'21')&& gg("remote_mqtt_updated.isController")) // if remote failed { if ($temp2Floor < '21' && !getGlobal("rConserveSW.status") && timeBetween('2:00', '8:00')) { if (!getGlobal("rDieselHome.status")) { callMethod("rDieselHome.turnOn"); } } else if ($temp2Floor > '23') { if (getGlobal("rDieselHome.status")) { callMethod("rDieselHome.turnOff"); } } }
Обратите внимание, что в первом условии есть ветка проверки, этот ли сервер управляет устройствами в настоящий момент (gg(«remote_mqtt_updated.isController»)). remote_mqtt_updated – это объект контроля работы серверов.
Теперь у меня число обращений к БД порядка 380 в секунду, что гораздо лучше по сравнению с начальным значением в 1200.
Итог
Вот так, добавив сервер за 2500 рублей, получил полное резервирование брокера сообщений, сервера управления устройствами (логики) и можно получать графики с актуальными данными с домашнего сервера, не нагружая сервер, работающий через GSM модем.
ссылка на оригинал статьи https://habr.com/ru/post/544478/
Добавить комментарий