Как стать автором
Обновить

Разработка защищённого WEB интерфейса для микроконтроллеров

Время на прочтение18 мин
Количество просмотров10K

Цель - разработка компактного, простого, быстрого, защищённого и адаптивного WEB интерфейса для встраиваемого устройства на микроконтроллере. Микроконтроллер на ядре ARM Cortex-M, с размером Flash 1...4 Мегабайта и RAM 200...1000 Кбайт, частота ядра 100-1000... МГц.

А WEB интерфейс в данном случае предназначен для настройки параметров и управления в локальной но при этом небезопасной сети со смартфонов или других мобильных гаджетов.

Выбор аппаратной платформы

Платформа в виде платы должна иметь чип или модуль Wi-Fi, микроконтроллер, желательно SD карту или чип внешней памяти ёмкостью не менее нескольких мегабайт.

Выбор RTOS

В проектируемом устройстве WEB сервер играет вторичную роль. Кроме него будут работать ещё десятки задач. Организовать WEB сервер так чтобы его присутствие не влияло на работу остальной функциональности можно с помощью RTOS.

От middleware RTOS нам нужен развитый набор сервисов синхронизации, вытесняющая многозадачность, надёжный менеджер динамической памяти, быстрая файловая система для SD или eMMC карт, стек TCP/IP протоколов и очень желательно сервисные инструменты отладки.
Все это есть в Azure RTOS. Эта RTOS имеет очень длинную и славную историю с тех пор, когда ещё называлась ThreadX. Она нашла применение в нескольких миллиардах устройств. Её отличает надёжность, компактность и хорошая документация. Долгое время была коммерческой и очень дорогой. Впервые в открытом виде появилась в пакете ПО Synergy от Renesas для микроконтроллеров Synergy на базе ARM Cortex-M4, и сразу получила широкую популярность благодаря исключительно богатой палитре предоставляемого middleware.  Теперь портированное middleware Azure RTOS доступно бесплатно и для STM32.

Выбор WEB сервера

Верхним протоколом, на котором непосредственно базируется WEB сервер является HTTP.
Azure RTOS уже имеет в своём составе HTTP сервер, работающий поверх стека сетевых протоколов Azure RTOS NetX Duo. HTTP Azure, согласно документации, требует всего от 3.0 KB до 9.5 KB FLASH и от 0.5 KB до 2 KB ОЗУ. Ниже будет дана более реалистичная оценка необходимого объема ОЗУ.
Поскольку NetX Duo обладает интерфейсом BSD, то есть возможность достаточно легко адаптировать другие сторонние WEB сервера, хотя тесная связь таких серверов с нижележащим API делает такой выбор малоэффективным.

Свойства WEB сервера реализованного в Azure RTOS:

  • Поддержка 2-х типов авторизации: basic (передача пароля в открытом виде) и digest (передача хэша пароля),

  • Работа по  протоколам HTTP и HTTPS (HTTP защищённый с помощью TLS)

  • Чтение файлов страниц с SD карты или другого носителя с FAT32

  • Обработка запросов: GET, POST, HEAD, PUT, DELETE

  • Поточная передача без указания размера данных: Content-Length

  • Работа нескольких подключений одновременно.

Полностью документация на WEB сервер здесь.

Технология работы WEB интерфейса

Способ работы сервера очень простой. Устройство получает через TCP соединение текстовую строку запроса от браузера пользователя сформированную согласно спецификации HTTP.
Устройство также должно ответить строкой по спецификации HTTP. Поэтому WEB сервер в Azure RTOS называется HTTP сервером. HTTP сервер Azure RTOS сразу готов отдавать по запросам браузера файлы с SD карты устройства. Но статические страницы мало интересны.

Самой распространённой технологией в WEB серверах встраиваемых устройств является технология Server Side Includes  (SSI).  В Azure WEB сервере такой технологии нет. Видимо она считается устаревшей. Действительно, SSI позволял придать динамичность страницам, когда в браузере была отключена возможность исполнять JavaScript. Теперь же JavaScript включён повсеместно, без него сложно добиться адаптируемости.

