https://habrahabr.ru/post/326330/- Промышленное программирование
- Визуализация данных
- SCADA
- JavaScript
- AngularJS
Сегодня мы поговорим об отображении мнемосхем технологических объектов в браузере посредством таких технологий как SVG, JavaScript и т.д.
Опишем, что мы хотим получить:
- Мнемосхема открывается в браузере. Графика – SVG.
- Вверху мнемосхемы кнопки переходов на другие мнемосхемы.
- Данные обновляются раз в секунду.
- По клику на изображение выключателя появляется окно, из которого его можно включить или отключить.
SCADA-система (точнее её серверная часть) позволяет добавлять пользовательские html-страницы. Я не буду вручную набирать код страницы, а нарисую простую схему в
редакторе, потом покажу, что получилось и как это работает.
Вот мнемосхема:
Мощность и ток будут привязаны к переменным
P_10_111
и
I_10_111
соответственно. Включенное состояние выключателя привязано к красному прямоугольнику (он спрятан под зеленый прямоугольник), отключенное к зеленому.
Сохраним схему, перезапустим сервис, откроем в браузере
127.0.0.1, получим следующее:
Нажмем Сtrl+u в браузере, увидим исходный код страницы (непривязанные линии опущены):
Исходный код страницы<!DOCTYPE html>
<html ng-app="countryApp">
<head>
<meta name="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>SoloSCADA Пример мнемосхемы</title>
<link rel="stylesheet" href="./css/jquery-ui.css">
<script src="./js/jquery.js"></script>
<script src="./js/jquery-ui.js"></script>
<script src="/js/angular.min.js"></script>
<script src="/js/scada.js"></script>
<script>
var countryApp = angular.module('countryApp', []);
countryApp.controller('CountryCtrl', function ($scope, $http, $interval){
$http.get('all.json').success(function(data) {
$scope.val = data.val;
});
$interval(function(){
$http.get('all.json').success(function(data) {
$scope.val = data.val;
$( "#label_err" ).text( "" );
}).error(function(data, status) {
console.error('Error occurred:', data, status);
$( "#label_err" ).text( 'Нет связи с сервером!' );
});
},1000);
});
countryApp.filter('format_off', function(){
return function(text){
if(text.indexOf("0")===0){
return "1";
}
else{
return "0";
}
}
});
countryApp.filter('format_on', function(){
return function(text){
if(text.indexOf("1")===0){
return "1";
}
else{
return "0";
}
}
});
</script>
</head>
<body ng-controller="CountryCtrl">
<div id="dialog-confirm" title="Выберите действие">
<label id="label2">444VG</label>
</div>
<p>
<button type="button" class="ui-button ui-widget ui-corner-all" onClick="javascript:window.location='index.html'">Главная</button>
<button type="button" class="ui-button ui-widget ui-corner-all" onClick="javascript:window.location='ps10.html'">10 кВ</button>
<button type="button" class="ui-button ui-widget ui-corner-all" onClick="javascript:window.location='name_3.html'">Пример мнемосхемы</button>
<label id="label_err"></label>
</p>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 930 504" xml:space="preserve">
<desc>Created with Fabric.js 1.7.3</desc>
<defs>
</defs>
<g transform="translate(260.61 175.8) matrix(1 0 0 1 0 0) ">
<text font-family="helvetica" font-size="20" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(1,1,1); fill-rule: nonzero; opacity: 1;" >
<tspan x="-50.88" y="6.3" fill="rgb(1,1,1)">Мощность:</tspan>
</text>
</g>
<g transform="translate(293.54 208.8) matrix(1 0 0 1 0 0) ">
<text font-family="helvetica" font-size="20" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(1,1,1); fill-rule: nonzero; opacity: 1;" >
<tspan x="-18.04" y="6.3" fill="rgb(1,1,1)">Ток:</tspan>
</text>
</g>
<rect id="V_10_111_on" x="-25" y="-25" rx="0" ry="0" width="50" height="50" style=" cursor:pointer; stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,0,0); fill-rule: nonzero; opacity:{{val.V_10_111 | format_on}};" transform="translate(94.34 185.86) scale(0.94 0.94) matrix(1 0 0 1 0 0) " onclick="tmcontrol('V_10_111','В-10-111')" title="Управление В-10-111"/>
<g id="P_10_111" transform="translate(397.54 177.8) matrix(1 0 0 1 0 0) ">
<text font-family="helvetica" font-size="20" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(1,1,1); fill-rule: nonzero; opacity: 1;" >
<tspan x="-73.04" y="6.3" fill="rgb(1,1,1)">{{val.P_10_111}}</tspan>
</text>
</g>
<g id="I_10_111" transform="translate(393.65 208.8) matrix(1 0 0 1 0 0) ">
<text font-family="helvetica" font-size="20" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(1,1,1); fill-rule: nonzero; opacity: 1;" >
<tspan x="-69.15" y="6.3" fill="rgb(1,1,1)">{{val.I_10_111}}</tspan>
</text>
</g>
<rect id="V_10_111_off" x="-25" y="-25" rx="0" ry="0" width="50" height="50" style=" cursor:pointer; stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,191,95); fill-rule: nonzero; opacity:{{val.V_10_111 | format_off}};" transform="translate(94.28 185.78) scale(0.94 0.94) matrix(1 0 0 1 0 0) " onclick="tmcontrol('V_10_111','В-10-111')" title="Управление В-10-111"/>
</svg>
</body>
</html>
Разберем по порядку:
<script src="./js/jquery.js"></script>
подключаем jquery:
<script src="./js/jquery-ui.js"></script>
подключаем jquery-ui для диалога включить/отключить:
<script src="/js/angular.min.js"></script>
подключаем angularjs:
<script src="/js/scada.js"></script>
подключаем вспомогательный скрипт.
Далее создаём контроллер. Значения переменных берем из файла all.json, его мы запрашиваем с сервера при загрузке страницы и с периодичностью раз в секунду с помощью
$interval(function(){ },1000);
Если файл all.json запрошен успешно, выполняется:
Код$http.get('all.json').success(function(data) {
$scope.val = data.val;
$( "#label_err" ).text( "" );
}).
Обновляем данные. Если не успешно, вверху выводим сообщение
Нет связи с сервером! на
label_err
.
этим скриптом.error(function(data, status) {
console.error('Error occurred:', data, status);
$( "#label_err" ).text( 'Нет связи с сервером!' );
});
Json-файл должен быть такого формата:
{
"val":{
"V_10_111": "0",
"I_10_111": "55.88",
"P_10_111": "9.11"
}
}
SVG-файл встраиваем непосредственно в тело страницы:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 930 504" xml:space="preserve">
</svg>
В текстовое поле выводим значение переменной I_10_111 так:
спойлер <g id="I_10_111" transform="translate(393.65 208.8) matrix(1 0 0 1 0 0) ">
<text font-family="helvetica" font-size="20" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(1,1,1); fill-rule: nonzero; opacity: 1;" >
<tspan x="-69.15" y="6.3" fill="rgb(1,1,1)">{{val.I_10_111}}</tspan>
</text>
</g>
Главное здесь это запись
{{val.I_10_111}}.
Angularjs сам ищет такие записи и заменяет их на значение I_10_111.
Включенное состояние отображаем красным прямоугольником:
Включенное состояние<rect id="V_10_111_on" x="-25" y="-25" rx="0" ry="0" width="50" height="50" style=" cursor:pointer; stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(255,0,0); fill-rule: nonzero; opacity:{{val.V_10_111 | format_on}};" transform="translate(94.34 185.86) scale(0.94 0.94) matrix(1 0 0 1 0 0) " onclick="tmcontrol('V_10_111','В-10-111')" title="Управление В-10-111"/>
Свойство opacity зависит от переменной V_10_111 с примененным фильтром format_on. Когда значение равно 1, прямоугольник виден (opacity: 1). Когда значение равно 0, прямоугольник невиден (opacity: 0). Когда состояние неопределённо (это передаётся знаками вопросов в Json-файле «V_10_111»: "??") применение фильтра format_on даёт 0, т.е. прямоугольник невиден.
Отключенное состояние отображаем зеленым прямоугольником:
Отключенное состояние<rect id="V_10_111_off" x="-25" y="-25" rx="0" ry="0" width="50" height="50" style=" cursor:pointer; stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,191,95); fill-rule: nonzero; opacity:{{val.V_10_111 | format_off}};" transform="translate(94.28 185.78) scale(0.94 0.94) matrix(1 0 0 1 0 0) " onclick="tmcontrol('V_10_111','В-10-111')" title="Управление В-10-111"/>
Свойство opacity зависит от переменной
V_10_111
с примененным фильтром
format_off
. Когда значение переменной равно 0, прямоугольник виден (
opacity: 1
). Когда значение равно 1, прямоугольник невиден (
opacity: 0
).
При клике мышкой на прямоугольнике запускается скрипт
tmcontrol('V_10_111','В-10-111')
. Он описан в файле scada.js.
tmcontrolfunction tmcontrol(perem,rusname) {
elem = perem;
$( "#label2" ).text( rusname );
dialog.dialog( "open" );
};
$( "#label2" ).text( rusname );
— текстовая метка на диалоге отображает название элемента, над которым выполняется команда.
dialog.dialog( "open" );
— показываем диалоговое окно.
Функция диалогового окна:$( function() {
dialog = $( "#dialog-confirm" ).dialog({
autoOpen: false,
resizable: false,
height: "auto",
width: 400,
modal: true,
buttons: {
"Включить": function() {
$.post("control.php", elem+'&1');
$( this ).dialog( "close" );
},
"Отключить": function() {
$.post("control.php", elem+'&0');
$( this ).dialog( "close" );
},
"Отмена": function() {
$( this ).dialog( "close" );
}
}
});
$( "#create-user" ).button().on( "click", function() {
dialog.dialog( "open" );
});
} );
Для окна используется библиотека jquery-ui. Команду включить передаём на сервер методом post:
$.post("control.php", elem+'&1');
Команда отключить:
$.post("control.php", elem+'&0');
Диалоговое окно подключаем в html-файле:
<div id="dialog-confirm" title="Выберите действие">
<label id="label2"> </label>
</div>
Пробуем в разных браузерах!
Firefox:
И так:
SVG автоматически масштабируется под разные разрешения экранов и разные размеры экранов браузеров.
Opera:
Internet explorer как обычно преподнёс сюрприз.
Почему так пока не разобрался. Чтобы самостоятельно пощупать пример нужно скачать
SCADA-систему, установить её, затем подключить html-файл в конфигураторе. Сам файл положить в папку web_main.