[Железо] Удалённая прошивка AVR через ESP8266, находящихся за NAT-ом

Работа с конкретным оборудованием

Модераторы: immortal, newz20

Ответить
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

[Железо] Удалённая прошивка AVR через ESP8266, находящихся за NAT-ом

Сообщение Ko/|xo3HUk » Пн фев 19, 2018 9:12 pm

Доброго всем времени суток!
Появилась у меня такая необходимость - нужно управлять в гараже потолочным вентилятором (щас это называется по модному - дестратификатор :) ), думаю повесить два датчика температуры у пола и у потолка и по разнице температур включать плавно вентилятор, используя ШИМ. Также хочу получать статистику о температуре и частоте включений вентилятора. В гараже есть только мобильный интернет, достучаться извне нереально. А на первых порах, думаю, придётся часто корректировать программу микроконтроллера. Таскать каждый раз ноут в гараж нет никакого желания, разбирать всю схему и снимать МК, чтобы принести домой и заменить прошивку - лениво, да и не набегаешься, если что пойдёт не так.
Поэтому решил сделать связку Arduino Uno + ESP8266-E. За схему получения прошивки с сервера - взял схему обновления прошивки ESP8266 через OTA (Over-the-Air), только прошивать буду не саму ЕСП-шку, а скачивать прошивку для МК на ЕСП-шку, и после этого прошивать МК самой ЕСП-шкой.
Я не профессиональный программист, всего лишь любитель, к тому же с ЛУА-скриптами столкнулся впервые, так что можно считать этот проект пилотным дебютом :) . Заодно решил познакомиться с Гитхабом, поэтому выложил туда все исходники.