Более продвинутым считается способ взаимодействия браузера и сервера с помощью технологии AJAX. Т.е. браузер с помощью JavaScript после загрузки страницы в отдельном потоке запрашивает дополнительные данные из устройства. Сервер в устройстве на лету формирует блок запрашиваемых данных и отправляет браузеру в таком же виде как он посылал страницы т.е. по спецификации HTTP.

Никто, конечно, не мешает реализовать и SSI в сервере Azure. Это делается очень просто и выльется в конечном итоге в длинную цепочку операторов if else с проверками на вхождение строк-директив. Но такой подход вызовет слишком тесную связность между страницами и кодом в микроконтроллере.

Гораздо привлекательней выглядит AJAX, причём с передачей файлов в JSON кодировке.
Дело в том, что JSON является форматом внутреннего представления объектов JavaScript в WEB страницах. Нет ничего проще для WEB разработчика чем передавать и принимать данные в формате JSON.   В JSON можно выполнить сериализацию буквально всего: параметров, таблиц параметров, баз данных параметров, иерархических деревьев параметров, представления параметров в виде виджетов и прочее.

JSON кодировка довольно простая. JSON в конечном счёте просто строка. Внутри неё нельзя использовать байт 0, поэтому эта строка легко интерпретируется как C-и строка. Одновременно это же обстоятельство позволяет без перекодировки вставлять JSON в HTTP строку ответа.
Немного сложнее дела обстоят с парсингом JSON. Парсинг JSON необходим когда браузер клиента пришлёт устройству запрос например с отредактированными настройками. Простые JSON строки можно парсить и средствами языка C-и, но большие JSON строки уже требуют серьёзных парсеров.

Здесь можно посоветовать проект Jansson. Надо только знать что Jansson активно использует динамическую память, и если передавать в JSON около сотни числовых и строковых параметров, то для парсера может понадобиться около 50 Кбайт ОЗУ, зависит от длины имён переменных и длины самих переменных.

Дополнительные замечания по HTTP серверу Azure.

Многие настройки сервера находятся в файле конфигурации nxd_web_http_server_cfg.h. Его надо изучить и отредактировать нужные макросы. В частности нужно определиться с размером строки ресурса (это то что передается в URL при запросах GET). По умолчанию там очень маленький размер. Выставляем

#define NX_WEB_HTTP_MAX_RESOURCE                        128

Это означает, что если строка в GET будет длиннее 128 символов, то запрос будет проигнорирован.

MIME типы
Сервер Azure HTTP не поддерживает HTTP pipelining, но поддерживает передачу файлов один за другим в одном TCP соединении. WEB страницы как правило содержат ссылки на файлы стилей, скриптов, картинок и прочего. Все эти файлы скачиваются последовательно один за другим браузером клиента. Чтобы получить файл браузер посылает HTTP запрос. Сервер на старте отправки каждого файла отправляет HTTP заголовок, например такой:

HTTP/1.1 200 OK
Content-Type: text/html
Connection: keep-alive
Content-Length: 13210
Date: Sun, 17 May 2020 00:59:19 GMT
Cache-Control: max-age=1
Last-Modified: Sun, 17 May 2021 00:59:19 GMT

Здесь имеет большое значение содержание поля Content-Type. Если его указать неправильно браузер может неправильно отобразить страницу. Сервер Azure содержание  Content-Type устанавливает в соответствии с расширением передаваемого файла. Но список самих таких известных расширений у сервера небольшой, поэтому в файле nx_web_http_server.c дополняем массив _nx_web_http_server_mime_maps следующим образом:

/* Define basic MIME maps. */
static NX_WEB_HTTP_SERVER_MIME_MAP _nx_web_http_server_mime_maps[] =
{
    {"html",     "text/html"},
    {"htm",      "text/html"},
    {"txt",      "text/plain"},
    {"css",      "text/css"},
    {"js",       "application/javascript"},
    {"gif",      "image/gif"},
    {"jpg",      "image/jpeg"},
    {"png",      "image/png"},
    {"ico",      "image/x-icon"},
};

Способы размещения контента. Для более удобного и быстрого размещения статического контента и сопутствующих файлов на WEB сервере устройства можно применить FTP сервер. FTP сервер имеется в поставке Azure RTOS. Такие среды разработки как Adobe Dreamweaver способны автоматически обновлять контент на целевом FTP сервере содержащем контент WEB сайта. 

