Wizualizacja Scada: Napięcie, Moc, Wejścia, Wyjścia oraz Temperatura

W dzisiejszym wpisie przedstawię jak graficznie zilustrować informacje odbierane z poszczególnych urządzeń przy pomocy uScady.

Plik użytego JS wraz z zapisem licencji znajduję się w pliku gauge.min.css

Soft >= v1.00

Urządzenia wykorzystane do wizualizacji:

– LANtick: wejścia, wyjścia

– Nano Temp: temperatura

– Lumel N30P: napięcie, moc

Wykorzystane elementy

1. uScada

2. Nano Temp

3. Lantick 4-4

4. Miernik Lumel N30P

5. Zasilacze PoE

6. Switch i patchcord’y (kable LAN)

Schemat podłączenia

Zasada działania wraz z konfiguracją

W omawianym przykładzie moduł μScada (Master) odpytuje po Modbus’ie TCP wartości z adresów rejestrów odebranych od modułu Nano Temp (Slave), który przez przyłączony czujnik wysyła aktualną wartość temperatury powietrza, z modułu LANtick 4-4 (Slave) zostają odbierane stany wejść oraz wyjść, wraz z możliwością zmiany stanu wyjść. Został również dołączony miernik Lumel N30P (Slave) odpowiadający za pomiar napięcia i mocy pobieranej przez urządzenie. Miernik posiada zaimplementowany protokół komunikacyjny Modbus RTU. Do pamięci μScady zostały zapisane odpowiednie pliki i napisane odpowiednie skrypty.

Konfiguracja

Na początku należy załączyć MODBUS TCP w modułach: Nano Temp oraz LANtick.

Fabryczne dane logowania do Nano Temp i LANtick:

user: admin

hasło: admin00

W μScada zaczynamy od konfiguracji pliku POINTS.xml (w instrukcji zostało zawarte jak go znaleźć i co oznaczają poszczególne tagi).

Zaczynamy od zdefiniowania ilości grup punktów. W naszym przypadku jest to 5 grup. Odpowiednio:

1. Grupa punktów dla odczytu napięcia.

Definiujemy nazwy widoczne w pliku comm.xml:

– nazwę grupy punktów <id> np. Miernik_n

– numer od którego rozpoczynamy numerowanie <id_start> np. 1

– ilość punktów w grupie <len> np. 2

Z uwagi na to, że w dokumentacji miernika widnieje zapis, że:

Wartość jest umieszczona w dwóch kolejnych rejestrach 16 bitowych.
Definiujemy 2 punkty w grupie.

– wybieramy protokół <protocol> dla modułu N30P- RTU

– adres fizyczny Modbus <dev_addr> np. 1

– ustawienie komendy <cmd> MB_HOLD

– ustawienie adresu rejestrów <address> – należy sprawdzić w dokumentacji, jakie adresy odpowiadają za napięcie- 7019

Należy pamiętać, że niekiedy adresacja w Modbus jest przesunięta o 1. Tak jest w mierniku Lumel N30P.

Dzieje się tak, ponieważ adresy rejestru modbus zaczynają się od 1, a adresy w ramce danych od 0. Jest to częsta przyczyna powstawania błędów w czasie programowania.

– typ dostępu <access> np. r – tylko odczyt

2. Grupa punktów dla odczytu mocy.

– nazwę grupy punktów <id> np. Miernik_p

– numer od którego rozpoczynamy numerowanie <id_start> np. 1

– ilość punktów w grupie <len> np. 2

– wybieramy protokół <protocol> dla modułu N30P- RTU

– adres fizyczny Modbus <dev_addr> np. 1

– ustawienie komendy Modbus <cmd> MB_HOLD

– ustawienie adresu rejestrów <address> – należy sprawdzić w dokumentacji, jakie adresy odpowiadają za moc- 7023

– typ dostępu <access> np. r – tylko odczyt

3. Grupa punktów dla odczytu stanu wejść.

– nazwę grupy punktów <id> np. Miernik_p

– numer od którego rozpoczynamy numerowanie <id_start> np. 1

– ilość punktów w grupie <len> np. 4 (z uwagi na 4 wejścia)

– wybieramy protokół <protocol> dla modułu LANtick- TCP

– adres fizyczny Modbus <dev_addr> np. 1

– ustawienie komendy Modbus <cmd> MB_COIL

– ustawienie adresu rejestrów <address> – należy sprawdzić w dokumentacji, jakie adresy odpowiadają za odczyt wejść- 1004

