Drucken
S7-1200 betrifft S7-1200

Problembeschreibung:

Ab der S7-1200er Reihe ist serienmäßig in der SPS ein Webserver integriert. Über diesen Webserver kann auf die SPS-Variablen beliebig zugegriffen werder. Das eröffnet natürlich völlig neue Möglichkeiten, z. B. Fernsteuerung über einen Webbrowser oder einer App auf einem Smartphone.

Einen Haken hat die Sache allerdings. Die interne Verknüpfung des parallel laufenden Webservers und der SPS-CPU-Einheit selbst ist alles andere als schnell. Pro abgefragte Variable auf einer Website benötigt die CPU rund 0,5 Sekunden - unabhängig von der Größe der Variable. Wenn man also z. B. 10 Variablen auf der Webseite anzeigen will, dauert der Aufbau der Webseite 5 Sekunden. Für ein Smart-Home-Projekt, wo z. B. auf einer Übersichtsseite der Lichtstatus aller Räume angezeigt werden soll, kommt man schnell in Größenordnungen von 10-15 Sekunden. Solch eine Zeitverzögerung ist einfach nicht akzeptabel.

Funktionierende Lösung

Zum Glück gibt es eine Lösung, die von SIEMENS selbst auch vorgeschlagen wird. Der Trick besteht darin, alle zu übertragenden Variablen in einer einzigen String-Variable zu übertragen und dann auf Client-Seite z. B. mittels Javascript wieder zu trennen und den einzelnen Bereichen der Website zuzuführen.

Universelle Beispiellösung

Nachfolgend wird ein Beispiel beschrieben, welches folgenden Prämissen folgt:

Die Websteuerung besteht aus folgenden Dateien:

Das Beispielpaket kann hier heruntergeladen werden.

Programmcode und Variablen in der S7-1200

Nachfolgende Bilder zeigen den Programmcode (KOP) im Operationsbaustein und die Variablendefinitionen im Datenbaustein ''Webvariablen''.

KOP
Datenbaustein

index.html

Die index.html stellt die Webseite dar, die vom Browser geladen wird. Sie enthält die Verweise auf die CSS-Vorlage und den Javascript-Code. Die Webseite enthält 3 anzuzeigende Werte bzw. Stati von Variablen:

  1. XML-Status: Zeigt an, ob die XML-Datei mit den SPS-Variablenwerten vollständig geladen und verarbeitet wurde. Nach laden der index.html wird der standardmäßig der Status ''nicht geladen'' mit der Grafik ''xml_nicht_geladen.png'' angezeigt. Gleichzeitig wird aber die Funktion ''loadXML()'' aufgerufen, um die XML-Daten nachzuladen. Nach erfolgreicher Verarbeitung der XML-Daten, wechselt der Status auf ''geladen'' und die Grafik ''xml_geladen.png'' wird angezeigt.
  2. Empfohlene Helligkeit: Zeigt einen numerischen Wert an.
  3. Licht an: Zeigt in Form einer Grafik einen Bit-Wert (An/Aus) an.

Eine ganz wichtige Rolle spielt die Funktion ''Init_Regler()'', die nach Laden der Seite durch den Body-Tag aufgerufen wird. Sie ermittelt aus dem HTML-Code sämtliche SPS-Variablen, die eine dazugehörige Benutzerinteraktion bei Aktivierung (z.B. Klick oder Touch) verlangen. Diese Variablen sind durch die CSS-Klasse ''touch'' gekennzeichnet. Diesen Variablen wird die zugehörige Interaktion, z.B. WU = Wechseln_Uebertragen, zugeordnet, die im Namensattribut angegeben ist.

Damit nach dem Laden der XML-Datei der Javascript-Code weiß, wo er die Werte einsetzen muss, sind die einzelnen SPS-Variablen im HTML-Code durch die Attribute ''id'' und ''name'' entsprechend gekennzeichnet.