Ограничение видимости для WEB сервера Azure.
По умолчанию корневой директорией HTTP сервера Azure  является корневая директория SD карты.

Чтобы сервер мог считывать файлы только из определенной поддиректории ему надо установить функцией fx_directory_local_path_set локальный путь для задачи сервера. Это удобно делать при первом вызове в callback функции перехватчика запросов сервера. Указатель на callback функцию передаётся в задачу сервера при его создании с помощью функции nx_web_http_server_create. Весь HTTP сервер выполняется в одной задаче, поэтому такой метод работает.

Сжатие контента. Современные браузеры поддерживают компрессию передаваемых страниц и сопутствующих файлов. Компрессия типичных HTML файлов, скриптов и стилей позволяет уменьшит объем передаваемых данных в 3-4 раза. Сервер HTTP Azure не поддерживает компрессию на лету, для микроконтроллеров компрессия может оказаться более продолжительным процессом чем передача несжатого контента. Есть возможность выполнить компрессию статических файлов сразу же при подготовке контента. И хранить файлы на SD карте уже сжатыми, однако HTTP сервер Azure не поддерживает распознавание сжатых файлов и соответствующую модификацию заголовков HTTP.

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

Неточность в исходниках Azure HTTP сервера при базовой авторизации.
Исходные тексты содержат неточность в файле nx_web_http_server.c после строки 3689.
В таком виде базовая авторизация затягивается на 10 сек., поскольку не разрывается соединение после неавторизированного запроса. Туда следует вставить вот такой фрагмент:

if (status == NX_WEB_HTTP_BASIC_AUTHENTICATE)
        {
          _nx_web_http_server_connection_reset(server_ptr,server_ptr -> nx_web_http_server_current_session_ptr , NX_WEB_HTTP_SERVER_TIMEOUT_SEND);
          return;
        }

После такого исправления пользователь сразу, а не через 10 сек. увидит диалог с вводом имени и пароля.
Не забыть также вставить этот фрагмент для методов POST и DELETE.

Выбор WEB фреймворка

Выбор фреймворка очень важен. Фреймворки – это такое сочетание JavaScript библиотек и файлов стилей CSS. Бывает просто одна библиотека, как например jQuery, бывает библиотека со стилями, как например jQuery UI. Фреймворк значительно облегчает создание адаптируемых, интуитивных и привлекательных WEB страниц, в противовес использованию голого HTML и JavaScript с архаичными стилями. С помощью фреймворков даже самый примитивный WEB сервер выполненный на Arduino, может предоставить удивительно стильный WEB интерфейс. Фреймворки автоматически адаптируют страницы так чтобы они оставались в приемлемом качестве на экранах любых размеров и в любых браузерах. 

Но фреймворков очень много. В нашем случае прежде всего интересует сколько файлов и какого размера требуется для фреймворка, насколько удобен фреймворк в использовании неспециалисту, насколько богат и гибок набор стилей и виджетов. Надо помнить что файлы фреймворка скачиваются браузером вместе со страницами WEB интерфейса из устройства. Слишком большой объем фреймворка замедлит время открытия страниц.

Охватить анализом все фреймворки и библиотеки практически невозможною Они возникают как грибы после дождя. Поэтому тут я привёл таблицу как попытку оценить несколько фреймворков которые есть на слуху:

Большинство фреймворков вводят новые элементы синтаксиса разметки в страницы HTML, добиваясь  таким образом новых способов выражения дизайна. Это влечёт за собой необходимость дополнительно изучать кроме HTML, JavaScript, CSS и DOM модели ещё и уникальный язык и архитектурный подход фреймворка. Это ещё более усложняет задачу выбора. Поэтому об оптимальном выборе речь не идёт.

Как локальный оптимум выбираем jQuery mobile. Он использует одну из старейших и проверенных библиотек – jQuery и интегрирован в WYSIWYG среду разработки Adobe Dreamweaver, что позволяет более удобно конструировать интерфейс по сравнению со способами без WYSIWYG. jQuery mobile как следует из названия предназначен в первую очередь для мобильных устройств, и это как раз то что нужно, поскольку большинство пользователей скорее всего захотят работать с WEB интерфейсом через мобильные устройства

Защита WEB сервера, генерация и инсталляция сертификатов

