ESP8266 Multi Broker

Ситуация нечастая, но если происходит, то выбешивает зверски.

Устройства на даче «висят» на брокере, находящемся в городской квартире. За последние лет десять, если и были сбои в этой связке, то на стороне дачного интернета.

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

Вместе с тем, дома отсутствует информация о происходящем. И еще грустнее, когда «ломается» домашний Интернет. Таки это случилось, с 14 февраля и по сей день(19 февраля) Интернета нет и … есть уже три обещания починить «21 февраля(!!!!! Медвежий угол!) до 12 часов.» Провайдер — Алмател.

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

А что бы сделать с этой несправедливостью? Почему бы не предложить каждому устройству, в зависимости от доступности, на выбор штуки три брокера? Первый, главный — квартира. К нему прицеплен Домотикз. Второй — поставить брокер на даче, благо старых роутеров и малин хватает. Третий — общедоступный брокер, типа mqtt.eclipse.org. Устройство переключается между брокерами и периодически проверяет доступность своего, главного. Связи нет — возвращается к доступному.

Если «валится» основная система — ничто не мешает подключиться к запасным и в ручном режиме, через MqttSpy или MqttFX поинтересоваться делами.

И скажу честнее — несмотря на декларации о суверенитете каждого устройства, есть некоторые, что все таки требуют сторонней информации — выключатели света должны знать об уровне освещенности, управление отоплением мастерской входит в аварийный режим (Оно работает, но без обратной связи. Греет мастерскую на среднюю мощность), если не имеет данных о температуре в помещении.

Ок, как будем действовать.

Напрашивается первый шаг — создать таблицу с адресами брокеров. Шаг разумный, но затратный по памяти. Поход выбрал такой — делаю список брокеров в отдельном файле и читаю его, при необходимости.

Файл brklist.lua очень прост:

мой.брокер.ru,8883
192.168.1.84,1883
mqtt.eclipse.org,1883

Добывать строки из списка будет файл brkget.lua так:

-- возвращаем функцию, что принимает номер строки
return function(n)
    local dt
    -- открываем файл с перечнем брокеров и читаем построчно до нужной
    if file.open("brklist.lua", "r") then
        for i = 1, n do
            dt = file.readline()
        end
        file.close()
    end
    if not dt then print('Lost List'); return end
    -- через захваты выделяем брокер и порт
    local brk, port = string.match(dt, "(.+),(%d+)")
    return brk, port
end

Применять связку вышеименованных файлов надо так:

brk, port = dofile('brkget.lua')(номерстроки)

Теперь несколько мыслей. Объект mqtt будет периодически «изготавливаться» неким куском кода и возвращаться в оперативную память, ибо он там обязан висеть постоянно. Этот код может быть изготовлен отдельным файлом, ибо постоянно в памяти он сам не нужен.

Объект этот, также, обязан иметь (тоже в оперативной памяти) две реакции на события «потеря связи с брокером» и «пришло сообщение от брокера». Последнее важно даже для одиночного датчика температуры, потому что нынче я привык вызывать онлайн редактор для любого устройства и править там код.

Таким образом, для создания объекта MQTT потребуется еще два файла — один вызываемый периодически для конструирования mqtt а другой — единожды, для заготовки событий «offline» и «message».

mqttset.lua, вызывается один раз и укладывает в память типовые реакции на события mqtt:

do
-- Строка в списке брокеров 
dat.nobrk = 1
-- Количество ошибок подключения к основному брокеру
-- ибо любая случайная ошибка должна приводить к попытке восстановления
-- связи, а не к пререключению на запасной брокер 
dat.erhome = 0
-- Резервируем переменные для реакций, одинаковых для
-- любого брокера
local subscribe, merror, mconnect, msg
-- Что делаем после соединения с брокером 
function subscribe(con)
    dat.broker = true
    con:subscribe(dat.clnt.."/com/#", 0)
    con:publish(dat.clnt..'/state', "ON", 0, 1)
    print("Subscribed to "..dat.clnt.."/com/# Heap: "..node.heap())
    dat.erhome = 0
    -- Если это не первая строка (не главный брокер), то будет попытка
    -- пересоединения, вызывается соответствующий таймер
    if dat.nobrk ~= 1 then dofile('brkkill.lua')(mconnect) end
end
-- Реакция на ошибку одинакова для всех брокеров
-- уничтожение соединения, объекта и повторное его создание
function merror(con, reason)
    con, reason, m = nil,nil,nil
    tmr.create():alarm(10000, tmr.ALARM_SINGLE, mconnect)
    collectgarbage()
end
-- Приход сообщения
function msg(con, top, dt)
    -- Таблица для вставки и передачи топика и даты на обработку
    if not killtop then killtop = {} end
    -- Захват топика из всей подписки
    top = string.match(top, "/(%w+)$")
    print('Got', top, dt)
    if dt then
        -- Вставляем данные в глобльную таблицу и вызываем обработчик
        table.insert(killtop, {top, dt})
        if not dat.analiz then
            dofile("mqttanalize.lua")
        end
    end
end
-- Функция создания соединения и соединение
function mconnect()
    -- Вызываем файл создания соединения и передаем ему ссылки
    -- на общие реакции или функцию обработки ошибки, если нет wifi
    if (wifi.sta.getip()) then   
        return dofile('mqttmake.lua')(subscribe, merror, msg)
    else
        return merror()
    end
end
-- Первый вызов
mconnect()
end

Файл mqttmake.lua вызывается многократно, создает новый mqtt и вызывает соединение:

-- Возвращает функцию с аргументами типовых событий
return function(subscribe, merror, msg)
    -- Небольшой счетчик для обеспечения трех попыток соединения с
    -- домашним сервером
    if dat.erhome > 2 then
        dat.nobrk = dat.nobrk == dat.brkn and 1 or dat.nobrk + 1  
    else dat.erhome = dat.erhome + 1 end
    -- Получаем очередного брокера
    local brk, port = dofile('brkget.lua')(dat.nobrk)
    -- Создаем соединение и вызываем его
    m = mqtt.Client(dat.clnt, 25, dat.clnt, 'pass22')
    m:lwt(dat.clnt..'/state', "Off", 0, 1)
    m:on("offline", merror)
    m:on("message", msg)
    print('Connect to', brk, 'Heap', node.heap())
    m:connect(brk, port, false, subscribe, merror)
end

Для обеспечения работы всего выше указанного, заранее должна быть создана таблица dat с таким содержимым:

dat = {}
-- Номер строки брокера
dat.nobrk = 1
-- Имя клиента MQTT
dat.clnt = 'test8266/'-- ..node.chipid()
-- Количество строк в файле списка брокеров
dat.brkn = 3
dofile'mqttset.lua'

Общий типовой шаблон запуска скриптов с init.lua, ide.lua, etc., со всем перечисленным ранее и кое-какими изменениями (ибо выше описана только логика) — по ссылке ниже.

ESP8266 Multi Broker: 4 комментария

    1. Что я там забыл. У меня сейчас столярка на первом месте, переделка подсветки и еще туча туча дел разного калибра. Нет времени что-то читать.

      Нравится

Оставьте комментарий