Der HTML-Code der index.html sieht wie folgt aus:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
<head>
<title>S7-1200-Smart-Home-Steuerung</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="expires" content="0">
<link rel="stylesheet" type="text/css" href="/standard.css" />
<script src="/standard.js" type="text/javascript"></script>
</head>
<body onLoad="Init_Regler()">
<div class="xml_status" id="XML_geladen" onclick="loadXML()"><img id="xml_status" src="/xml_nicht_geladen.png" alt="XML-Status"></div>
<div class="tages_status">
<table><tbody><tr>
<td><span id="DB_Globale_Steuerung_empfohlene_Helligkeit">-1</span>%</td>
</tr></tbody></table>
</div>

<div id="hauptbox">

<h1>SPS-Haussteuerung</h1>
<div class="box gross">
<h2>Wohnzimmer</h2>
<form name="Wohnzimmer_Licht_an" method="post">
<input type="hidden" name='"Wohnzimmer".Licht_an' id="Wohnzimmer_Licht_an" value=''0'' />
<div class="taste touch" name="WU~Wohnzimmer_Licht_an~:Wohnzimmer:.Licht_an">
<img id="Grafik_Wohnzimmer_Licht_an" src="/Licht_an_0.png" alt="Licht_an_0" name="WU~Wohnzimmer_Licht_an~:Wohnzimmer:.Licht_an" />
</div>
</form>
</div>

</div>
<div class="copyright">&copy; 2012-2015 by Dirk Drescher</div>
</body>
</html>

index.xml

Die index.xml wird automatisch durch die Funktion ''Load_XML()'' nachgeladen, die nach Laden der index.html aufgerufen wird. Die XML-Datei muss immer genau so wie die dazugehörige HTML-Datei benannt sein. Die XML-Datei beschreibt die abzurufenden SPS-String-Variable und die Aufteilungsanweisung in die einzelnen Variablen.

Die index.xml sieht wie folgt aus:

<!-- AWP_In_Variable Name='"Wohnzimmer".Licht_an' -->
<!-- AWP_In_Variable Name='"DB_Globale_Steuerung".empfohlene_Helligkeit' -->
<set>
<sammler>:="Webvariablen".Startseite:</sammler>
<variable db="Wohnzimmer" name="Licht_an" type="bitgrafik" index="yes" invers="0">0</variable>
<variable db="DB_Globale_Steuerung" name="empfohlene_Helligkeit" type="wert" index="yes" invers="1">1</variable>
</set>

Am Anfang der XML-Datei stehen alle SPS-Variablen, die auch beschrieben werden dürfen. Werden diese Angaben vergessen, können die Werte aus der SPS zwar abgerufen aber nicht verändert werden. Hier wird die Variable ''Licht_an'' aus dem Datenbaustein ''Wohnzimmer'' abgerufen und aus dem Datenbaustein ''DB_Globale_Steuerung'' die Variable ''empfohlene_Helligkeit''.

Der Tag ''sammler'' beinhaltet die SPS-String-Variable, die alle Werte in einer Variable vereint, getrennt durch die Tilde ''~''. Diese Variable (''Startseite'' aus dem Datenbaustein ''Webvariablen'') wird durch die SPS beim Abruf gesetzt.

Die Tags ''variable'' beschreiben die enthaltenen Werte im Sammel-String wie folgt:

Die XML-Datei beschreibt also zwei Variablen, den Status des Wohzimmer-Lichts (als erstes im Sammelstring enthalten) und die empfohlene Helligkeit (als zweites im Sammel-String enthalten).

standard.js

Die Javascript-Datei enthält den Interaktionscode und die Verarbeitung der XML-Datei. Die Funktionen habe folgende Funktionen:

/* Standard-Funktionen für SPS-Steuerung
Autor: Dr.-Ing. Dirk Drescher
*/

function Wechseln_Uebertragen(Formular,Name)
{
var SPSVariablenname = Name.replace(/:/g, '"');
var Wert = document.forms[Formular].elements[SPSVariablenname].value;
if (Wert == 0) {Wert = 1;}
else {Wert = 0;}
document.forms[Formular].elements[SPSVariablenname].value = Wert;
Schieberegler_Send(SPSVariablenname, Wert);
}