W przedstawionych modułach Inveo adresy Modbus zostały ujednolicone i nie są przesunięte.

W LANtick 4-4 wyjścia są numerowane jako 1, 2, 3, 4, a wejścia 5, 6, 7, 8.

– typ dostępu <access> np. r – tylko odczyt

– adres IP urządzenia, które odpytujemy <ip_addr> (można go odpowiednio ustawić)

– port ip- 502

4. Grupa punktów dla odczytu i ustawienia stanu wyjść.

– nazwę grupy punktów <id> np. LanTick_Wyj_nr_

– numer od którego rozpoczynamy numerowanie <id_start> np. 1

– ilość punktów w grupie <len> np. 4 (z uwagi na 4 wyjścia)

– wybieramy protokół <protocol> dla modułu LANtick- TCP

– adres fizyczny Modbus <dev_addr> np. 1

– ustawienie komendy Modbus <cmd> MB_COIL

– ustawienie adresu rejestrów <address> – należy sprawdzić w dokumentacji, jakie adresy odpowiadają za odczyt wyjść- 1000

– typ dostępu <access> np. rw – odczyt i zapis

– adres IP urządzenia, które odpytujemy <ip_addr> 192.168.111.60

– port ip- 502

5. Grupa punktów dla odczytu temperatury.

– nazwę grupy punktów <id> np. Temperatura_

– numer od którego rozpoczynamy numerowanie <id_start> np. 1

– ilość punktów w grupie <len> 1

– wybieramy protokół <protocol> dla modułu Nano Temp- TCP

– adres fizyczny Modbus <dev_addr> np. 1

– ustawienie komendy Modbus <cmd> MB_MULTIHOLD

– ustawienie adresu rejestrów <address> – należy sprawdzić w dokumentacji- adres rejestru 4004 odpowiada za temperaturę x10

– typ dostępu <access> np. rw – odczyt i zapis

– adres IP urządzenia, które odpytujemy <ip_addr> 192.168.111.15

– port ip- 502

Gdy wszystkie grupy zostały zdefiniowane należy uruchomić ponownie moduł, aby zmiany zostały zaktualizowane.

W celu sprawdzenia, czy wszystko przebiegło pomyślnie można sprawdzić plik comm.xml. W przeglądarkę wpisujemy adresIPurządzenia/comm.xml.

Zaznaczone linie dają nam informacje ile sekund wcześniej nastąpiło poprawne odpytanie z Modbusa. Jeżeli czas od ostatniej odpowiedzi cały czas rośnie (40-50 sekund) trzeba poprawić plik POINTS.xml, bądź sprawdzić dane urządzenie czy jest poprawnie podpięte.

Kolejny krokiem jest wgranie odpowiednich skryptów i plików obrazów do katalogu WWW.

Na koniec zajmujemy się edycją pliku index.htm.

W omawianym przykładzie zdefiniowaliśmy odnośniki do poszczególnych skryptów i zmienne w sekcji nagłówkowej.

Następnie zdefiniowaliśmy zawartość strony.

Na samym początku są zapisane informacje odnośnie stylu nagłówka i samego tekstu.

Elementem <canvas> dostosowujemy wygląd miernika, podpis, wartości max i min, podziałki, opóźnienia, kolory itp.

id=gauge3 odpowiada za wskaźnik dotyczący pomiaru mocy

id=gauge2 odpowiada za wskaźnik dotyczący pomiaru napięcia

id=gauge1 odpowiada za wskaźnik dotyczący pomiaru temperatury

Poniżej zdefiniowanego odczytu temperatury rozpoczyna się definicja wyglądu przycisków informujących o stanie wyjść oraz zdarzeniach występujących po ich naciśnięciu. W tym przypadku po naciśnięciu przycisku zostaje wywołana funkcja sendData(nr wejścia, stan na jaki ma zostać ustawiony przekaźnik (wł/wył)).

Ostatnim elementem wizualnym są ikony informujące o stanie wejść LANtick.

Teraz zajmiemy się wykorzystanymi funkcjami:

Funkcja updateStatus zajmuje się pobraniem danych z pliku xml’a. W pętli for sprawdzamy tablice lantick1 odpowiadającą za stan wyjść i określającą jaki obrazek ma zostać wyświetlony (0-OFF, 1- ON). Kolejna pętla odpowiada za wejścia.

var t1 = parseInt(getXMLValue(xmlData, 'Temperatura_1'));   

temperature = t1/10;