Схема работы, примерно, такая:
СпойлерПоказать
01.png
01.png (29.42 КБ) 10011 просмотров
1,2,3,4 - ESP8266 периодически опрашивает сервер на наличие новой прошивки. Смартфон находится за NAT-ом мобильной сети, не имеет белого IP-адреса, поэтому достучаться до него извне невозможно – ОпСоС (МТС в моём случае) блокирует весь входящий траффик
5,6,7,8 – При наличии новой прошивки на сервере – он передаёт её на ESP8266. Если нет новой прошивки – возвращает код и текст ошибки
9 – после получения новой прошивки – ESP8266 прошивает Ардуину через SPI-интерфейс.
Использованная документация, программы и исходники:
СпойлерПоказать
• NodeMCU Documentation – https://nodemcu.readthedocs.io/en/master/
• Создание кастомных прошивок от NodeMCU – https://nodemcu-build.com/
• Библиотека для загрузки файлов по HTTP – https://github.com/Manawyrm/ESP8266-HTTP
• Как прошить ардуину через ESP8266 – https://geektimes.ru/post/292801/
• Очень полезная статья о прошивке AVR по SPI – https://habrahabr.ru/post/152052/
• Софт для прошивки ESP8266 - NODE MCU PyFlasher
• ESP8266:Прошивки/Arduino/OTA-апдейты - http://wikihandbk.com/wiki/ESP8266:Прош ... TA-апдейты
Используемое оборудование:
СпойлерПоказать
• Ардуино UNO
• ESP8266-E
• LevelShifter для согласования уровней - копеечная платка с Алиэкспресса, типа такой.
1. Схема подключения
СпойлерПоказать
Схема подключения_схема.png
Схема подключения_схема.png (37.63 КБ) 10006 просмотров
Схема подключения_bb.png
Схема подключения_bb.png (51.15 КБ) 10011 просмотров
Вот как всё в время отладки выглядит :)
02.png
02.png (239.5 КБ) 10011 просмотров
2. Создание прошивки ESP8266
СпойлерПоказать
Идём на сайт https://nodemcu-build.com/ и создаём там прошивку со следующими модулями:
03.png
03.png (134.18 КБ) 10011 просмотров
(можно взять мою:
nodemcu-master-12-modules-2018-01-10-14-00-50-integer.zip
(269.9 КБ) 240 скачиваний
)
Получим на e-mail два файла: nodemcu-master-12-modules-2018-01-10-14-00-50-integer.bin и nodemcu-master-12-modules-2018-01-10-14-00-50-float.bin. Заливаем integer через софтину NODE MCU PyFlasher (не забываем указать «yes, wipe all data»!):
04.png
04.png (57.51 КБ) 10011 просмотров
После заливки прошивки запускаем ESPlorer (можно скачать здесь: https://esp8266.ru/esplorer/#download )
Указываем СОМ-порт и скорость и жмём кнопку «Open»
05.png
05.png (34.27 КБ) 10011 просмотров
После подключения открываем поочерёдно все файлы из репозитория: https://github.com/SergeyKopylov/ESP826 ... trough_NAT
(желательно init.lua грузить последним, т.к. он запускает все остальные файлы)
и загружаем в ESP-шку:
06.png
06.png (103.44 КБ) 10011 просмотров
3. Создаём в МажорДоМо сценарий "esp_ota_update" и копируем туда весь код из файла esp_ota_update.php из репозитория
4. Получаем HEX-файл для прошивки:
СпойлерПоказать
Для получения HEX-файла прошивки в Arduino IDE:
07.png
07.png (23.68 КБ) 10011 просмотров
После этого открываем папку с файлом этого проекта и видим, что появились два HEX-файла:
08.png
08.png (6.74 КБ) 10011 просмотров
Переименовываем hex-файл с бутлоадером (на тот случай, если вдруг захочется впоследствии прошивать ардуину снова из Arduino IDE) в файл с именем «file_name.hex», где file_name – это имя, которое мы назначили данной ESP-шке в скрипте MajorDoMo “esp_ota_update” (в моём случае это будет файл: Garage_Fan.hex):
09.png
09.png (99.56 КБ) 10011 просмотров
И копируем переименованный файл в папку на сервере, которая прописана в нашем скрипте esp_ota_update:
10.png
10.png (59.1 КБ) 10011 просмотров
В моём случае это папка «../www/bin/»
Теперь остаётся подождать когда в ESP-шке по таймеру сработает проверка наличия новой прошивки, скачает эту прошивку и зальёт её в МК.
Авторизация
СпойлерПоказать
Если на сервере настроена авторизация, то нужно указать логин:пароль в хидере. Например, для логина:пароля «admin:password» - заходим на сайт, к примеру, http://sitespy.ru/base64
И пишем там admin:password
11.png
11.png (18.79 КБ) 10011 просмотров
В результате в хидере нужно прописать следующую строку:

Код: Выделить всё

"Authorization: Basic YWRtaW46cGFzc3dvcmQ= \r\n"..
За это сообщение автора Ko/|xo3HUk поблагодарил:
skysilver (Пн фев 19, 2018 9:28 pm)
Рейтинг: 1.16%
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение Ko/|xo3HUk » Вт фев 20, 2018 8:08 am

Добавлю немного описаний Луа-скриптов.

init.lua
СпойлерПоказать

Код: Выделить всё

print ( "Waiting ...")

wifi.setmode(wifi.STATION)
local cfg={}
cfg.ssid="WiFi"
cfg.pwd="wifi_password"
wifi.sta.config(cfg)
cfg = nil
collectgarbage()
tmr.delay(2000)

-- Каждые 5 минут запускаем функцию проверки наличия новой прошивки
local mytimer = tmr.create()
mytimer:register (300000, tmr.ALARM_AUTO, function (t)  
    if (wifi.sta.getip() ~= nil) then
        print ("Запуск проверки наличия новой прошивки")
        dofile ( "check_firmware.lua")
    else
        print("Нет доступной сети WiFi")
    end;
end)
mytimer:start (0)
Здесь всё стандартно - этот скрипт запускается сразу же при старте есп-шки. Поэтому здесь организовано подключение к заданной сети "WiFi" с паролем "wifi_password". Имя сети и пароль нужно поменять на свои настройки.
Далее вызывается таймер, который автоматически перезапускается, и считает 300000 мсек = 5 минут. Как только отсчитал заданное время - происходит запуск скрипта "check_firmware.lua" для проверки наличия новой прошивки. Это время я выбирал для проверки работы скриптов. Можно, в принципе, увеличить время, чтобы проверка происходила раз в полчаса-час.
Позже здесь добавлю ещё вызов функции для передачи на сервер температуры и частоты включений вентилятора, пока же реализована только прошивка МК.
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение Ko/|xo3HUk » Вт фев 20, 2018 8:49 am

check_firmware.lua
СпойлерПоказать

Код: Выделить всё

--**************************************************************
-- sending a GET request and get the return value
--Feed the system watchdog.
--In general, if you ever need to use this function, you are doing it wrong :) - https://nodemcu.readthedocs.io/en/dev/en/modules/tmr/#tmrwdclr
tmr.wdclr()

local sketch = "sketch_download"   --Имя файла на ESP8266, куда будет ложиться прошивка (скетч)
local extension = "no_ext"

    if (file.exists(sketch)) then
        md5 = crypto.toHex(crypto.fhash("md5",sketch))
    else
        --файла нету, прописываем 'левый' хэш:
        md5 = "NoSketchFilePresent"
    end