Защищённый WEB сервер работает по протоколу HTTPS на порту 433. В этом случае используется протокол шифрования и аутентификации Transport Layer Security (TLS). В Azure RTOS реализован протокол TLS версии 1.3 и также более ранние его версии.

По умолчанию сервер просит от клиента вот такой набор алгоритмов:
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)

Для того чтобы WEB c TLS работал устройство должно ещё иметь в своих недрах сертификат сервера и секретный ключ сервера. Здесь предлагается их хранить во Flash памяти в виде массивов. Сертификат сервера должен содержать подпись доверенного центра (это стоит денег и времени), поэтому проще всего сгенерировать самоподписанный сертификат. 

И ключ и сертификат должны быть представлены в двоичном формате DER.

Для примера: ключ занимает 1192 байта, один самоподписанный сертификат занимает 879 байт.

Чтобы  сгенерировать самоподписанный сертификат и секретный ключ надо скачать файлы openssl.exe, libeay32.dll, ssleay32.dll и выполнить в их директории несколько команд:

1.    Сгенерировать корневой секретный ключ ca.key -
openssl genrsa -out ca.key 2048  

2.    Сгенерировать корневой сертификат CA.crt -
openssl req -config CA.conf -new -x509 -sha256 -key ca.key -days 3650 -out CA.crt

3.    Сгенерировать секретный ключ сервера srv.key
openssl genrsa -out srv.key 2048

4.    Сгенерировать запрос сертификата сервера с использованием корневого сертификата srv.csr -
openssl req -new -config Server.conf -out srv.csr -key srv.key

5.    Верифицировать и сгенерировать сертификат сервера srv.crt -
openssl x509 -req -in srv.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out srv.crt -days 3650

6.    Конвертировать сертификаты и ключ в формат DER -
openssl x509 -in CA.crt -out CA.der -outform DER
openssl x509 -in srv.crt -out srv.der -outform DER
openssl rsa -inform pem -in srv.key -outform der -out srv_key.der

На HTTP сервер устанавливаем файлы srv.der и srv_key.der предварительно сконвертировав их в массивы. Причём у нас остаётся корневой сертификат, благодаря чему даже если злоумышленники взломают устройство и похитят сертификат сервера они не смогут сгенерировать новые сертификаты после того как похищенный сертификат буде аннулирован.

Сколько ОЗУ потребует защищённый WEB сервер

Задача http сервера в Azure RTOS называется TCPSERVER Thread.

Про этому названию её легко найти в списке запущенных задач в отладчике IDE IAR Embedded Workbench for Arm.

По наблюдениям на реальном проекте размер занятого этой задачей стека не превышал 2500 байта.

Движок TLS требует нескольких объёмных структур данных:

  • Структура NX_SECURE_X509_CERT имеет размер 304 байта

  • Массив crypto_metadata должен иметь размер не менее 17596 байта на каждую сессию. У нас выбрано до 2 сессий одновременно. Следовательно надо 35192 байта

  • Массив tls_packet_buffer требует не менее 2000 байт и не более 64 Кбайт. Задаём ему 4000 байта. Его размер определяется размером сертификата сервера. Сертификат может сопровождаться цепочкой сертификатов поэтому надо определять этот размер каждый раз по обстоятельствам. 

Итого защищённый сервер потребует не менее 42 Кбайт. Это сравнительно немного. В целом на весь TCP стек с сервером потребуется около 100 Кбайт ОЗУ. Это с учётом буфера пакетов TCP/IP пакетов, JSON парсера для минимальных структур и прочих расходов на протоколы. Если иметь в виду ещё файловую систему, то размер возрастёт на 30-70 Кбайт в зависимости от того какое быстродействие хотим получить.

Пример разработанной страницы WEB интерфейса

Используя фреймворк jQuery mobile сконструирована вот такая страница:

Если бы фреймворка не было, то эта же страница выглядела бы так:

