This example will add two buttons to start and stop DC motor position control algorithm. Besides, we will add some more data to the logging on the client side.
We should create example14.js and example14.html files. First, we should add two buttons in the .html file after canvas div:
<button id="buttonStartControlAlgorithm" onClick="startControlAlgorithm()">Start Ctrl Alg</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm()">Stop Ctrl Alg</button>
We should add functions that this two buttons will trigger; these two functions just emit the command to the server side:
function startControlAlgorithm () {
socket.emit("startControlAlgorithm");
}
function stopControlAlgorithm () {
socket.emit("stopControlAlgorithm");
}
On the server side in .js file, we should add two variables, one for flagging weather the control algorithm has been started, to prevent multiple triggering of control algorithm and one variable for stopping the setInterval function. These two variables should be defined in the global scope.
var controlAlgorihtmStartedFlag = 0; // flag in global scope to see weather ctrlAlg has been started
var intervalCtrl; // var for setInterval in global space
Next we should put setInterval to our new variable (this is in function startControlAlgorithm () / entire function is printed lower):
intervalCtrl = setInterval(function() {controlAlgorithm(); }, 30); // na 30ms klic
We should also add condition:
if (controlAlgorihtmStartedFlag == 0)
and switch it back:
controlAlgorihtmStartedFlag = 1; // set flag that the algorithm has started
Entire function startControlAlgorithm () should look like:
function startControlAlgorithm () {
if (controlAlgorihtmStartedFlag == 0) {
controlAlgorihtmStartedFlag = 1; // set flag that the algorithm has started
intervalCtrl = setInterval(function() {controlAlgorithm(); }, 30); // na 30ms klic
console.log("Control algorithm started");
}
};
We should add the function for stopping the algorithm:
function stopControlAlgorithm () {
clearInterval(intervalCtrl); // clear the interval of control algorihtm
board.analogWrite(3,0); // write 0 on pwm pin to stop the motor
controlAlgorihtmStartedFlag = 0; // set flag that the algorithm has stopped
};
Within the socket part, we should add the functions to trigger start and stop ctrlAlg:
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();
break;
case "stopControlAlgorithm":
stopControlAlgorithm();
break;
}
}); // end of wss.on code
Next we should test if the buttons are working with UNCOUPLED motor shaft and potentiometer shaft. If everything is working as expected, we should COUPLE the shaft and test the working of the system. The GUI should look like:
Next, we should add some additional log to the client side, this should include error, i.e. difference between desired and actual value and pwm, i.e. control signal.
First, in .js file, we should declare variable pwm as global:
var pwm = 0;
Next, we should send additional piece of data in the json structure, that will be pwm:
wss.broadcast(JSON.stringify({"type": "clientReadValues", "desiredValue": desiredValue, "actualValue": actualValue, "error": (desiredValue - actualValue), "pwm": (pwm).toFixed(0)}));
On the client side in .html file, we should push the data to our log function:
log(potValue1 + "|" + potValue2 + "|" + msg.error + "|" + msg.pwm);
The output with added log is shown in the next figure:
Difference between example13.js and example14.js
Difference between example13.html and example14.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);
}
});
};
wss.sendToLastWs = function sendToLastWs(data) {
if (wss.clients[wss.clients.length-1] !== undefined) { // if websocket exist
if (wss.clients[wss.clients.length-1].readyState === WebSocket.OPEN) { // if it is connected
wss.clients[wss.clients.length-1].send(data); // send to the last connected client - to only one client's websocket
}
}
};
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 + "/example14.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 pwm;
var factor = 0.1; // proportional factor that determines the speed of aproaching toward desired value
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
});
startControlAlgorithm(); // to start control alg.
wss.on('connection', function (ws, req) { // start of wss code
messageJSON = {"type": "message", "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();
break;
case "stopControlAlgorithm":
stopControlAlgorithm();
break;
}
}); // end of wss.on code
}); // end of sockets.on connection
}); // end of board.on(ready)
function controlAlgorithm () {
pwm = factor*(desiredValue-actualValue);
if(pwm > 255) {pwm = 255}; // to limit the value for pwm / positive
if(pwm < -255) {pwm = -255}; // 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));
};
function startControlAlgorithm () {
if (controlAlgorithmStartedFlag == 0) {
controlAlgorithmStartedFlag = 1;
intervalCtrl = setInterval(function(){controlAlgorithm();}, 30); // call the alg. on 30ms
console.log("Control algorithm has been started.");
}
};
function stopControlAlgorithm () {
clearInterval(intervalCtrl); // clear the interval of control algorihtm
board.analogWrite(3, 0);
controlAlgorithmStartedFlag = 0;
console.log("Control algorithm has been stopped.");
};
function sendValues () {
wss.broadcast(JSON.stringify({"type": "clientReadValues", "desiredValue": desiredValue, "actualValue": actualValue, "error": (desiredValue - actualValue), "pwm": (pwm).toFixed(0)}));
};
<!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>
<button id="buttonStartControlAlgorithm" onClick="startControlAlgorithm()">Start Ctrl Alg</button>
<button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm()">Stop Ctrl Alg</button>
<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;
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 "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 startControlAlgorithm () {
messageJSON = {"type": "startControlAlgorithm"};
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>