local remaining, used, total=file.fsinfo()
local majorVer, minorVer, devVer, chipid, flashid, flashsize, flashmode, flashspeed = node.info()

http.get("http://192.168.1.2/objects/?script=esp_ota_update&sketch_req=NewSketchChecking",
              "x-esp8266-sta-mac: "..wifi.sta.getmac().."\r\n"..
              "x-esp8266-sta-ip: "..wifi.sta.getip().."\r\n"..
              "x-esp8266-ap-mac: "..wifi.ap.getmac().."\r\n"..
              "x-esp8266-free-space: "..node.heap().."\r\n"..
              "x-esp8266-chip-size: "..node.flashsize().."\r\n"..
              "x-esp8266-chip-id: "..chipid.."\r\n"..
              "x-esp8266-sdk-version: "..majorVer.."."..minorVer.."."..devVer.."\r\n"..
              "x-esp8266-mode: "..wifi.getmode().."\r\n"..
              "x-esp8266-fs-total: "..total.."\r\n"..
              "x-esp8266-fs-used: "..used.."\r\n"..
              "x-esp8266-fs-remaining: "..remaining.."\r\n"..
              "x-esp8266-sketch-md5: "..md5.."\r\n"..
              "x-esp8266-extension: no_ext\r\n"..
              "x-file-name: noname\r\n"..
              "Connection: keep-alive\r\n"..
              "Accept-Charset: utf-8\r\n"..
              "Accept-Encoding: \r\n"..
              "User-Agent: ESP8266-http-Update\r\n".. 
              "Host: homeserver\r\n"..
              "Authorization: Basic YWRtaW46cGFzc3dvcmQ=\r\n"..
              "Accept: */*\r\n\r\n",
              function(code, data, headers)
                    print("code = "..code.."\tdata = "..data)
                    print("x-file-name = ", headers["x-file-name"])
                    print("x-esp8266-extension = ", headers["x-esp8266-extension"])
                    print("content-length = ", headers["content-length"])
if (headers["x-esp8266-extension"] == nil) then
    print("Нет новой прошивки или отсутствует связь с сервером")
    extension = "no_sketch"
elseif (headers["x-esp8266-extension"] == "hex") then
    print("Используемый тип файла прошивки = "..headers["x-esp8266-extension"])
    extension = "hex"
--    sketch = "sketch.hex"
elseif (headers["x-esp8266-extension"] == "bin") then
    print("Используемый тип файла прошивки = "..headers["x-esp8266-extension"])
    extension = "bin"
--    sketch = "sketch.bin"
else
    print("Используемый тип файла прошивки - неизвестен = "..headers["x-esp8266-extension"])
    extension = "unknown"
end
                if (code < 0) then
                    print("HTTP request failed")
                else
                    if (data == "OK") then
--**************************************************************
-- Download a file

tmr.wdclr()

httpDL = require("httpDL")
collectgarbage()

httpDL.download("192.168.1.1", "80", "objects/?script=esp_ota_update", sketch, md5, function(ret_val)
    -- Finished downloading
remaining = nil
flashspeed = nil
flashsize = nil
majorVer = nil
minorVer = nil
flashmode = nil
devVer = nil
md5 = nil
used = nil
flashid = nil
chipid = nil

package.loaded["httpDL"]=nil
httpDL = nil
conn = nil
collectgarbage()

if (ret_val == nil) then
    print("ret_val == nil.. Что-то пошло не так...")
elseif (ret_val == "ok") then
    if (extension == "bin") then
        print("extension = bin")
        -- Начинаем прошивку
        require("Program_Flash")
        --Program_Flash ("sketch.bin")
        Program_Flash ("sketch_download")
        package.loaded["Program_Flash"]=nil
        Program_Flash = nil
    elseif (extension == "hex") then
        print("extension = hex")
        -- Копируем файл прошивки в sketch.hex
        local source = file.open(sketch, "r")
        local destin = file.open("sketch.hex", "w")
        local size = source:seek("end")
        source:seek("set",0)
        destin:seek("set",0)
        destin:write(source:read(size))
        destin:close()
        source:close()
        destin = nil
        source = nil
        -- Начинаем прошивку
        require("Program_Flash")
        Program_Flash ("sketch.hex")
        package.loaded["Program_Flash"]=nil
        Program_Flash = nil
    elseif (extension == "no_sketch") then
        print("extension = no_sketch")
    elseif (extension == "unknown") then
        print("extension = unknown")
    else
        print("Странное значение extension... Что-то пошло не так...")
    end  
elseif (ret_val == "failed") then
    print("ret_val == failed. Downloading was failed! (MD5 does not match)")
else
    print("Странное значение ret_val... Что-то пошло не так...")
end


end)

collectgarbage()
--==============================================================
                    end

                end

    remaining = nil
    flashspeed = nil
    flashsize = nil
    majorVer = nil
    minorVer = nil
    flashmode = nil
    devVer = nil
    md5 = nil
    used = nil
    flashid = nil
    chipid = nil
    collectgarbage()
               
end)
Данный скрипт вычисляет md5 хэш файла "sketch_download", который создаётся при скачивании файла прошивки с сервера. Если хэш уже скачанного файла совпадает с хэшем файла прошивки на сервере - то ничего не выполняем, т.к. файлы совпадают.
Здесь реализован предварительный запрос на сервер, который выполняется функцией "http.get("http://192.168.1.2/objects/?script=esp_ ... chChecking"". Сервер, видя такой запрос, вызывает сценарий "esp_ota_update" и проверяет что за запрос пришёл - запрос на проверку новой прошивки (sketch_req=NewSketchChecking), либо запрос на скачивание файла прошивки (sketch_req не указывается в запросе), либо запрос на проверку корректности скачанного файла (sketch_req=AfterChecking). За обработку этого запроса отвечает данная часть php-кода сценария "esp_ota_update":
СпойлерПоказать

