Example: Maschine.pro with HTML5, SVG, JavaScript
Web based standard technologies like HTML5, SVG (Scaleable Vector Graphics) and JavaScript are perfect for creating user interfaces which are platform independend and easy to deploy.
Required software:
- HTML Editor
- TwinCAT 2.11
- TcAdsWebService
- TcAdsWebService.js Library
- Microsoft IIS (Internet Information Services) Webserver
Before you can start...
- TwinCAT and the PLC program must be active.
- Microsoft IIS has to be installed and configured for hosting the TcAdsWebService.
1. Creating the SVG for the user interface
Use a vector based design software of your choice to create the draft for the SVG user interface and make sure that all elements which you want to manipulate from JavaScript are named reasonable.
2. Creating the basic HTML5 document
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<title>HTML5. SVG, JavaScript Machine Sample</title>
<link rel="Stylesheet" href=StyleSheet.css />
<script type="text/javascript" src="TcAdsWebService.js"></script>
<script type="text/javascript">
<!-- JavaScript will be placed here -->
</script>
</head>
<body>
<!-- SVG will be placed here -->
</body>
</html>
This meta tag enables inline svg support for the Internet Explorer 9.
<meta http-equiv="X-UA-Compatible" content="IE=9" />
2. Implement the SVG code into the body of the HTML5 document
{...}
<body>
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/"
id="svgROOT" height="100%" width="100%" viewBox="0 0 800 600" version="1.1" style="display: block;">
{...}
</svg>
</body>
{...}
To grant the optimal displaying of the SVG user interface we have to add some additional information to the SVG root element.
- The id is set to "svgROOT". This identifier can be used in JavaScript to get access to the SVG elements.
- The "height" and "width" properties are set to 100% because we want the user interface to use all the available space in the browser.
- The "viewBox" property defines the position and the original size of the SVG in the pattern "x-coordinate y-coordinate width height".
Because of this, the SVG will only scale within its aspect ratio.
2. Implement the JavaScript based logic
Our code is created within a function which is called directly by the JavaScript interpreter to prevent from conflicts with other JavaScript elements which may exist later in this HTML document.
(function (window) {
// We create our code here;
})(window);
At fist we define variables which exists in the whole context of our directly called JavaScript function.
var NETID = ""; // Empty string for local machine;
var PORT = "801"; // PLC Runtime port;
var SERVICE_URL = "http://" + window.document.location.hostname + "/TcAdsWebService/TcAdsWebService.dll"; // HTTP path to the target TcAdsWebService;
// The TcAdsWebService.Client object for ajax communication with the TcAdsWebService;
var client = new TcAdsWebService.Client(SERVICE_URL, null, null);
var general_timeout = 500; // This timeout value is used for all requests;
var readLoopID = null; // The id of the read interval; Can be used to stop the polling if need;
var readLoopDelay = 100;
var readSymbolValuesData = null; // This variable will store the Base64 encoded binary data string for the read sumcommando;
// Array of symbol names to read;
var handlesVarNames = [
".engine",
".deviceUp",
".deviceDown",
".steps",
".count",
".switch"
];
// Symbol handle variables;
var hEngine = null;
var hDeviceUp = null;
var hDeviceDown = null;
var hSteps = null;
var hCount = null;
var hSwitch = null;
// Base64 encoded binary data strings for write requests;
var switchTrueBase64 = null;
var switchFalseBase64 = null;
// UI Elements;
var radioActiveFast = null;
var radioActiveSlow = null;
var radioBackgroundFast = null;
var radioBackgroundSlow = null;
var textCount = null;
var upArrow = null;
var downArrow = null;
var progress = null;
var progressBackground = null;
var progress_initial_width = 0;
The initialization is done in the callback function of the window.onload event.
We do this, because we can be sure that all SVG and HTML elements do exist now.
window.onload = (function () { /* Initialization is done here */ });
Initializing
// Initialize UI Elements;
radioActiveFast = svgROOT.getElementById("radioActiveFast");
radioActiveSlow = svgROOT.getElementById("radioActiveSlow");
radioBackgroundFast = svgROOT.getElementById("radioBackgroundFast");
radioBackgroundSlow = svgROOT.getElementById("radioBackgroundSlow");
textCount = svgROOT.getElementById("textCount");
upArrow = svgROOT.getElementById("UPArrow");
downArrow = svgROOT.getElementById("DownArrow");
progress = svgROOT.getElementById("progress");
progress_initial_width = progress.width.baseVal.value;
radioBackgroundFast.onclick = RadioBackgroundFastClick;
radioBackgroundSlow.onclick = RadioBackgroundSlowClick;
// Prepare data for writing to switch variable;
var switchTrueBase64Writer = new TcAdsWebService.DataWriter();
switchTrueBase64Writer.writeBOOL(true);
switchTrueBase64 = switchTrueBase64Writer.getBase64EncodedData();
var switchFalseBase64Writer = new TcAdsWebService.DataWriter();
switchFalseBase64Writer.writeBOOL(false);
switchFalseBase64 = switchFalseBase64Writer.getBase64EncodedData();
Now we initialize a TcAdsWebService.DataWriter object to create a Base64 encoded binary string which contains all the data which the sumcommando needs to request handles for the TwinCAT Symbol names in the "handlesVarNames" array.
With the "readwrite" function of the TcAdsWebService.Client object we start the sumcommando request against the TcAdsWebService.
// Create sumcommando for reading twincat symbol handles by symbol name;
var handleswriter = new TcAdsWebService.DataWriter();
// Write general information for each symbol handle to the TcAdsWebService.DataWriter object;
for (var i = 0; i < handlesVarNames.length; i++) {
handleswriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolHandleByName);
handleswriter.writeDINT(0);
handleswriter.writeDINT(4); // Expected size; A handle has a size of 4 byte;
handleswriter.writeDINT(handlesVarNames[i].length); // The length of the symbol name string;
}
// Write symbol names after the general information to the TcAdsWebService.DataWriter object;
for (var i = 0; i < handlesVarNames.length; i++) {
handleswriter.writeString(handlesVarNames[i]);
}
// Send the list-read-write command to the TcAdsWebService by use of the readwrite function of the TcAdsWebService.Client object;
client.readwrite(
NETID,
PORT,
0xF082, // IndexGroup = ADS list-read-write command; Used to request handles for twincat symbols;
handlesVarNames.length, // IndexOffset = Count of requested symbol handles;
(handlesVarNames.length * 4) + (handlesVarNames.length * 8), // Length of requested data + 4 byte errorcode and 4 byte length per twincat symbol;
handleswriter.getBase64EncodedData(),
RequestHandlesCallback,
null,
general_timeout,
RequestHandlesTimeoutCallback,
true);
If the sumcommando to request the TwinCAT Symbol handles has finished, the RequestHandlesCallback function is called.
The parameter "e" will contain a TcAdsWebService.Response object with information about the request against the TcAdsWebService.
The parameter "s" is the "userState" parameter which was set to "null" in the function call.
// Occurs if the readwrite for the sumcommando has finished;
var RequestHandlesCallback = (function (e, s) {
{...}
});
Within the RequestHandlesCallback function we collect the handle information from the TcAdsWebService.DataReader object in the "reader" property of the TcAdsWebService.Response object. With these handles we initialize another TcAdsWebService.DataWriter object with data for a read sumcommando which will be called cyclic in the ReadLoop function.
if (e && e.isBusy) {
// HANDLE PROGRESS TASKS HERE;
// Exit callback function because request is still busy;
return;
}
if (e && !e.hasError) {
// Get TcAdsWebService.DataReader object from TcAdsWebService.Response object;
var reader = e.reader;
// Read error code and length for each handle;
for (var i = 0; i < handlesVarNames.length; i++) {
var err = reader.readDWORD();
var len = reader.readDWORD();
if (err != 0) {
// HANDLE SUMCOMMANDO ERRORS HERE;
return;
}
}
// Read handles from TcAdsWebService.DataReader object;
hEngine = reader.readDWORD();
hDeviceUp = reader.readDWORD();
hDeviceDown = reader.readDWORD();
hSteps = reader.readDWORD();
hCount = reader.readDWORD();
hSwitch = reader.readDWORD();
// Create sum commando to read symbol values based on the handle;
var readSymbolValuesWriter = new TcAdsWebService.DataWriter();
// ".engine" // BOOL
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hEngine); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(1); // Size to read;
// ".deviceUp" // BOOL
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hDeviceUp); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(1); // Size to read;
// ".deviceDown" // BOOL
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hDeviceDown); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(1); // Size to read;
// ".steps" // BYTE
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hSteps); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(1); // Size to read;
// ".count" // UINT
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hCount); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(2); // Size to read;
// ".switch" // BOOL
readSymbolValuesWriter.writeDINT(TcAdsWebService.TcAdsReservedIndexGroups.SymbolValueByHandle); // IndexGroup
readSymbolValuesWriter.writeDINT(hSwitch); // IndexOffset = The target handle
readSymbolValuesWriter.writeDINT(1); // Size to read;
// Get Base64 encoded data from TcAdsWebService.DataWriter;
readSymbolValuesData = readSymbolValuesWriter.getBase64EncodedData();
// Start cyclic reading of symbol values;
readLoopID = window.setInterval(ReadLoop, readLoopDelay);
} else {
if (e.error.getTypeString() == "TcAdsWebService.ResquestError") {
// HANDLE TcAdsWebService.ResquestError HERE;
}
else if (e.error.getTypeString() == "TcAdsWebService.Error") {
// HANDLE TcAdsWebService.Error HERE;
}
}
The ReadLoop function calls the the read sumcommando on every tick;
// Interval callback for cyclic reading;
var ReadLoop = (function () {
// Send the read-read-write command to the TcAdsWebService by use of the readwrite function of the TcAdsWebService.Client object;
client.readwrite(
NETID,
PORT,
0xF080, // 0xF080 = Read command;
handlesVarNames.length, // IndexOffset = Variables count;
7 + (handlesVarNames.length * 4), // Length of requested data + 4 byte errorcode per variable;
readSymbolValuesData,
ReadCallback,
null,
general_timeout,
ReadTimeoutCallback,
true);
});
In the ReadCallback function we collect the values of each TwinCAT Symbol in the TcAdsWebService.DataReader object of the "reader" porperty of the TcAdsWebService.Response object and use them to update the user interface.
// Occurs if the read-read-write command has finished;
var ReadCallback = (function (e, s) {
if (e && e.isBusy) {
// HANDLE PROGRESS TASKS HERE;
// Exit callback function because request is still busy;
return;
}
if (e && !e.hasError) {
var reader = e.reader;
// Read error codes from begin of TcAdsWebService.DataReader object;
for (var i = 0; i < handlesVarNames.length; i++) {
var err = reader.readDWORD();
if (err != 0) {
// HANDLE SUMCOMMANDO ERRORS HERE;
return;
}
}
// READ Symbol data from TcAdsWebService.DataReader object;
// ".engine" // BOOL
var engine = reader.readBOOL();
// ".deviceUp" // BOOL
var deviceUp = reader.readBOOL();
// ".deviceDown" // BOOL
var deviceDown = reader.readBOOL();
// ".steps" // BYTE
var steps = reader.readBYTE();
// ".count" // UINT/WORD
var count = reader.readWORD();
// ".switch" // BOOL
var switchVar = reader.readBYTE();
// Update UI
// Radio buttons
if (switchVar) {
radioActiveFast.style.visibility = "visible";
radioActiveSlow.style.visibility = "hidden";
} else {
radioActiveFast.style.visibility = "hidden";
radioActiveSlow.style.visibility = "visible";
}
// Textblock: Count
textCount.textContent = "Count: " + count;
// DeviceUp / DeviceDown Arrows
if (deviceUp) {
upArrow.style.fill = "#ea0000";
} else {
upArrow.style.fill = "#ffffff";
}
if (deviceDown) {
downArrow.style.fill = "#ea0000";
} else {
downArrow.style.fill = "#ffffff";
}
// Progress
var progress_scale = steps / 25; // 25 = max value of steps symbol;
if (steps >= 0 && steps <= 25) {
progress.width.baseVal.value = progress_initial_width * progress_scale;
}
else if (steps < 0) {
progress.width.baseVal.value = 0.00;
}
else if (steps > 25) {
progress.width.baseVal.value = progress_initial_width;
}
} else {
if (e.error.getTypeString() == "TcAdsWebService.ResquestError") {
// HANDLE TcAdsWebService.ResquestError HERE;
}
else if (e.error.getTypeString() == "TcAdsWebService.Error") {
// HANDLE TcAdsWebService.Error HERE;
}
}
});
If the RadioBackgroundFastClick function or the RadioBackgroundSlowClick functions are called we send a "write" request with the specified Base64 encoded binary data against the TcAdsWebService.
// Occurs if the background of the radio button "Fast" is clicked;
var RadioBackgroundFastClick = (function () {
client.write(
NETID,
PORT,
0x0000F005, // IndexGroup = Write variable by handle;
hSwitch,
switchTrueBase64,
RadioBackgroundFastWriteCallback,
null,
general_timeout,
RadioBackgroundFastWriteTimeoutCallback,
true
);
});
// Occurs if the background of the radio button "Slow" is clicked;
var RadioBackgroundSlowClick = (function () {
client.write(
NETID,
PORT,
0x0000F005, // Write variable by handle;
hSwitch,
switchFalseBase64,
RadioBackgroundSlowWriteCallback,
null,
general_timeout,
RadioBackgroundSlowWriteTimeoutCallback,
true
);
});
If the browser window or the browser tab is going to be closed we release the used handles with the IndexGroup 0xF006 (Relase Symbol Handle).
window.onbeforeunload = (function () {
// Free Handles
client.write(NETID, PORT, 0xF006, hEngine, "", FreeHandleCallback, "hEngine", general_timeout, FreeHandleTimeoutCallback, true);
client.write(NETID, PORT, 0xF006, hDeviceUp, "", FreeHandleCallback, "hDeviceUp", general_timeout, FreeHandleTimeoutCallback, true);
client.write(NETID, PORT, 0xF006, hDeviceDown, "", FreeHandleCallback, "hDeviceDown", general_timeout, FreeHandleTimeoutCallback, true);
client.write(NETID, PORT, 0xF006, hSteps, "", FreeHandleCallback, "hSteps", general_timeout, FreeHandleTimeoutCallback, true);
client.write(NETID, PORT, 0xF006, hCount, "", FreeHandleCallback, "hCount", general_timeout, FreeHandleTimeoutCallback, true);
client.write(NETID, PORT, 0xF006, hSwitch, "", FreeHandleCallback, "hSwitch", general_timeout, FreeHandleTimeoutCallback, true);
});
Now we can watch the Machine Sample in any Browser with HTML5 and inline SVG support.