Содержимое HTML страницы
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>IoT Logger</title>
    <link href="jquery-mobile/jquery.mobile.theme-1.3.0.min.css" rel="stylesheet" type="text/css">
    <link href="jquery-mobile/jquery.mobile.structure-1.3.0.min.css" rel="stylesheet" type="text/css">
    <script src="jquery-mobile/jquery-1.11.1.min.js">
    </script>
    <script src="jquery-mobile/jquery.mobile-1.3.0.min.js">
    </script>
    <style>
        @media only screen and (device-width: 300px), only screen and (max-width:300px) {
            .css-element {
                yourcsscode:;
            }
        }

        input.cb_larger {
            width: 25px;
            height: 25px;
            margin: -12px 0 0 -10px;
        }

        .tbl_hdr {
            font-size: 14px;
            font-style: oblique;
            font-weight: normal;
            text-align: left;
            background-color: #DEDEDE
        }
    </style>

  </head>

  <body>
    <div align="center" data-role="page" id="page">
      <div data-role="header" data-position="fixed">
        <p id="page_header1" style="padding: 0px 0px 0px 0px; margin: 5px 0px 0px 0px ">IoT Logger</p>
        <p style="font-size: 10px; padding: 0px 0px 0px 0px; margin: 0px 0px 5px 5px; text-align: left ">
        <span>ID:</span>
        <em id="page_header2" style="color:deepskyblue">?</em>
        <span id="page_header3" style="margin-left: 10px">?</span>
        </p>
        <div data-role="navbar" style="width: 400px">
          <ul>
            <li></li>
            <li></li>
            <li><li><button type="submit" id="reset_log" onclick="location.assign('device_log.html');" data-theme="b">Show log</button></li></li>
          </ul>
        </div>
      </div>

      <div data-role="content">
        <div style="overflow:auto;">
        <table id="ap_list" style="width:800px">
          <caption style="font-size: 16px; font-weight: bold; text-align: left">
          Available Access Points list for Station mode
          </caption>
          <tr>
            <th class="tbl_hdr"></th><th class="tbl_hdr">Enable</th>
            <th class="tbl_hdr">Access Point SSID</th>
            <th class="tbl_hdr">Password</th>
            <th class="tbl_hdr">Use DHCP</th>
            <th class="tbl_hdr">IP address</th>
            <th class="tbl_hdr">IP mask</th>
            <th class="tbl_hdr">IP gateway</th>
          </tr>
          <tr>
            <td>1</td>
            <td><input type="checkbox" class="cb_larger" id="ssiden1" name="SSIDEN1"></td>
            <td><input type="text" id="ssid1" name="SSID1" value=""></td>
            <td><input type="password" id="pass1" name="PASS1" value=""></td>
            <td><input type="checkbox" class="cb_larger"  id="dhcp1" name="DHCP1"></td>
            <td><input type="text"    id="ipaddr1"    name="IPADDR1" value=""></td>
            <td><input type="text"    id="ipmask1"    name="IPMASK1" value=""></td>
            <td><input type="text" id="ipgateway1" name="IPGATEWAY1" value=""></td>
          </tr>
          <tr>
            <td>2</td>
            <td><input type="checkbox" class="cb_larger" id="ssiden2" name="SSIDEN2"></td>
            <td><input type="text" id="ssid2" name="SSID2" value=""></td>
            <td><input type="password" id="pass2" name="PASS2" value=""></td>
            <td><input type="checkbox" class="cb_larger"  id="dhcp2" name="DHCP2"></td>
            <td><input type="text"    id="ipaddr2"    name="IPADDR2" value=""></td>
            <td><input type="text"    id="ipmask2"    name="IPMASK2" value=""></td>
            <td><input type="text" id="ipgateway2" name="IPGATEWAY2" value=""></td>
          </tr>
          <tr>
            <td>3</td>
            <td><input type="checkbox" class="cb_larger" id="ssiden3" name="SSIDEN3"></td><td>
            <input type="text" id="ssid3" name="SSID3" value=""></td>
            <td><input type="password" id="pass3" name="PASS3" value=""></td>
            <td><input type="checkbox" class="cb_larger"  id="dhcp3" name="DHCP3"></td>
            <td><input type="text"    id="ipaddr3"    name="IPADDR3" value=""></td>
            <td><input type="text"    id="ipmask3"    name="IPMASK3" value=""></td>
            <td><input type="text" id="ipgateway3" name="IPGATEWAY3" value=""></td>
          </tr>
          <tr>
            <td>4</td>
            <td><input type="checkbox" class="cb_larger" id="ssiden4" name="SSIDEN4"></td>
            <td><input type="text" id="ssid4" name="SSID4" value=""></td>
            <td><input type="password" id="pass4" name="PASS4" value=""></td>
            <td><input type="checkbox" class="cb_larger"  id="dhcp4" name="DHCP4"></td>
            <td><input type="text"    id="ipaddr4"    name="IPADDR4" value=""></td>
            <td><input type="text"    id="ipmask4"    name="IPMASK4" value=""></td>
            <td><input type="text" id="ipgateway4" name="IPGATEWAY4" value=""></td>
          </tr>
          <tr>
            <td>5</td>
            <td><input type="checkbox" class="cb_larger" id="ssiden5" name="SSIDEN5"></td>
            <td><input type="text" id="ssid5" name="SSID5" value=""></td>
            <td><input type="password" id="pass5" name="PASS5" value=""></td>
            <td><input type="checkbox" class="cb_larger"  id="dhcp5" name="DHCP5"></td>
            <td><input type="text"    id="ipaddr5"    name="IPADDR5" value=""></td>
            <td><input type="text"    id="ipmask5"    name="IPMASK5" value=""></td>
            <td><input type="text" id="ipgateway5" name="IPGATEWAY5" value=""></td>
          </tr>
        </table>
        </div>
        <table id="ap_selector">
          <tr>
            <th>
            <div style="text-align:left ; padding-top: 10px">
              Selected operational mode
            </div>
            </th>
          </tr>
          <tr>
            <td>
            <input type="radio" id="md_sta" name="wifi_mode" value="0">
            <label id="md_sta_lb" for="md_sta">
            Station mode <br>
            <span style="font-weight: normal; font-size: 14px">
            (The device will connect to the someone <br>
            of access points listed in the table above)
            </span>
            </label>
            </td>
          </tr>
          <tr>
            <td>
            <input type="radio" id="md_ap" name="wifi_mode" value="1">
            <label id="md_ap_lb" for="md_ap">
            Access Point mode<br>
            <span style="font-weight: normal; font-size: 14px">(The device will wait for someone to join with it)</span>
            </label>
            </td>
          </tr>
        </table>

        <fieldset class="ui-grid-a" style="width: 300px">
          <div class="ui-block-a">
            <button type="submit" id="submit_data" data-theme="a">Submit</button>
          </div>
          <div class="ui-block-b">
            <button type="submit" id="reset_dev" data-theme="e">Reset device</button>
          </div>
        </fieldset>

        <div data-role="footer" data-position="fixed">
          <p id="status_msg" style="font-weight:normal" hidden=true>
          </p>
        </div>

      </div>
    </div>
  </body>
  <script>
    // Глобальные переменные для хранения полученной из дивайса информации
    var params;
    var dev;
    var apl;
    var wrl;

    // Вывод строки статуса выполнения операции по нажатию кнопок
    function Show_cmd_status(data, status)
    {
      $("#status_msg").text("Data sending status: " + status); $("#status_msg").show();
      setTimeout(function() { $("#status_msg").hide(); }, 2000);
    }

    // Функция извлечение интересующих параметров из объекта data и помещение их в поля ввода
    function Data_accept(data, status)
    {
      params = data;
      
      
      var v = params.find(function(item, i) { if (item["Client_AP_list"] != undefined) return true; });

      apl = v["Client_AP_list"];
      if (apl != undefined)
      {
        $("#ssid1").val(apl[0][1]);
        $("#pass1").val(apl[0][2]);
        $("#ipaddr1").val(apl[0][4]);
        $("#ipmask1").val(apl[0][5]);
        $("#ipgateway1").val(apl[0][6]);

        $("#ssid2").val(apl[1][1]);
        $("#pass2").val(apl[1][2]);
        $("#ipaddr2").val(apl[1][4]);
        $("#ipmask2").val(apl[1][5]);
        $("#ipgateway2").val(apl[1][6]);

        $("#ssid3").val(apl[2][1]);
        $("#pass3").val(apl[2][2]);
        $("#ipaddr3").val(apl[2][4]);
        $("#ipmask3").val(apl[2][5]);
        $("#ipgateway3").val(apl[2][6]);

        $("#ssid4").val(apl[3][1]);
        $("#pass4").val(apl[3][2]);
        $("#ipaddr4").val(apl[3][4]);
        $("#ipmask4").val(apl[3][5]);
        $("#ipgateway4").val(apl[3][6]);

        $("#ssid5").val(apl[4][1]);
        $("#pass5").val(apl[4][2]);
        $("#ipaddr5").val(apl[4][4]);
        $("#ipmask5").val(apl[4][5]);
        $("#ipgateway5").val(apl[4][6]);

        if (apl[0][0] == 1) $("#ssiden1").attr("checked", true);
        if (apl[1][0] == 1) $("#ssiden2").attr("checked", true);
        if (apl[2][0] == 1) $("#ssiden3").attr("checked", true);
        if (apl[3][0] == 1) $("#ssiden4").attr("checked", true);
        if (apl[4][0] == 1) $("#ssiden5").attr("checked", true);

        if (apl[0][3] == 1) $("#dhcp1").attr("checked", true);
        if (apl[1][3] == 1) $("#dhcp2").attr("checked", true);
        if (apl[2][3] == 1) $("#dhcp3").attr("checked", true);
        if (apl[3][3] == 1) $("#dhcp4").attr("checked", true);
        if (apl[4][3] == 1) $("#dhcp5").attr("checked", true);

      }

      v = params.find(function(item, i) { if (item["Parameters"] != undefined) return true; });
      if (v != undefined)
      {
        wrl = v["Parameters"].find(function(item, i) { if (item[0] == "wifi_role") return true; });
        if (wrl != undefined)
        {
          if (wrl[1] == 0)
          {
            $("#md_sta_lb").click();
          } else
          {
            $("#md_ap_lb").click();
          }
        }
      }

      v = params.find(function(item, i) { if (item["Device"] != undefined) return true; });
      dev = v["Device"];
      if (dev["HW_Ver"] != undefined)
      {
        $("#page_header1").html(dev["HW_Ver"]);
        $("#page_header2").html(dev["CPU_ID"]);
        $("#page_header3").html(dev["CompDate"] + " " + dev["CompTime"]);
      }
    }

    // Считываем параметры из полей ввода, записываем их в объект JSON
    // Сериализируем объект и отправляем устройству методом POST
    function Data_send()
    {
      
      apl[0][1] = $("#ssid1").val();
      apl[0][2] = $("#pass1").val();
      apl[0][4] = $("#ipaddr1").val();
      apl[0][5] = $("#ipmask1").val();
      apl[0][6] = $("#ipgateway1").val();

      apl[1][1] = $("#ssid2").val();
      apl[1][2] = $("#pass2").val();
      apl[1][4] = $("#ipaddr2").val();
      apl[1][5] = $("#ipmask2").val();
      apl[1][6] = $("#ipgateway2").val();

      apl[2][1] = $("#ssid3").val();
      apl[2][2] = $("#pass3").val();
      apl[2][4] = $("#ipaddr3").val();
      apl[2][5] = $("#ipmask3").val();
      apl[2][6] = $("#ipgateway3").val();

      apl[3][1] = $("#ssid4").val();
      apl[3][2] = $("#pass4").val();
      apl[3][4] = $("#ipaddr4").val();
      apl[3][5] = $("#ipmask4").val();
      apl[3][6] = $("#ipgateway4").val();

      apl[4][1] = $("#ssid5").val();
      apl[4][2] = $("#pass5").val();
      apl[4][4] = $("#ipaddr5").val();
      apl[4][5] = $("#ipmask5").val();
      apl[4][6] = $("#ipgateway5").val();

      if ($("#ssiden1").prop("checked") == true) apl[0][0] = 1;
      else apl[0][0] = 0;
      if ($("#ssiden2").prop("checked") == true) apl[1][0] = 1;
      else apl[1][0] = 0;
      if ($("#ssiden3").prop("checked") == true) apl[2][0] = 1;
      else apl[2][0] = 0;
      if ($("#ssiden4").prop("checked") == true) apl[3][0] = 1;
      else apl[3][0] = 0;
      if ($("#ssiden5").prop("checked") == true) apl[4][0] = 1;
      else apl[4][0] = 0;

      if ($("#dhcp1").prop("checked") == true) apl[0][3] = 1;
      else apl[0][3] = 0;
      if ($("#dhcp2").prop("checked") == true) apl[1][3] = 1;
      else apl[1][3] = 0;
      if ($("#dhcp3").prop("checked") == true) apl[2][3] = 1;
      else apl[2][3] = 0;
      if ($("#dhcp4").prop("checked") == true) apl[3][3] = 1;
      else apl[3][3] = 0;
      if ($("#dhcp5").prop("checked") == true) apl[4][3] = 1;
      else apl[4][3] = 0;


      if ($("#md_sta").prop("checked") == true) wrl[1] = "0";
      else wrl[1] = "1";

      // Преобразуем объект JavaScript в строку JSON
      json_str = JSON.stringify(params);
      // Отправляем устройству методом POST строку JSON с отредактированными параметрами
      $.post("data.json", json_str, Show_cmd_status);
    }

    // Перейти на страницу отображения лога 
    function Show_log()
    {
      location.assign("device_log.html");
    }

    // По клику на кнопке с id = "submit_data" посылаем отредактированные данные обратно устройству  
    $("#submit_data").click(Data_send);
    // По клику на кнопке с id = "reset_dev" посылаем команду сброса устройства
    $("#reset_dev").click(function() {$.post("reset", "", Show_cmd_status)});
    // Здесь сразу запрашиваем у устройства по протоколу AJAX текущие настройки 
    $.get("data.json", Data_accept);

  </script>
