Итак, с мониторингом мы в прошлый раз разобрались, поэтому сегодня будем решать задачу удалённого управления и конфигурирования.
Как вы помните, для примера мы взяли в качестве контроллера шлюз UART-to-I2C/SPI/1W. Последнюю версию того, что у нас получилось (самодельный web-сервер на C++ Builder, позволяющий удалённо мониторить подключенные к шлюзу далласовские термометры и дискретные I/O), можно скачать вот здесь. Её-то мы и будем дополнять возможностями управления и конфигурирования.
Теперь давайте подумаем, что нам нужно для решения нашей задачи? Ну, очевидно, нужно иметь возможность отправлять с web-страницы команды серверу на переключение I/O или обновление списка подключенных датчиков или… Короче, главное во всём этом — иметь возможность удалённо отправлять команды серверу с web-страницы.
Хм, а ведь мы уже имеем такую возможность. Действительно, когда мы через параметры GET-запроса запрашиваем у сервера состояние I/O или значения измеренных термометрами температур, мы фактически даём серверу команды отправить нам эти значения. То есть сейчас нам нужно просто добавить ещё несколько параметров-команд, прописать реакцию сервера на эти команды и модифицировать web-страницу таким образом, чтобы новые параметры добавлялись в GET-запрос не по времени (каждую секунду, две, три и так далее), а по каким-то другим событиям (по нажатию кнопок, по превышению температурой определённых значений…) Всё остальное мы умеем.
Итак, сначала модифицируем шаблон отдаваемой сервером страницы.
Там, где в шаблоне описываются глобальные переменные, добавим ещё несколько (ну как несколько, ещё 12 штук к тем трём, которые уже есть):
// глобальные переменные var top_http; // здесь будем хранить указатель на объект XMLHttpRequest var status; // эта переменная нужна чтобы опознать, что ответ получен и обработан var elements_col; // количество переменных, которые мы будем запрашивать/получать var swio0input; // команда переключения io0 на вход var swio0output; // команда переключения io0 на выход var swio1input; // команда переключения io1 на вход var swio1output; // команда переключения io1 на выход var swio2input; // команда переключения io2 на вход var swio2output; // команда переключения io2 на выход var setio0low; // команда установки io0 в 0 var setio0hi; // команда установки io0 в 1 var setio1low; // команда установки io1 в 0 var setio1hi; // команда установки io1 в 1 var setio2low; // команда установки io2 в 0 var setio2hi; // команда установки io2 в 1 |
Для того, чтобы страничка выглядела более читабельно, добавим в секцию head глобальный стиль для таблиц, а так же отдельный стиль для ячеек новой таблицы, имитирующих кнопки (делать кнопки через теги input или button уже не модно, ага; стили и javascript позволяют сделать кнопки практически из любого элемента):
<!-- добавим глобальный стиль для таблиц и для кнопок --> <style type="text/css"> td { border: 1px solid blue; padding: 10px; } td.button { background-color: #40C781; box-shadow: 0 -3px #35A76E inset; } td.button:hover { background-color: #35A76E; } td.button:active { background-color: #21935A; box-shadow: 0 3px #21935A inset; } </style> |
Добавим в шаблон саму новую таблицу:
<p>Таблица управления I/O:</p> <table style="border: 1px solid blue"> <tr> <td>Tag</td> <td>Configuration</td> <td>Value</td> </tr> <tr> <td rowspan="2">io0</td> <td class="button" onclick="sw1()">switch to input</td> <td class="button" onclick="set1()">set to 1</td> </tr> <tr> <td class="button" onclick="sw2()">switch to output</td> <td class="button" onclick="set2()">set to 0</td> </tr> <tr> <td rowspan="2">io1</td> <td class="button" onclick="sw3()">switch to input</td> <td class="button" onclick="set3()">set to 1</td> </tr> <tr> <td class="button" onclick="sw4()">switch to output</td> <td class="button" onclick="set4()">set to 0</td> </tr> <tr> <td rowspan="2">io2</td> <td class="button" onclick="sw5()">switch to input</td> <td class="button" onclick="set5()">set to 1</td> </tr> <tr> <td class="button" onclick="sw6()">switch to output</td> <td class="button" onclick="set6()">set to 0</td> </tr> </table> |
В секцию head, туда, где у нас описаны яваскрипты, напишем обработчики событий onclick для кнопок (для ячеек таблицы, имитирующих кнопки). Эти обработчики будут устанавливать в true значения заведённых нами ранее переменных (таким образом мы в дальнейшем сможем определять — было нажатие на кнопку или нет):
function sw1() { swio0input=true; } function sw2() { swio0output=true; } function sw3() { swio1input=true; } function sw4() { swio1output=true; } function sw5() { swio2input=true; } function sw6() { swio2output=true; } function set1() { setio0hi=true; } function set2() { setio0low=true; } function set3() { setio1hi=true; } function set4() { setio1low=true; } function set5() { setio2hi=true; } function set6() { setio2low=true; } |
В шаблоне осталось только переписать функцию формирования запроса. Перепишем её таким образом, чтобы в случае, если было нажатие на кнопку, — в запрос добавлялась соответствующая команда серверу:
function sendTop() // В этой функции мы формируем запрос и отправляем его на сервер { status=0; // сбрасываем статус top_http = getXmlHttpRequestObject(); // вызываем функцию создания объекта XMLHttpRequest top_http.onreadystatechange = handleTop;// цепляем на onreadystatechange обработчик ответа var url='?GetTemp=true&GetIO=true'; // строка запроса if(swio0input==true) url=url+'&io0sw=1'; // если было нажатие - if(swio0output==true) url=url+'&io0sw=0'; // добавляем команду в запрос if(swio1input==true) url=url+'&io1sw=1'; if(swio1output==true) url=url+'&io1sw=0'; if(swio2input==true) url=url+'&io2sw=1'; if(swio2output==true) url=url+'&io2sw=0'; if(setio0low==true) url=url+'&io0set=0'; if(setio0hi==true) url=url+'&io0set=1'; if(setio1low==true) url=url+'&io1set=0'; if(setio1hi==true) url=url+'&io1set=1'; if(setio2low==true) url=url+'&io2set=0'; if(setio2hi==true) url=url+'&io2set=1'; top_http.open('GET', url, true); // настраиваем асинхронный запрос с адресом url top_http.setRequestHeader('If-Modified-Since','0'); // добавляем заголовки top_http.setRequestHeader('Cache-Control','no-cache'); top_http.send(null); // отправляем запрос } |
Мы забыли ещё один важный момент. Переменные swio и setio нужно ещё и как-то сбрасывать, иначе после первого и единственного нажатия на кнопку, соответствующая команда будет добавляться в запрос при всех последующих вызовах функции sendTop.
Можно, например, сбрасывать их после того, как запрос отправлен, — в этом случае не гарантируется, что сервер эту команду выполнит, поскольку возможно, что этот запрос до сервера и не долетит.
Можно сбрасывать эти переменные после того, как получен положительный ответ от сервера (код 200 — Ok!), тогда команда будет посылаться до тех пор, пока сервер её не выполнит (если первый запрос до сервера не долетел — команда будет отправлена в следующем запросе, если и он не долетел, тогда в следующем — и так далее, и отменить эту команду будет нельзя).
Можно, кстати, вообще, при нажатии на кнопку не добавлять команду в периодически формируемые запросы, а отправлять на сервер отдельный запрос, содержащий только эту самую команду.
Можно придумать ещё кучу разных вариантов, но мы будем использовать второй. Для этого перепишем код обработчика функции handleTop следующим образом:
function handleTop() // обработчик ответа сервера { if(top_http.readyState == 4) // если запрос выполнен (4 - состояние complete) { if(top_http.status == 200) // если статус ответа - 200 ( Ok! ) { swio0input=false; // сбрасываем нажатие кнопок swio0output=false; swio1input=false; swio1output=false; swio2input=false; swio2output=false; setio0low=false; setio0hi=false; setio1low=false; setio1hi=false; setio2low=false; setio2hi=false; var value = top_http.responseText; // получаем в переменную текст ответа var ArrVal=value.split(';'); // разделяем ответ на массив параметров var obj_tag; // здесь будет указатель на контейнер var i=0; // это просто счётчик elements_col=ArrVal[0]+6; // количество элементов, которые нужно // обработать: количество датчиков + // 6 - для I/O (3 - конфа, 3 - защёлки) while(i<elements_col) { // проверяем, есть ли на странице контейнер с нужным id? obj_tag=document.getElementById(ArrVal[2*i+1]); if(obj_tag) // если есть { // меняем текст внутри него на принятый от сервера obj_tag.innerHTML = ArrVal[2*i+2]; } i=i+1; // проверяем следующий элемент } status=1; // меняем статус } } } |
Теперь нужно переделать серверную часть, — проверить, есть ли в GET-запросе наши новые команды и написать обработчики этих команд.
Открываем в Builder-е наш проект и модифицируем обработчик события OnCommandGet. В секции, где мы просматриваем по очереди все распарсенные параметры GET-запроса, добавляем следующий код:
// обработчики команд от дистанционных кнопок (из браузера) if(RequestInfo->Params->Names[i]=="io0sw") { if(RequestInfo->Params->Values["io0sw"]==1) IO0SwIn=true; else if(RequestInfo->Params->Values["io0sw"]==0) IO0SwOu=true; else break; } if(RequestInfo->Params->Names[i]=="io1sw") { if(RequestInfo->Params->Values["io1sw"]==1) IO1SwIn=true; else if(RequestInfo->Params->Values["io1sw"]==0) IO1SwOu=true; else break; } if(RequestInfo->Params->Names[i]=="io2sw") { if(RequestInfo->Params->Values["io2sw"]==1) IO2SwIn=true; else if(RequestInfo->Params->Values["io2sw"]==0) IO2SwOu=true; else break; } if(RequestInfo->Params->Names[i]=="io0set") { if(RequestInfo->Params->Values["io0set"]==1) IO0Set1=true; else if(RequestInfo->Params->Values["io0set"]==0) IO0Set0=true; else break; } if(RequestInfo->Params->Names[i]=="io1set") { if(RequestInfo->Params->Values["io1set"]==1) IO1Set1=true; else if(RequestInfo->Params->Values["io1set"]==0) IO1Set0=true; else break; } if(RequestInfo->Params->Names[i]=="io2set") { if(RequestInfo->Params->Values["io2set"]==1) IO2Set1=true; else if(RequestInfo->Params->Values["io2set"]==0) IO2Set0=true; else break; } |
Ну, что? Всё? Тогда компилируем и запускаем (проект со всеми внесёнными изменениями).
Вуаля, — теперь web-страничка, отдаваемая http-сервером, выглядит так, как на картинке ниже, и самое главное, — мы можем переключать I/O прямо из браузера (кнопками в таблице управления).
Аналогично можно прикрутить любое другое управление (передавать на сервер из браузера другие команды, которые будут выполнять другие действия), смысл, я думаю, вы поняли 😉
Итак, теперь у нас есть и отображение и управление, тем не менее выглядит наша страничка довольно убого. Таблицы не позволяют визуально привязать отображаемые данные к объекту управления. Чтобы такую визуальную привязку осуществить, нам необходимо нарисовать на web-страничке объект управления и на этом рисунке показать расположение датчиков и переключателей (так, как это делают в настоящих скадах).
Ok, в следующий раз мы именно этим и займёмся, — придумаем какой-нибудь конкретный объект управления, который будет управляться нашим шлюзом, и сделаем для него продвинутую визуализацию (тем более современные web-технологии позволяют решить подобную задачу в браузере гораздо эффективнее, чем в традиционной скаде).
- Часть 1. Противостояние неизбежно, результат — предсказуем.
- Часть 2. Простой удалённый мониторинг через web-браузер.
- Часть 3. Удалённое управление через web-браузер.
- Часть 4. Продвинутая визуализация в web-браузере. АСУТП аквариума.