Powyższy kod odpowiada z pobranie wartości temperatury z pliku comm.xml i podzielenie jej przez 10, aby otrzymać interesującą nas wartość.

W tym momencie ominiemy linie kody dotyczące napięcia i mocy, aby wpierw wytłumaczyć występującą w nich funkcję “function ieee32ToFloat(intval)”. Funkcja to odpowiada za konwersję wartości z ieee (32bity) na wartość float- zmiennoprzecinkową. Dzięki temu otrzymujemy znane dla człowieka wartości napięcia i mocy.

Wracając do ominiętych linii kodu:

var v1 = parseInt(getXMLValue(xmlData, 'Miernik_n1'));
var v2 = parseInt(getXMLValue(xmlData, 'Miernik_n2'));
var v = v2 + v1*65536;
var f = ieee32ToFloat(v);
voltDeviation = f.toFixed(2);


v1 = parseInt(getXMLValue(xmlData, 'Miernik_p1'));
v2 = parseInt(getXMLValue(xmlData, 'Miernik_p2'));
v = v2 + v1*65536;
f = ieee32ToFloat(v);
power = f.toFixed(2);

Wpierw definiujemy zmienne v1 oraz v2 i przypisujemy im wartości z odczytanego pliku xml. Zmienna v1 odpowiada za pierwszy punkt w grupie (16bit), v2 za drugi punkt (również 16 bit). Sprowadzamy te wartości do jednej zmiennej o nazwie v. W zmiennej f zostaje zapisana przekonwertowana wartość po użyciu funkcji ieee32ToFloat(). Ostatnim krokiem jest zapisanie przekonwertowanej wartości do zmiennej, która jest wyświetlana jako licznik.

Postępujemy analogicznie w przypadku odczytu mocy.

W ten sposób wizualizacja została ukończona.

Pliki do pobrania

Zasoby plików- wizualizacja

POINTS.XML

<group>
<id>Miernik_n</id>
<id_start>1</id_start>
<len>2</len>
<protocol>RTU</protocol>
<dev_addr>1</dev_addr>
<cmd>MB_HOLD</cmd>
<address>7019</address>
<access>r</access>
<pool>50</pool>
</group>

<group>
<id>Miernik_p</id>
<id_start>1</id_start>
<len>2</len>
<protocol>RTU</protocol>
<dev_addr>1</dev_addr>
<cmd>MB_HOLD</cmd>
<address>7023</address>
<access>r</access>
<pool>50</pool>
</group>

<group>
<id>LanTick_Wej_nr_</id>
<id_start>1</id_start>
<len>4</len>
<protocol>TCP</protocol>
<dev_addr>1</dev_addr>
<cmd>MB_COIL</cmd>
<address>1004</address>
<access>r</access>
<pool>50</pool>
<ip_addr>192.168.111.60</ip_addr>
<ip_port>502</ip_port>
</group>

<group>
<id>LanTick_Wyj_nr_</id>
<id_start>1</id_start>
<len>4</len>
<protocol>TCP</protocol>
<dev_addr>1</dev_addr>
<cmd>MB_COIL</cmd>
<address>1000</address>
<access>rw</access>
<pool>50</pool>
<ip_addr>192.168.111.60</ip_addr>
<ip_port>502</ip_port>
</group>

<group>
<id>Temperatura_</id>
<id_start>1</id_start>
<len>1</len>
<protocol>TCP</protocol>
<dev_addr>1</dev_addr>
<cmd>MB_MULTIHOLD</cmd>
<address>4004</address>
<access>rw</access>
<pool>50</pool>
<ip_addr>192.168.111.15</ip_addr>
<ip_port>502</ip_port>
</group>