Код: Выделить всё

if ($params['sketch_req']=="AfterChecking") {
DebMes("AfterChecking()"); // После этого в XRay во вкладке debug можно смотреть результат.
     AfterChecking($localBinary);
DebMes("==================================================================="); // После этого в XRay во вкладке debug можно смотреть результат.
}
else {

    if($_SERVER["HTTP_X_ESP8266_SKETCH_MD5"] != md5_file($localBinary)){
        if ($params['sketch_req']=="NewSketchChecking") {
DebMes("Answer()"); // После этого в XRay во вкладке debug можно смотреть результат.
            Answer($localBinary, $file_extension);
foreach (getallheaders() as $name => $value) 
  {
    DebMes("Answer. getallheaders -> $name: $value\n"); // После этого в XRay во вкладке debug можно смотреть результат.
  }
        }
        else {
DebMes("sendFile(localBinary)"); // После этого в XRay во вкладке debug можно смотреть результат.
DebMes("file_extension = ".$file_extension); // После этого в XRay во вкладке debug можно смотреть результат.
            sendFile($localBinary, $file_extension);
            foreach (getallheaders() as $name => $value) 
            {
                DebMes("SendFile. getallheaders -> $name: $value\n"); // После этого в XRay во вкладке debug можно смотреть результат.
            }
        }
DebMes("==================================================================="); // После этого в XRay во вкладке debug можно смотреть результат.
    } else {
//DebMes($_SERVER["SERVER_PROTOCOL"]." 304 Not Modified"); // После этого в XRay во вкладке debug можно смотреть результат.
DebMes($_SERVER["HTTP_X_ESP8266_SKETCH_MD5"]." <- SERVER[HTTP_X_ESP8266_SKETCH_MD5]"); // После этого в XRay во вкладке debug можно смотреть результат.
DebMes(md5_file($localBinary). " <- md5_file(localBinary)"); // После этого в XRay во вкладке debug можно смотреть результат.
DebMes("304 Not Modified"); // После этого в XRay во вкладке debug можно смотреть результат.
DebMes("==================================================================="); // После этого в XRay во вкладке debug можно смотреть результат.
        header($_SERVER["SERVER_PROTOCOL"].' 304 Not Modified',true, 304);
        echo "You have actual sketch, no need to download\n";
        foreach (getallheaders() as $name => $value) 
        {
            DebMes("Sketch Not Modified. getallheaders -> $name: $value\n"); // После этого в XRay во вкладке debug можно смотреть результат.
        }
    }
}
Если нет новой прошивки - то сервер разрывает соединение с кодом 304 и текстом "You have actual sketch, no need to download"
Если есть новая прошивка, то вызывается скрипт httpDL.lua для скачивания файла с сервера

Код: Выделить всё

