Подвернулась интересная задача (на собеседовании):
- Три хоста в одной сети с веб интерфейсами – мастер и два слейва
- Хосты общаются друг с другом на специфичном протоколе поверх 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