Первые шаги к Web SCADA-системе. Оживляем мнемосхему в браузере с помощью 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.

    tmcontrol
    function 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.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      0
      а про серверную часть где почитать?

      откуда
      I_10_111

      значения берутся?

      Для SCADA еще не хватает авторизации с правами, алармов, трендов…
      А подобие джина/динамического_объекта_с_библиотеки предполаагется? (один раз нарисовал насос, а потом его тиражируешь и только PUMP_1 на PUMP_2 меняешь… ну вы поняли...)
        0
        а про серверную часть где почитать?

        В дистрибутиве есть документация
        значения берутся?

        От ОРС-сервера.
        Для SCADA еще не хватает авторизации с правами, алармов, трендов…

        В светлом будущем. Следите за обновлениями.
        Вообще, статья не про SCADA, а как картинку в браузере оживить.

      Only users with full accounts can post comments. Log in, please.