var Senden_aktiv = false;
var xmlhttp = null;
var XML_Variablen = new Array();
var XML_Typen = new Array();
var XML_Indexes = new Array();
var XML_Werte = new Array();
var XML_Schalter = new Array();
var XML_DB = new Array();
var Dokumente = new Array();

function Init_Regler()
{ // initialisiert die Schieberegler Tasten und muss im body-Tag aufgerufen werden, onLoad="Init_Regler"
var Tasten = document.getElementsByClassName('touch');
for (var i=0; i<Tasten.length; i++)
{
var TastenID = Tasten[i].getAttribute('name');
var TastenInfo = TastenID.split('~');
if (TastenInfo[0] == 'WU')
{
Tasten[i].onclick = Taste_Click;
Tasten[i].ontouchstart = Taste_Touch;
}
}
loadXML();
}

function Wechseln_Uebertragen(Formular,Name)
{
var SPSVariablenname = Name.replace(/:/g, '"');
var Wert = document.forms[Formular].elements[SPSVariablenname].value;
if (Wert == 0) {Wert = 1;}
else {Wert = 0;}
document.forms[Formular].elements[SPSVariablenname].value = Wert;
Schieberegler_Send(SPSVariablenname, Wert);
}

function Taste_Click(Ereignis)
{ // Event-Handler für drücken
var TastenID = Ereignis.target.getAttribute('name');
var TastenInfo = TastenID.split('~');
if (TastenInfo[0] == 'WU') Wechseln_Uebertragen(TastenInfo[1],TastenInfo[2]);
}

function Taste_Touch(Ereignis)
{ // Event-Handler für drücken
var Touches = Ereignis.targetTouches;
for (var i=0; i<Touches.length; i++)
{ // Jedes Touchereignis prüfen
var TastenID = Ereignis.targetTouches[i].target.getAttribute('name');
if (TastenID)
{
var TastenInfo = TastenID.split('~');
if (TastenInfo[0] == 'WU')
{
Wechseln_Uebertragen(TastenInfo[1],TastenInfo[2]);
break;
}
}
}
Ereignis.preventDefault();
}

function Schieberegler_Send(SPSVariablenname, Reglerwert)
{ // Reglerwert senden
var Dateipfad = document.location.href;
var Dateiname = Dateipfad.substring(Dateipfad.lastIndexOf("/")+1);
var XMLName = Dateiname.substring(0,Dateiname.lastIndexOf(".")) + '.xml';

if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
// Eigentlicher Aufruf
Senden_aktiv = true;
xmlhttp.open("POST",XMLName,true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.onreadystatechange=OnStateChange;
xmlhttp.send(SPSVariablenname+'='+Reglerwert);
}

function loadXML()
{
// Detail-HTML ermitteln
var Dateipfad = document.location.href;
var Dateiname = Dateipfad.substring(Dateipfad.lastIndexOf("/")+1);
var XMLName = Dateiname.substring(0,Dateiname.lastIndexOf(".")) + ''.xml'';

if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}

document.getElementById("xml_status").src = 'xml_nicht_geladen.png';
xmlhttp.onreadystatechange=OnStateChange;
xmlhttp.open("GET",XMLName,true);
xmlhttp.send();
}

function OnStateChange()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
Senden_aktiv = false;
var xmlDoc=xmlhttp.responseXML;
var VariableID;
// alle Frames inklusive Hauptfenster registrieren
Dokumente[0] = top.document;
Dokumente[1] = top.frames["Details"].document;

