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:

Before you can start...

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.

Example: Maschine.pro with HTML5, SVG, JavaScript 1:

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.

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.

Example: Maschine.pro with HTML5, SVG, JavaScript 2:

 

2.  Download the example

HTML5, SVG, JavaSript Example