index.htm

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-2" >
<title>Inveo</title>
<link href="/style.css" rel="stylesheet" type="text/css" >
<style>
table{
font-family: "Trebuchet MS", sans-serif;
font-size: 16px;
font-weight: bold;
line-height: 1.4em;
font-style: normal;
border-collapse:separate;
width:100%;
}
table thead th{
padding:3px;
color:#fff;
text-shadow:1px 1px 1px #568F23;
border:1px solid #93CE37;
border-bottom:3px solid #9ED929;
background-color:#9DD929;
background:-webkit-gradient(
linear,
left bottom,
left top,
color-stop(0.02, rgb(123,192,67)),
color-stop(0.51, rgb(139,198,66)),
color-stop(0.87, rgb(158,217,41))
);
background: -moz-linear-gradient(
center bottom,
rgb(123,192,67) 2%,
rgb(139,198,66) 51%,
rgb(158,217,41) 87%
);
-webkit-border-top-left-radius:5px;
-webkit-border-top-right-radius:5px;
-moz-border-radius:5px 5px 0px 0px;
border-top-left-radius:5px;
border-top-right-radius:5px;
}
table thead th:empty{
background:transparent;
border:none;
}
table tbody th{
color:#fff;
text-shadow:1px 1px 1px #568F23;
background-color:#9DD929;
border:1px solid #93CE37;
border-right:3px solid #9ED929;
padding:0px 3px;
background:-webkit-gradient(
linear,
left bottom,
right top,
color-stop(0.02, rgb(158,217,41)),
color-stop(0.51, rgb(139,198,66)),
color-stop(0.87, rgb(123,192,67))
);
background: -moz-linear-gradient(
left bottom,
rgb(158,217,41) 2%,
rgb(139,198,66) 51%,
rgb(123,192,67) 87%
);
-moz-border-radius:5px 0px 0px 5px;
-webkit-border-top-left-radius:5px;
-webkit-border-bottom-left-radius:5px;
border-top-left-radius:5px;
border-bottom-left-radius:5px;
}
table tfoot td{
color: #9CD009;
font-size:32px;
text-align:center;
padding:3px 0px;
text-shadow:1px 1px 1px #444;
}
table tfoot th{
color:#666;
}
table tbody td{
padding:3px;
text-align:center;
background-color:#DEF3CA;
border: 2px solid #E7EFE0;
-moz-border-radius:2px;
-webkit-border-radius:2px;
border-radius:2px;
color:#666;
text-shadow:1px 1px 1px #fff;
}


</style>

<script src="/ajax.js" type="text/javascript"></script>
<script src="gauge.min.js"></script>
<script type="text/javascript" src="segment-display.js"></script>
<script type="text/javascript">
var temperature = 22.3;
var voltDeviation=230.0;
var power=0.0;
var lantick1 = [0,0,0,0];
var lantick2_wej = [0,0,0,0];
</script>
</head>

<body>
<div id="shadow-one"><div id="shadow-two"><div id="shadow-three"><div id="shadow-four">
<div id="page">
<div style="padding:0 0 0 5px;"><img src="logo.png" alt="Logo" style="float:left"/><span style="font-size:40px;padding-left:290;font-weight:bolder;">uSCADA</span></div>
<div style="text-align:center;clear:both;width:100%;font-size:20px;font-weight:bolder;color:#555;">Embedded Modbus RTU&amp;TCP WWW visualisation server</div>
<div id="content" style="position:relative;height:530px;">

<div style="top:80;left:630; position:absolute;width:250;height:250;">
<canvas id="gauge3" width="250" height="250"
data-type="canv-gauge"
data-title="Power"
data-min-value="0"
data-max-value="1000"
data-major-ticks="0 200 400 600 800 1000"
data-minor-ticks="5"
data-stroke-ticks="true"
data-units="W"
data-value-format="3.2"
data-glow="true"
data-animation-delay="10"
data-animation-duration="200"
data-animation-fn="bounce"
data-colors-needle="#f00 #00f"
data-highlights="0 200 #00E, 200 600 #FF1, 600 900 #0F0, 900 1000 #F00"
data-onready="setInterval( function() { Gauge.Collection.get('gauge3').setValue( power );}, 1000);"
></canvas>
<div style="text-align:center;width:100%">N30P Power</div>
</div>


<div style="top:80;left:380; position:absolute;width:250;height:250;">
<canvas id="gauge2" width="250" height="250"
data-type="canv-gauge"
data-title="Voltage"
data-min-value="200"
data-max-value="260"
data-major-ticks="200 220 230 240 260"
data-minor-ticks="10"
data-stroke-ticks="true"
data-units="V"
data-value-format="3.2"
data-glow="true"
data-animation-delay="10"
data-animation-duration="200"
data-animation-fn="bounce"
data-colors-needle="#f00 #00f"
data-highlights="200 220 #E00, 220 240 #0F0, 240 260 #e00"
data-onready="setInterval( function() { Gauge.Collection.get('gauge2').setValue( voltDeviation );}, 1000);"
></canvas>
<div style="text-align:center;width:100%">N30P Voltage</div>
</div>