// alle enthaltenen Elemente auswerten
for (var i=0; i<xmlDoc.getElementsByTagName('set').length; i++)
{ // jedes Raum element verarbeiten
var Raumknoten = xmlDoc.getElementsByTagName('set')[i];

// Jetzt Sammler lesen
var Sammler = Raumknoten.getElementsByTagName('sammler')[0].firstChild.nodeValue;
var Sammlerwerte = Sammler.split("~");

// jede Variable lesen
for (var j=0; j<Raumknoten.getElementsByTagName('variable').length; j++)
{ // jede Raumvariable verarbeiten, die einen direkten Wert hat
var Raumvariablename = Raumknoten.getElementsByTagName('variable')[j].getAttribute("name");
var Raumvariabletyp = Raumknoten.getElementsByTagName('variable')[j].getAttribute("type");
var Raumvariableindex = Raumknoten.getElementsByTagName('variable')[j].getAttribute("index");
var Raumvariableschalter = Raumknoten.getElementsByTagName('variable')[j].getAttribute("invers");
var Raumvariablenwert = Raumknoten.getElementsByTagName('variable')[j].firstChild.nodeValue;
var Raumname = Raumknoten.getElementsByTagName('variable')[j].getAttribute("db");
XML_DB[j] = Raumname;
XML_Variablen[j] = Raumvariablename;
XML_Typen[j] = Raumvariabletyp;
XML_Indexes[j] = Raumvariableindex;
XML_Werte[j] = Raumvariablenwert;
XML_Schalter[j] = Raumvariableschalter;
}
// Jetzt die Werte verarbeiten
for (var j=0; j < XML_Variablen.length; j++)
{
// Wert im Detailfenster und Hauptfenster aktualisieren
if (XML_Typen[j] == 'wert')
{ // Zahlenwert
VariableID = XML_DB[j] + "_" + XML_Variablen[j];
// Jeden Frame durchsuchen
for (var f=0; f < Dokumente.length; f++)
{
var Hauptframe = Dokumente[f];
//alert("Variable: "+VariableID);
if (Hauptframe.getElementById(VariableID))
{ // Formular existiert -> Werte setzen
if (XML_Indexes[j] == "yes")
{ // es ist ein Index -> Wert auslesen
var XML_Idx = XML_Werte[j];
Raumvariablenwert = Sammlerwerte[XML_Idx].replace(/^\\s+|\\s+$/g,'');
}
else {Raumvariablenwert = XML_Werte[j];}
if (XML_Schalter[j] == 1) {Hauptframe.getElementById(VariableID).innerHTML = Raumvariablenwert;}
else {Hauptframe.getElementById(VariableID).value = Raumvariablenwert;}
}
}
}
else if (XML_Typen[j] == 'bitgrafik')
{ // Umschaltgrafik
VariableID = XML_DB[j] + "_" + XML_Variablen[j];
// Jeden Frame durchsuchen
for (var f=0; f < Dokumente.length; f++)
{
var Hauptframe = Dokumente[f];
//alert("Variable: "+VariableID);
if (XML_Indexes[j] == "yes")
{ // es ist ein Index -> Wert auslesen
var XML_Idx = XML_Werte[j];
Raumvariablenwert = Sammlerwerte[XML_Idx].replace(/^\\s+|\\s+$/g,'');
}
else {Raumvariablenwert = XML_Werte[j];}
if (Hauptframe.getElementById(VariableID))
{ // Formular existiert -> Werte setzen
Hauptframe.getElementById(VariableID).value = Raumvariablenwert;
}
// Grafik setzen
//alert("ID: Grafik_"+VariableID+"; Wert="+XML_Variablen[j]+'_'+Raumvariablenwert+'.png');
if (Hauptframe.getElementById("Grafik_"+VariableID))
{
Hauptframe.getElementById("Grafik_"+VariableID).src = XML_Variablen[j]+'_'+Raumvariablenwert+'.png';
// bei Warntasten noch Farbe setzen
// Elern-DIV ermitteln
var Eltern_Div = Hauptframe.getElementById("Grafik_"+VariableID).parentNode;
if (Eltern_Div.classList.contains('mark_0'))
{ // Class color_0
if (Raumvariablenwert == 0)
{
if (!Eltern_Div.classList.contains('color_0')) {Eltern_Div.classList.add('color_0');}
}
else {Eltern_Div.classList.remove('color_0');}
}
}
}
}
else
{ // Typ unbekannt -> Fehlermeldung
alert("unbekannter Typ '" + XML_Typen[j] + "' in Raum " + XML_DB[j] + ", Variable " + XML_Variablen[j]);
}
}
}
// Status geladen anzeigen
document.getElementById("xml_status").src = 'xml_geladen.png';
}
}