</html>

Как видно в заголовке страница вместе с файлом HTML будут скачаны еще 4-е файла принадлежащие фреймворку. На самом деле будет скачано больше, поскольку файлы фреймворка скачивают сами ещё несколько нужных им файлов.

Содержимое JSON файла c данными
[
 {
  "Device": {
   "CPU_ID": "5301646835393735C86643535454227D",
   "SW_Ver": "V0.0.2",
   "HW_Ver": "IoT Logger 1.0.0",
   "CompDate": "Apr 16 2021",
   "CompTime": "13:03:54"
  }
 },
 {
  "Parameters": [
   [
    "leds_mode",
    "1"
   ],
   [
    "wifi_role",
    "1"
   ]
  ]
 },
 {
  "Client_AP_list": [
   [
    1,
    "SSID",
    "PASS",
    0,
    "192.168.1.1",
    "255.255.255.0",
    "192.168.1.254"
   ],
   [
    1,
    "SSID",
    "PASS",
    0,
    "192.168.1.1",
    "255.255.255.0",
    "192.168.1.254"
   ],
   [
    1,
    "SSID",
    "PASS",
    0,
    "192.168.1.1",
    "255.255.255.0",
    "192.168.1.254"
   ],
   [
    1,
    "SSID",
    "PASS",
    0,
    "192.168.1.1",
    "255.255.255.0",
    "192.168.1.254"
   ],
   [
    1,
    "SSID",
    "PASS",
    0,
    "192.168.1.1",
    "255.255.255.0",
    "192.168.1.254"
   ]
  ]
 }
]