httpDL.download("192.168.1.1", "80", "objects/?script=esp_ota_update", sketch, md5, function(ret_val)
После скачивания файла проверяется расширение файла. Для прошивки МК можно использовать либо BIN-файл, либо HEX-файл. Изначально я сделал прошивку только BIN-файлами, но это оказалось неудобно, нужно было конвертировать прошивку вручную отдельной софтиной, поэтому реализовал на есп-шке преобразование hex-файла в bin-файл.
Если скачался hex-файл, то копируем файл "sketch_download" в файл "sketch.hex"
После того, как скачалась новая прошивка - её хэш проверяется снова с хэшем на сервере, и если он совпадает - то запускаем скрипт "Program_Flash.lua" для прошивки МК.
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
smart_g
Сообщения: 292
Зарегистрирован: Вт окт 17, 2017 11:29 am
Откуда: Украина, Киев
Благодарил (а): 15 раз
Поблагодарили: 33 раза

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение smart_g » Вт фев 20, 2018 12:29 pm

К чему фраза, что есть только мобильный интернет. А решение - подключение по WIFI. Если добивает в гараж WIFI, то есть решение WIFI-to-Com. Подключить таким образом ардуино и все.
Ubuntu Server 16.04 LTS, OpenWRT 1-wire, SonOFF, MYSensors, MiHome, Yeelight, MiFlower
smart_g
Сообщения: 292
Зарегистрирован: Вт окт 17, 2017 11:29 am
Откуда: Украина, Киев
Благодарил (а): 15 раз
Поблагодарили: 33 раза

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение smart_g » Вт фев 20, 2018 12:30 pm

А с мобильным интернетом VPN наше все, стучись не хочу.
Ubuntu Server 16.04 LTS, OpenWRT 1-wire, SonOFF, MYSensors, MiHome, Yeelight, MiFlower
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение Ko/|xo3HUk » Вт фев 20, 2018 1:21 pm

smart_g писал(а):Если добивает в гараж WIFI...
Если б у меня был гараж в пределах досягаемости WiFi - я б так не заморачивался :) До гаража больше километра (прямой видимости нет), wifi не достаёт.
smart_g писал(а):А с мобильным интернетом VPN наше все, стучись не хочу.
Ну ка, расскажите мне, как организовать данное решение? Допустим, я поднял OpenVPN между сервером и мобилой (уже делал это), как дальше мне получить данные с ардуины на домашний сервер?
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
smart_g
Сообщения: 292
Зарегистрирован: Вт окт 17, 2017 11:29 am
Откуда: Украина, Киев
Благодарил (а): 15 раз
Поблагодарили: 33 раза

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение smart_g » Вт фев 20, 2018 1:51 pm

У меня на даче стоит роутер с OpemWRT. WAN по 3G-GPRS. На нем поднят VPN клиент. Вся сеть за роутером доступна мне. Хоть по SSH, хоть шары устройств за роутером. Вот и все. Описаний как это сделать в инете масса.
Ubuntu Server 16.04 LTS, OpenWRT 1-wire, SonOFF, MYSensors, MiHome, Yeelight, MiFlower
smart_g
Сообщения: 292
Зарегистрирован: Вт окт 17, 2017 11:29 am
Откуда: Украина, Киев
Благодарил (а): 15 раз
Поблагодарили: 33 раза

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение smart_g » Вт фев 20, 2018 2:01 pm

А по поводу ардуины, смотря как она подключена в сетку. VPN это сетевое решение. Можно вообще без ардуины обойтись, все решить на роутере. У меня в роутере стоит I2C-to-1wire переходник, поднят OWFS и MQTT клиент и данные по cron гонятся на сервер.
Ubuntu Server 16.04 LTS, OpenWRT 1-wire, SonOFF, MYSensors, MiHome, Yeelight, MiFlower
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение Ko/|xo3HUk » Вт фев 20, 2018 2:03 pm

Понятно. Нужен роутер, у меня его на данный момент нет, так что пока остановлюсь на своём варианте. Он мои задачи выполняет.
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
Ko/|xo3HUk
Сообщения: 160
Зарегистрирован: Ср окт 07, 2015 9:36 am
Благодарил (а): 51 раз
Поблагодарили: 27 раз

Re: Удалённая прошивка AVR через ESP8266, находящихся за NAT

Сообщение Ko/|xo3HUk » Вт фев 20, 2018 2:06 pm

smart_g писал(а):А по поводу ардуины, смотря как она подключена в сетку.
Вот почему я и выбрал есп-шку - он выполняет роль как модуль связи ардуины с сервером, так и выполняет функцию прошивки этой ардуины. 2 в 1, так сказать.
Текущий сервер: Ноутбук: HP Probook 4515s (без монитора). ОС: Debian GNU/Linux 8.6 (jessie)
Предыдущий сервер: Raspberry Pi 2B. ОС: Raspbian (jessie)
Ответить