Подвернулась интересная задача (на собеседовании):
- Три хоста в одной сети с веб интерфейсами – мастер и два слейва
- Хосты общаются друг с другом на специфичном протоколе поверх TCP
- Данные в веб-мордах отображают общение, управление
- Памяти и вычислительной мощности очень мало – железки встраиваемые, крайне дешевые, но с Линуксом
- Доступа во внешнюю сеть нет
Базовый проект
Собирается достаточно просто – тут описано все по шагам.
http://stefanfrings.de/qtwebapp/tutorial/index.html
Сокращенно перескажу то, что я использовал
Следуем по шагам ПРОПУСКАЕМ КУКИ И НАСТРОЙКУ СЕССИИ
А теперь добавим кое-что интересное, примеров использования которого я не особо нашел – AJAX в этой связке. То есть логично, что это будет работать (автор либы указывает на это в своем туториале), но вот реализацию было не подсмотреть нигде. Даже в исходниках “большого” проекта разработчика этой библиотеки – машины для варки пива(!).
Что есть AJAX двумя словами? Динамическое обновление. Или – это динамическое обновление содержания страницы данными с сервера без необходимости перезагружать страницу.
Раз в две секунды у нас отправляется GET-запрос на сервер по ноде “/parse_inc_data”. Ответ от сервера загружается в блок <textarea> с id=”demo”. Название test_msg_data здесь важно, т.к. именно это название будет фигурировать далее в коде бэкграунда при сборе JSON-строки ответа.
<!DOCTYPE html> <html> <body> <div> <textarea readonly style="height:150px; width:70%; resize: none;" id="demo"></textarea></p> </div> <script> function handle_data(obj){ if (obj.test_msg_data != undefined && obj.test_msg_data.length > 0) document.getElementById("demo").value = obj.test_msg_data; } } setInterval(loadDoc, 1000); function loadDoc() { const xhttp = new XMLHttpRequest(); xhttp.onload = function() { var obj = JSON.parse(this.responseText); console.log(obj); handle_data(obj); } xhttp.open("GET", "/parse_inc_data"); xhttp.send(); } </script> </body> </html>
Коротко, со стороны Qt, в main.c пишем следующее
#include <httpserver/httplistener.h> #include "requesthandler.h" #include <QCoreApplication> #include <QDir> #include <QFile> int main(int argc, char *argv[]) { //базовые настройки QSettings* settings=new QSettings(&app); settings->setValue("port","8080"); settings->setValue("minThreads","4"); settings->setValue("maxThreads","100"); settings->setValue("cleanupInterval","60000"); settings->setValue("readTimeout","60000"); settings->setValue("maxRequestSize","16000"); settings->setValue("maxMultiPartSize","10000000"); //регистрируем слушателя наших запросов new HttpListener(settings,new RequestHandler(&app),&app); app.exec(); }
Создаем класс RequestMapper по образцу из библиотеки, но делаем свой ответ на запрос. Здесь мы используем счетчик, который отправляем в JSON-объект, сериализуем его в QByteArray и отправляем ответ.
... RequestMapper::RequestMapper(QObject* parent) : HttpRequestHandler(parent) { // empty } void RequestMapper::service(HttpRequest& request, HttpResponse& response) { QByteArray path=request.getPath(); static int counter; //AJAX request if (path=="/parse_inc_data") { if (request.getMethod() == "GET"){ //JSON data prepare QJsonObject object; //Show current status of icons object.insert("test_msg_data", counter++); //Serialize JSON to QByteArray and send it as answer QJsonDocument doc(object); QByteArray bytes = doc.toJson(); response.write(bytes, true); } } else { response.setStatus(404,"Not found"); response.write("The URL is wrong, no such document.",true); } } ...
Собранное демо-приложение выглядит как-то так.
Как можем видеть – раз в секунду у нас обновляется счетчик. Таймаут обновления задается в setInterval(loadDoc, 1000) в .html-файле
Суть тестового задания и как оно было реализовано
Мастер-устройство (для каждого слейва):
- Цветовой индикатор, управляемый слейвами
- Поле ввода
- Поле вывода
- Чекбокс, меняющий цвет вывода на слейве
- Кнопка, показывающая цветовой индикатор на слейве
Слейв-устройтсво:
- Цветовой индикатор, управляемый мастером
- Поле ввода
- Поле вывода
- Чекбокс, показывающий цветовой индикатор на мастере
Я решил делать все в одном проекте. Библиотека по условиям задания – FreeMODBUS.
С чекбоксами и кнопками достаточно просто – Modbus к ним приспособлен. Просто читаем или пишем в регистры-койлы. С сообщениями было интереснее – к ним протокол не особо приспособлен. Я решил сделать так:
- Выделить определенный диапазон адресов под символы одного сообщения. У слйва их (диапазонов) 2 – от мастера и к мастеру. У мастера их 4. За очистку поля сообщения ответственна принимающая сторона.
- Выделить к каждому диапазону адресов по одному статусному регистру. Флаг выставляется записывающей стороной и снимается принимающей. Флажок, по которому записывающая сторона может понять – были ли прочитаны данные, а принимающая – есть ли новые. После очистки данных с сообщением, приемник должен убрать флаг.
Были небольшие проблемы с библиотеками FreeMODBUS. Для реализации части Master были проблемы и инклудами, а для Slave – в открытом доступе не было. Нужно было делать запрос на “бесплатную покупку”, чтобы тебе выслали пример, но заработал он практически сразу.
Весь код приводить и расписывать не имеет смысла. В итоге, простенькое приложение будет выглядеть так:
Для того, чтобы собрать Slave или Master нужно определить одну из директив в файле web_server.pro
#can be defined as MODBUS_MASTER or MODBUS_SLAVE DEFINES += MODBUS_MASTER
В директории web_server находятся исходные коды и проект для QT
В данном репозитории содержаться собранные проекты для windows:
- release_final_master – TCP мастер
- release_final_slave_1 – TCP подчиненный 1
- release_final_slave_2 – TCP подчиненный 2
Файл etc/webapp1.ini в проектах позволяет провести конфигурацию запускаемого приложения, настроить порты, ip у подчиненных устройств и прочее
Примечание: Из мастера не сделать подчиненное устройство через конфигурационный файл(webapp1.ini) (верно и обратное)
Весь код находится по ссылке:
Comment