<div style="top:80;left:130; position:absolute;width:250;height:250;">
<canvas id="gauge1" width="250" height="250"
data-type="canv-gauge"
data-title="Temperature"
data-min-value="0"
data-max-value="40"
data-major-ticks="0 10 20 30 40"
data-minor-ticks="10"
data-stroke-ticks="true"
data-units="&deg;C"
data-value-format="2.1"
data-glow="true"
data-animation-delay="10"
data-animation-duration="200"
data-animation-fn="bounce"
data-colors-needle="#f00 #00f"
data-highlights="0 20 #33e, 20 30 #0F0, 30 40 #e33"
data-onready="setInterval( function() { Gauge.Collection.get('gauge1').setValue( temperature );}, 1000);"
></canvas>
<div style="text-align:center;width:100%">Nano Temperature</div>
</div>

<div style="width: 120px; height: 320px; position: absolute; top:50; left:890;border:2px solid #BBB;border-radius:8px;">
<h3 style="padding-left:10px"/><center>Lantick Output</center></h3>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wyj_nr_1" onClick="sendData(1, ((lantick1[0]^1)&1))"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wyj_nr_2" onClick="sendData(2, ((lantick1[1]^1)&1))"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wyj_nr_3" onClick="sendData(3, ((lantick1[2]^1)&1))"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wyj_nr_4" onClick="sendData(4, ((lantick1[3]^1)&1))"></div>
</div>

<div style="width: 120px; height: 320px; position: absolute; top:50; left:0;border:2px solid #BBB;border-radius:8px;">
<h3 style="padding-left:10px"/><center>Lantick Input</center></h3>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wej_nr_1"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wej_nr_2"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wej_nr_3"></div>
<div style="margin-left:32px;margin-bottom:5px;background:transparent url('OFFF.png');width:57px;height:57px;" id="LanTick_Wej_nr_4"></div>
</div>


<div id="mtbl" style="position:absolute;left:460;top:180;width:55%;"></div>


<script type="text/javascript">
<!--

function updateStatus(xmlData) {
setTimeout("newAJAXCommand('comm.xml', updateStatus, false)",500);
if(!xmlData) return;

for(var i=0;i<4;i++) {
lantick1[i] = parseInt(getXMLValue(xmlData,'LanTick_Wyj_nr_'+(i+1)));
var obj = document.getElementById('LanTick_Wyj_nr_'+(i+1));
if(obj) obj.style.backgroundImage = (lantick1[i]) ? 'url(/ON.png)' : 'url(/OFFF.png)';

}

for(var i=0;i<4;i++) {
lantick2_wej[i] = parseInt(getXMLValue(xmlData,'LanTick_Wej_nr_'+(i+1)));
var obj = document.getElementById('LanTick_Wej_nr_'+(i+1));
if(obj) obj.style.backgroundImage = (lantick2_wej[i]) ? 'url(/ON.png)' : 'url(/OFFF.png)';

}

var t1 = parseInt(getXMLValue(xmlData, 'Temperatura_1'));
temperature = t1/10;


var v1 = parseInt(getXMLValue(xmlData, 'Miernik_n1'));
var v2 = parseInt(getXMLValue(xmlData, 'Miernik_n2'));
var v = v2 + v1*65536;
var f = ieee32ToFloat(v);
voltDeviation = f.toFixed(2);


v1 = parseInt(getXMLValue(xmlData, 'Miernik_p1'));
v2 = parseInt(getXMLValue(xmlData, 'Miernik_p2'));
v = v2 + v1*65536;
f = ieee32ToFloat(v);
power = f.toFixed(2);

}
function ieee32ToFloat(intval) {
var fval = 0.0;
var x;//exponent
var m;//mantissa
var s;//sign
s = (intval & 0x80000000)?-1:1;
x = ((intval >> 23) & 0xFF);
m = (intval & 0x7FFFFF);
switch(x) {
case 0:
//zero, do nothing, ignore negative zero and subnormals
break;
case 0xFF:
if (m) fval = NaN;
else if (s > 0) fval = Number.POSITIVE_INFINITY;
else fval = Number.NEGATIVE_INFINITY;
break;
default:
x -= 127;
m += 0x800000;
fval = s * (m / 8388608.0) * Math.pow(2, x);
break;
}
return fval;
}
function sendData(id, data)
{
newAJAXCommand('comm.xml?data=LanTick_Wyj_nr_' + id + ';' + data);
}

setTimeout("newAJAXCommand('comm.xml', updateStatus, false)",2000);
-->
</script>

</body>
</html>

Nasza strona korzysta z plików Cookie zgodnie z Polityką Prywatności.

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close