The GUI will be augmented with the possibility to start different control algorithms from the client side. This time, we have P control and PID control. We would like to select an algorithm for comparison. We will also add static message on the client side.
We will build on the Example 15.
In the .js file, we should add type staticMsgToClient
for printing out the static messages in <div> on client side:
{"type": "staticMsgToClient", "content": "Srv connected, board OK"};
We should change example15 to
fs.readFile(__dirname + "/example16.html"
We should broadcast additional message, this time static:
messageJSON = {"type": "staticMsgToClient", "content": "Srv connected, board OK"};
ws.send(JSON.stringify(messageJSON));
Function startControlAlgorithm is changed, this time, function has an argument with msg object from client side (msg) which determine which control algorithm we would like to trigger as well as parameter values:
case "startControlAlgorithm":
startControlAlgorithm(msg); // msg has all values, alg. type and parameters
break;
Function controlAlgorithm has now argument (msg); we check which algorithm should be started (check msg.ctrlAlgNo==1 or msg.ctrlAlgNo==2):
function controlAlgorithm (msg) { // the parameter in the argument holds ctrlAlgNo and param. values
if(msg.ctrlAlgNo == 1) {
pwm = msg.pCoeff*(desiredValue-actualValue);
if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive
if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative
if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // določimo smer če je > 0
if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // določimo smer če je < 0
board.analogWrite(3, Math.abs(pwm));
console.log(Math.round(pwm));
}
if(msg.ctrlAlgNo == 2) {
err = desiredValue - actualValue; // error as difference between desired and actual val.
errSum += err; // sum of errors | like integral
dErr = err - lastErr; // difference of error
pwm = msg.Kp1*err+msg.Ki1*errSum+msg.Kd1*dErr; // PID expression
lastErr = err; // save the value of error for next cycle to estimate the derivative
if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive
if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative
if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // determine direction if > 0
if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // determine direction if < 0
board.analogWrite(3, Math.abs(pwm));
};
}
Line with pwm should be changed; this time we get the parameters from the client side (notice 1 at Kp1, Ki1, Kd1):
pwm = msg.Kp1*err+msg.Ki1*errSum+msg.Kd1*dErr; // PID expression
Function startControlAlgorithm should now have argument (msg):
function startControlAlgorithm (msg) {
if (controlAlgorithmStartedFlag == 0) {
// reset parameters
pwm = 0; // Reset Pulse Width Modulation value
err = 0; // Reset error
errSum = 0; // Reset sum of errors as integral
dErr = 0; // Reset difference of error
lastErr = 0; // Reset value whih keeps the value of previous error to estimate derivative
controlAlgorithmStartedFlag = 1;
intervalCtrl = setInterval(function(){controlAlgorithm(msg);}, 30); // call the alg. on 30ms
console.log("Control algorithm has been started.");
messageJSON = {"type": "staticMsgToClient", "content": msg.ctrlAlgNo + " started | " + json2txt(msg)};
wss.broadcast(JSON.stringify(messageJSON));
}
};
In the set interval, the function has argument, we ad log and staticMsgToClient
:
intervalCtrl = setInterval(function(){controlAlgorithm(msg);}, 30); // call the alg. on 30ms
console.log("Control algorithm has been started.");
messageJSON = {"type": "staticMsgToClient", "content": msg.ctrlAlgNo + " started | " + json2txt(msg)};
wss.broadcast(JSON.stringify(messageJSON));
In the function stopControlAlgorithm we add:
console.log("Control algorithm has been stopped.");
messageJSON = {"type": "staticMsgToClient", "content": "Stopped."};
wss.broadcast(JSON.stringify(messageJSON));
We add function for printing out the json content when sending string to client:
function json2txt(obj) // function to print out the json names and values
{
var txt = '';
var recurse = function(_obj) {
if ('object' != typeof(_obj)) {
txt += ' = ' + _obj + '\n';
}
else {
for (var key in _obj) {
if (_obj.hasOwnProperty(key)) {
txt += '.' + key;
recurse(_obj[key]);
}
}
}
};
recurse(obj);
return txt;
}
In .html file we should add input fields and add 1 and 2 at the end of functions:
pCoeff: <input id="pCoeff" value="0.1" size="5" />
<button id="buttonStartControlAlgorithm1" onClick="startControlAlgorithm1();">Start Ctrl Alg1</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm();">Stop Ctrl Alg</button>
<p></p>
Kp: <input id="Kp1" value="0.15" size = "5" />
Ki: <input id="Ki1" value="0.0055" size = "5" />
Kd: <input id="Kd1" value="0.25" size = "5" />
<button id="buttonStartControlAlgorithm2" onClick="startControlAlgorithm2();">Start Ctrl Alg2</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm();">Stop Ctrl Alg</button>
<div id="divForStaticPrint"> </div>
<p></p>
We should declare function staticMsgToClient
listening on WebSocket:
case "staticMsgToClient":
document.getElementById("divForStaticPrint").innerHTML = "Status: " + msg.content; // we print to div
break;
Functions should have 1 and 2 at the end (startControlAlgorithm1):
function startControlAlgorithm1() {
stopControlAlgorithm(); // just in case, if it is not started
pCoeff = document.getElementById("pCoeff").value; // read the value of coeff from input field
messageJSON = {"type": "startControlAlgorithm", "ctrlAlgNo": 1, "pCoeff": pCoeff};
ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket
}
function startControlAlgorithm2() {
stopControlAlgorithm(); // just in case, if it is not started
Kp1 = document.getElementById("Kp1").value; // read the value of coeff from input field
Ki1 = document.getElementById("Ki1").value; // read the value of coeff from input field
Kd1 = document.getElementById("Kd1").value; // read the value of coeff from input field
messageJSON = {"type": "startControlAlgorithm", "ctrlAlgNo": 2, "Kp1": Kp1, "Ki1": Ki1, "Kd1": Kd1};
ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket
}
GUI is shown on the next figure. We can select between P and PID controller and specify the parameters of each:
Difference between example15.js and example16.js
Difference between example15.html and example16.html
var http = require("http").createServer(handler); // on req - hand
var fs = require("fs"); // variable for file system for providing html
var firmata = require("firmata");
const WebSocket = require('ws'); // for permanent connection between server and client
const wss = new WebSocket.Server({port: 8888}); // websocket port is 8888
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
};
var messageJSON;
var controlAlgorithmStartedFlag = 0; // variable for indicating weather the Alg has benn sta.
var intervalCtrl; // var for setInterval in global scope
console.log("Starting the code");
var board = new firmata.Board("/dev/ttyACM0", function(){
console.log("Connecting to Arduino");
console.log("Enabling analog Pin 0");
board.pinMode(0, board.MODES.ANALOG); // analog pin 0
board.pinMode(1, board.MODES.ANALOG); // analog pin 1
board.pinMode(2, board.MODES.OUTPUT); // direction of DC motor
board.pinMode(3, board.MODES.PWM); // PWM of motor i.e. speed of rotation
board.pinMode(4, board.MODES.OUTPUT); // direction DC motor
});
function handler(req, res) {
fs.readFile(__dirname + "/example16.html",
function (err, data) {
if (err) {
res.writeHead(500, {"Content-Type": "text/plain"});
return res.end("Error loading html page.");
}
res.writeHead(200);
res.end(data);
})
}
var desiredValue = 0; // desired value var
var actualValue = 0; // variable for actual value (output value)
var Kp1 = 0.55; // proportional factor of PID controller
var Ki1 = 0.008; // integral factor of PID controller
var Kd1 = 0.15; // differential factor of PID controller
var factor = 0.3; // proportional factor that determines speed of resonse
var pwm = 0; // set pwm as global variable
var pwmLimit = 254; // to limit value of the pwm that is sent to the motor
var err = 0; // error
var errSum = 0; // sum of errors as integral
var dErr = 0; // difference of error
var lastErr = 0; // to keep the value of previous error to estimate derivative
http.listen(8080); // server will listen on port 8080
board.on("ready", function() {
board.analogRead(0, function(value){
desiredValue = value; // continuous read of analog pin 0
});
board.analogRead(1, function(value) {
actualValue = value; // continuous read of pin A1
});
wss.on('connection', function (ws, req) { // start of wss code
messageJSON = {"type": "message", "content": "Srv connected, board OK"};
ws.send(JSON.stringify(messageJSON));
messageJSON = {"type": "staticMsgToClient", "content": "Srv connected, board OK"};
ws.send(JSON.stringify(messageJSON));
setInterval(sendValues, 40); // on 40ms we send message to client
ws.on("message", function (msgString) { // message comes as string -> msgString
var msg = JSON.parse(msgString); // string from ws which comes as a string is put to JSON
switch(msg.type) {
case "startControlAlgorithm":
startControlAlgorithm(msg); // msg has all values, alg. type and parameters
break;
case "stopControlAlgorithm":
stopControlAlgorithm();
break;
}
}); // end of wss.on code
}); // end of sockets.on connection
}); // end of board.on(ready)
function controlAlgorithm (msg) { // the parameter in the argument holds ctrlAlgNo and param. values
if(msg.ctrlAlgNo == 1) {
pwm = msg.pCoeff*(desiredValue-actualValue);
if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive
if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative
if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // določimo smer če je > 0
if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // določimo smer če je < 0
board.analogWrite(3, Math.abs(pwm));
console.log(Math.round(pwm));
}
if(msg.ctrlAlgNo == 2) {
err = desiredValue - actualValue; // error as difference between desired and actual val.
errSum += err; // sum of errors | like integral
dErr = err - lastErr; // difference of error
pwm = msg.Kp1*err+msg.Ki1*errSum+msg.Kd1*dErr; // PID expression
lastErr = err; // save the value of error for next cycle to estimate the derivative
if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive
if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative
if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // determine direction if > 0
if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // determine direction if < 0
board.analogWrite(3, Math.abs(pwm));
};
}
function startControlAlgorithm (msg) {
if (controlAlgorithmStartedFlag == 0) {
// reset parameters
pwm = 0; // Reset Pulse Width Modulation value
err = 0; // Reset error
errSum = 0; // Reset sum of errors as integral
dErr = 0; // Reset difference of error
lastErr = 0; // Reset value whih keeps the value of previous error to estimate derivative
controlAlgorithmStartedFlag = 1;
intervalCtrl = setInterval(function(){controlAlgorithm(msg);}, 30); // call the alg. on 30ms
console.log("Control algorithm has been started.");
messageJSON = {"type": "staticMsgToClient", "content": " No. " + msg.ctrlAlgNo + " started | " + json2txt(msg)};
wss.broadcast(JSON.stringify(messageJSON));
}
};
function stopControlAlgorithm () {
clearInterval(intervalCtrl); // clear the interval of control algorihtm
board.analogWrite(3, 0);
controlAlgorithmStartedFlag = 0;
pwm = 0;
console.log("Control algorithm has been stopped.");
messageJSON = {"type": "staticMsgToClient", "content": "Stopped."};
wss.broadcast(JSON.stringify(messageJSON));
};
function sendValues () {
wss.broadcast(JSON.stringify({"type": "clientReadValues", "desiredValue": desiredValue, "actualValue": actualValue, "error": (desiredValue - actualValue), "pwm": (pwm).toFixed(0)}));
};
function json2txt(obj) // function to print out the json names and values
{
var txt = '';
var recurse = function(_obj) {
if ('object' != typeof(_obj)) {
txt += ' = ' + _obj + '\n';
}
else {
for (var key in _obj) {
if (_obj.hasOwnProperty(key)) {
txt += '.' + key;
recurse(_obj[key]);
}
}
}
};
recurse(obj);
return txt;
}
<!DOCTYPE html>
<meta charset = utf8>
<html>
<head>
<title>Example with potentiometers</title>
</head>
<body onload="load()";>
<div>
<canvas id="canvas1" width ="200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
</div>
pCoeff: <input id="pCoeff" value="0.1" size="5" />
<button id="buttonStartControlAlgorithm1" onClick="startControlAlgorithm1();">Start Ctrl Alg1</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm();">Stop Ctrl Alg</button>
<p></p>
Kp: <input id="Kp1" value="0.15" size = "5" />
Ki: <input id="Ki1" value="0.0055" size = "5" />
Kd: <input id="Kd1" value="0.25" size = "5" />
<button id="buttonStartControlAlgorithm2" onClick="startControlAlgorithm2();">Start Ctrl Alg2</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm();">Stop Ctrl Alg</button>
<div id="divForStaticPrint"> </div>
<p></p>
<div id="print1"></div>
<br>
<script type="text/javascript">
"use strict"; // in order to use clasess
var divElement = document.getElementById("print1"); // var for div el.
var numberOfLinesBeforeScroll = 10;
var linesPrintCounter = 0;
var potValue1 = 0; // value of the first potentiometer
var x1 = new Array(); // array for x1
var y1 = new Array(); // array for y1
var potValue2 = 0; // value of second potentiometer
var x2 = new Array(); // array for x2 variable
var y2 = new Array(); // array for y2 variable
var canvas1;
var ctx1;
var pCoeff = 0;
var Kp1 = 0;
var Ki1 = 0;
var Kd1 = 0;
function log(msg) {
var node=document.createElement("tr"); // we create the variable node as the a table row (tr)
var textnode=document.createTextNode(linesPrintCounter + " | " + msg); // we create element with the text adding the counter
node.appendChild(textnode); // adding text to "node", i.e. table row
divElement.insertBefore(node, divElement.childNodes[0]); // inserting into variable node
if (linesPrintCounter > numberOfLinesBeforeScroll-1) { // if the lines are more than limit -> start with scroll
divElement.removeChild(divElement.childNodes[numberOfLinesBeforeScroll]); // we remove the oldest printout
}
linesPrintCounter++; // increasing the number of printouts
}
let ws = new WebSocket("ws://192.168.254.149:8888"); // create socket - connect to it
var messageJSON;
function load() { // function that is started, when we open the page
canvas1 = document.getElementById("canvas1");
ctx1 = canvas1.getContext("2d");
// initialization of graph with points
ctx1.lineWidth = "1";
ctx1.strokeStyle = "#ff0000";
// initializaction of first time series
for (var i=0; i<200; i++) {
x1[i] = i; // for x values are 0, 1, 2, ...
y1[i] = 0; // for y values are 0
};
// initializaion of second graph
for (var i=0; i<200; i++) {
x2[i] = i; // x2 values are 0, 1, 2, ...
y2[i] = 0; // for y2 values are 0
};
};
ws.onmessage = function(event) {
var msg = JSON.parse(event.data); // string from ws is put to JSON
switch(msg.type) {
case "message":
log(msg.content); // add msg to div
break;
case "staticMsgToClient":
document.getElementById("divForStaticPrint").innerHTML = + msg.content; // we print to div
break;
case "clientReadValues":
potValue1 = msg.desiredValue;
potValue2 = msg.actualValue;
// Draw first graph *****************************************
ctx1.clearRect(0, 0, canvas1.width, canvas1.height); // clear the canvas
ctx1.lineWidth = "1";
ctx1.strokeStyle = "#ff0000";
ctx1.beginPath(); // to start drawing new line
y1.splice(0, 1); // on the position 0 in the field y1 we erase one value
y1[199] = potValue1; // new value is added at the end
for (var i=0; i<200; i++) {
ctx1.lineTo(x1[i], (100 - (y1[i] / 1023) * 100)); // 0,0 x,y coordinate is in upper left corner
};
ctx1.stroke(); // to draw the line
// End of draw graph***********************************
// Draw second graph **********************************************
ctx1.lineWidth = "1";
ctx1.strokeStyle = "#00ff00";
ctx1.beginPath(); // to start drawing new line
y2.splice(0, 1); // on the position 0 in the field y2 we erase one value
y2[199] = potValue2; // new value is added at the end
for (i=0; i<200; i++) {
ctx1.lineTo(x2[i], (100 - (y2[i] / 1023) * 100)); // 0,0 x,y coordinate is in upper left corner
};
ctx1.stroke(); // to draw the line
// Draw second graph graph END ************************************
log(potValue1 + "|" + potValue2 + "|" + msg.error + "|" + msg.pwm);
break;
}
};
function startControlAlgorithm1() {
stopControlAlgorithm(); // just in case, if it is not started
pCoeff = document.getElementById("pCoeff").value; // read the value of coeff from input field
messageJSON = {"type": "startControlAlgorithm", "ctrlAlgNo": 1, "pCoeff": pCoeff};
ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket
}
function startControlAlgorithm2() {
stopControlAlgorithm(); // just in case, if it is not started
Kp1 = document.getElementById("Kp1").value; // read the value of coeff from input field
Ki1 = document.getElementById("Ki1").value; // read the value of coeff from input field
Kd1 = document.getElementById("Kd1").value; // read the value of coeff from input field
messageJSON = {"type": "startControlAlgorithm", "ctrlAlgNo": 2, "Kp1": Kp1, "Ki1": Ki1, "Kd1": Kd1};
ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket
}
function stopControlAlgorithm () {
messageJSON = {"type": "stopControlAlgorithm"};
ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket
}
</script>
</body>
</html>