И наконец посмотрим отзывчивость WEB интерфейса на примере платформы Sinergy

Диаграмма времени закачки страницы по протоколу HTTP
Диаграмма времени закачки страницы по протоколу HTTP
Диаграмма времени закачки страницы по протоколу HTTPS с аппаратной реализайией алгоритмов шифрования
Диаграмма времени закачки страницы по протоколу HTTPS с аппаратной реализайией алгоритмов шифрования

Здесь надо отметить что для Sinergy у Azure RTOS есть драйвер аппаратного криптографического модуля, поэтому скорость HTTP и HTTPS отличаются всего лишь в два раза.

В случае программной реализации отличие более значительное.

Диаграмма времени закачки страницы по протоколу HTTPS с программной реализацией алгиритмов шифрования
Диаграмма времени закачки страницы по протоколу HTTPS с программной реализацией алгиритмов шифрования

Как видно программная реализация шифрования несмотря на то что достаточно оптимизирована замедляет передачу более чем в 30! раз.

Как обстоят дела с драйверами для криптографической периферии STM32 ещё предстоит выяснить.

Итак, загрузка адаптированной под мобильные гаджеты, зашифрованной с помощью TLS страницы вместе со всеми файлами фреймворка и файлом данных JSON в среднем займет не более 800 миллисекунд, что вполне приемлемо для интерфейса встраиваемого устройства.

Теги:
Хабы:
+4
Комментарии18

Публикации

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн