Z namenom primerjave različnih algoritmov dodajmo funkcijo, ki bo opredelila učinkovitost posameznega kontrolnega algoritma kot integral absolutne napake glede na čas.
Izgled uporabniškega vmesnika:
var http = require("http").createServer(handler); // ob zahtevi req -> handler
var firmata = require("firmata");
var fs = require("fs"); // knjižnjica za delo z datotekami (File System fs)
var io = require("socket.io").listen(http); // knjiž. za komunik. prek socket-a
console.log("Priklop Arduina");
var board = new firmata.Board("/dev/ttyACM0", function(){
console.log("Aktiviramo analogni pin 0");
board.pinMode(0, board.MODES.ANALOG);
console.log("Aktiviramo analogni pin 1");
board.pinMode(1, board.MODES.ANALOG);
console.log("Aktiviramo pin 2");
board.pinMode(2, board.MODES.OUTPUT); // pin za smer na H-mostu
console.log("Aktiviramo pin 3");
board.pinMode(3, board.MODES.PWM); // Pulse Width Modulation - hitrost
console.log("Aktiviramo pin 8");
board.pinMode(8, board.MODES.INPUT); // pin za HW gumb
});
function handler(req, res) {
fs.readFile(__dirname + "/primer19.html",
function(err, data) {
if (err) {
res.writeHead(500, {"Content-Type": "text/plain"});
return res.end("Napaka pri nalaganju html strani!");
}
res.writeHead(200);
res.end(data);
});
}
http.listen(8080); // strežnik bo poslušal na vratih 8080
var želenaVrednost = 0; // želeno vrednost postavimo na 0
var dejanskaVrednost = 0; // dejansko vrednost postavimo na 0
var faktor =0.4; // faktor, ki določa hitrost doseganja želenega stanja
var pwm = 0;
// Spremenljivke PID algoritma
var Kp = 0.8; // proporcionalni faktor
var Ki = 0.008; // integralni faktor
var Kd = 0.15; // diferencialni faktor
var err = 0; // error
var errVsota = 0; // vsota napak
var dErr = 0; // diferenca napak
var zadnjiErr = 0; // da obdržimo vrednost prejšnje napake
var KpE = 0; // množenje Kp x napaka
var KiIedt = 0; // množenje Ki x integral napake
var KdDe_dt = 0; // množenje Kd x diferencial napake i.e. Derror/dt
var vsotaAbsOdstopanj = 0; // vsota aboslutnih vrednosti odstopanj
var kontrolniAlgoritemVključen = 0; // spremenljivka, ki določa ali je ctrl. alg. vključen
var intervalCtrl; // spremenljivka za setInterval v globalnem prostoru
console.log("Zagon sistema"); // izpis sporočila o zagonu
var pošljiVrednostPrekoVtičnika = function(){}; // spr. za pošiljanje sporočil
var pošljiStatičnoSporočiloPrekoVtičnika = function() {}; // funkcija za pošiljanje statičnega sporočila
board.on("ready", function(){
console.log("Plošča pripravljena");
board.analogRead(0, function(value){
želenaVrednost = value; // neprekinjeno branje pina A0
});
board.analogRead(1, function(value){
dejanskaVrednost = value; // neprekinjeno branje pina A1
});
//startKontrolniAlgoritem(); // poženemo kontrolni algoritem
io.sockets.on("connection", function(socket){
socket.emit("pošljiStatičnoSporočiloPrekoVtičnika", "Strežnik povezan, plošča pripravljena.")
setInterval(pošljiVrednosti, 40, socket); // na 40ms pošlj. vred.
socket.on("startKontrolniAlgoritem", function(štKontrolnegaAlg){
startKontrolniAlgoritem(štKontrolnegaAlg);
});
socket.on("stopKontrolniAlgoritem", function(){
stopKontrolniAlgoritem();
});
pošljiVrednostPrekoVtičnika = function (value) {
io.sockets.emit("sporočiloKlientu", value);
}
pošljiStatičnoSporočiloPrekoVtičnika = function (value) {
io.sockets.emit("statičnoSporočiloKlientu", value);
}
}); // konec socket.on("connection")
}); // konec board.on("ready")
function kontrolniAlgoritem (parametri) {
if (parametri.štKontrolnegaAlg == 1) {
err = želenaVrednost - dejanskaVrednost; // odstopanje ali error
vsotaAbsOdstopanj += Math.abs(err);
pwm = parametri.faktor*(želenaVrednost-dejanskaVrednost);
if (pwm > 255) {pwm = 255}; // omejimo vrednost pwm na 255
if (pwm < -255) {pwm = -255}; // omejimo vrednost pwm na -255
if (pwm > 0) {board.digitalWrite(2,0)}; // določimo smer če je > 0
if (pwm < 0) {board.digitalWrite(2,1)}; // določimo smer če je < 0
board.analogWrite(3, Math.abs(pwm)); // zapišemo abs vrednost na pin 3
if (dejanskaVrednost < 150 || dejanskaVrednost > 910) {
stopKontrolniAlgoritem();
}
}
if (parametri.štKontrolnegaAlg == 2) {
err = želenaVrednost - dejanskaVrednost; // odstopanje ali error
vsotaAbsOdstopanj += Math.abs(err);
errVsota += err; // vsota napak (kot integral)
dErr = err - zadnjiErr; // razlika odstopanj
// za pošliljanje na stran klienta
KpE=parametri.Kp1*err;
KiIedt=parametri.Ki1*errVsota;
KdDe_dt=parametri.Kd1*dErr;
pwm = KpE + KiIedt + KdDe_dt; // uporabimo gornje izraze
zadnjiErr = err; // shranimo vrednost za naslednji cikel za oceno odvoda
if (pwm > 255) {pwm = 255}; // omejimo vrednost pwm na 255
if (pwm < -255) {pwm = -255}; // omejimo vrednost pwm na -255
if (pwm > 0) {board.digitalWrite(2,0)}; // določimo smer če je > 0
if (pwm < 0) {board.digitalWrite(2,1)}; // določimo smer če je < 0
board.analogWrite(3, Math.abs(pwm)); // zapišemo abs vrednost na pin 3
if (dejanskaVrednost < 200 || dejanskaVrednost > 850) {
stopKontrolniAlgoritem();
}
}
}
function startKontrolniAlgoritem (parametri) {
if (kontrolniAlgoritemVključen == 0) {
kontrolniAlgoritemVključen = 1;
intervalCtrl = setInterval(function(){kontrolniAlgoritem(parametri);}, 30); // kličemo alg. na 30ms
console.log("Vključen kontrolni algoritem št. " + parametri.štKontrolnegaAlg)
pošljiStatičnoSporočiloPrekoVtičnika("Kontrolni algoritem št. " + parametri.štKontrolnegaAlg + " zagnan | " + json2txt(parametri));
}
}
function stopKontrolniAlgoritem () {
clearInterval(intervalCtrl); // brišemo interval klica kontrolnega algoritma
board.analogWrite(3, 0);
kontrolniAlgoritemVključen = 0;
err = 0; // odstopanje ali error
errVsota = 0; // vsota napak (kot integral)
dErr = 0; // razlika odstopanj
pwm = 0; // izraz za PID kontroler (iz enačbe)
zadnjiErr = 0; // shranimo vrednost za naslednji cikel za oceno odvoda
console.log("Kontrolni algoritem zaustavljen.");
pošljiStatičnoSporočiloPrekoVtičnika("Kontrolni algoritem zaustavljen.");
};
function pošljiVrednosti(socket) {
socket.emit("klientBeriVrednosti",
{
"želenaVrednost": želenaVrednost,
"dejanskaVrednost": dejanskaVrednost,
"pwm": pwm,
"err": err,
"errVsota": errVsota,
"dErr": dErr,
"KpE": KpE,
"KiIedt": KiIedt,
"KdDe_dt": KdDe_dt,
"vsotaAbsOdstopanj": vsotaAbsOdstopanj
});
};
function json2txt(obj) // funkcija za izpis json imen in vrednosti
{
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>Kontrolni sistem</title>
</head>
<body onload="load()">
Kontrolni sistem P / PID
<br>
<canvas id="canvas1" width ="200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<canvas id="canvas4" width = "200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<canvas id="canvas7" width = "200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<br>
<canvas id="canvas2" width ="200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<canvas id="canvas5" width = "200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<br>
<canvas id="canvas3" width = "200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<canvas id="canvas6" width = "200" height = "100" style="border: 1px dashed #00c3c3;"></canvas>
<p></p>
faktor: <input id="faktor" value="0.5" size="5" />
<button id="startKontrolniAlgoritem1" onClick="startKontrolniAlgoritem1();">Start Ctrl Alg</button>
<button id="stopKontrolniAlgoritem" onClick="stopKontrolniAlgoritem();">Stop Ctrl Alg</button>
<p></p>
Kp: <input id="Kp1" value="0.5" size = "5" />
Ki: <input id="Ki1" value="0.0055" size = "5" />
Kd: <input id="Kd1" value="0.25" size = "5" />
<button id="startKontrolniAlgoritem2" onClick="startKontrolniAlgoritem2();">Start Ctrl Alg2</button>
<button id="stopKontrolniAlgoritem" onClick="stopKontrolniAlgoritem();">Stop Kontrolni Alg</button>
<p></p>
<div id="divZaStatičniIzpis">Status: ×</div>
<p></p>
<div id="divZaIzpis"></div>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var x1 = new Array(); // polje za spremenljivko x1
var y1 = new Array(); // polje za spremenljivko y1
var x2 = new Array(); // polje za spremenljivko x2
var y2 = new Array(); // polje za spremenljivko y2
var canvas;
var ctx;
var potVrednost1; // vrednost prvega potenciometra
var potVrednost2; // vrednost drugega potenciometra
var divZaIzpis = document.getElementById("divZaIzpis"); // var za div el.
var štVrsticPredPomikom = 10;
var števecIzpisanihVrstic = 0;
var graf1; // spremenljivka za objekt razreda Graf
var graf2; // spremenljivka za objekt razreda Graf
var graf3; // spremenljivka za objekt razreda Graf
var graf4; // spremenljivka za objekt razreda Graf
var graf5; // spremenljivka za objekt razreda Graf
var graf6; // spremenljivka za objekt razreda Graf
var graf7; // spremenljivka za objekt razreda Graf
var divLog; // spremenljivka za objekt izpisa
var socket = io.connect("192.168.1.136:8080"); // povezava na strež.
class Graf {
constructor(canvasId, minGraphX, maxGraphX, minGraphY, maxGraphY, color, legend, axisDescription) { // pri konstruktorju moramo podati ID platna, ki ga sicer ustvarimo v html-ju
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext("2d");
this.canvasWidth = this.canvas.width; // mind capital W at Width
this.canvasHeight = this.canvas.height; // mind capital H at Height
this.x = new Array(); // create new Array x
this.y = new Array();
this.rangeX = maxGraphX - minGraphX;
this.rangeY = maxGraphY - minGraphY;
// create y array (size) according to the color vector (could have multiple rows i.e. 2d)
for( var i=0; i<color.length; i++ ) {
this.y.push([]); // example of three row array init. would be: this.y = [[],[],[]];
}
this.minGraphX = minGraphX;
this.maxGraphX = maxGraphX;
this.minGraphY = minGraphY;
this.maxGraphY = maxGraphY;
this.color = color; // color of the graph
this.legend = legend;
this.axisDescription = axisDescription;
// fill x vector; vector y is filled in real-time
for (var i=0; i<this.maxGraphX+1; i++) {
this.x[i] = i; // values for the x coordinate; 0, 1, 2, ...
}
}
addValueOrCutAndAdd(yValue) {
if (this.y[0].length == this.maxGraphX+1) { // if canvas size is 10x10 we have 11x11 points (starting with 0 and ending with 10)
for (var i = 0; i < yValue.length; i++) { // v zanki gremo po polju yInput in na mestu 0 eno vrednost odrežemo, na koncu pa eno mesto dodamo - zapišemo novo vrednost yInput
this.y[i].splice(0, 1); // on the position 0 in the vector y we cut one value
this.y[i][this.maxGraphX] = yValue[i]; // at the end of the array the new value is added
}
}
else {
for (var i = 0; i < yValue.length; i++) { // z zanko gremo po vseh vrsticah za matrike y
this.y[i].push(yValue[i]); // if the array is not yet full, we push the new value in the array / vrednost v oklepaju [] t.j. index je za ena večji; npr., če imamo eno vrednost je indeks [0], length pa 1
}
}
}
plot(yValue) {
this.addValueOrCutAndAdd(yValue);
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // clear the canvas
for (var i=0; i < yValue.length; i++) { // zanka, ki gre po vrsticah y matrike
this.ctx.strokeStyle = this.color[i]; // determine color
this.ctx.beginPath(); // for the start of the line
for (var j=0; j<this.y[0].length; j++) {
this.ctx.lineTo(this.x[j]/this.rangeX*this.canvasWidth, (this.canvasHeight - ((this.y[i][j]-this.minGraphY)/this.rangeY) * this.canvasHeight)); // for y values we multiply with canas height, eg. 0.25 * 100 = 25
}
this.ctx.stroke();
}
// add legend
for( var i=0; i<this.legend.length; i++ ) { // legend and color should be of the same size
this.ctx.font = "11px Arial";
this.ctx.fillText(this.legend[i], 49+i*54, 10);
this.ctx.strokeStyle = this.color[i];
this.ctx.beginPath(); // beginning of the short line for the legend
this.ctx.lineTo(37+i*54, 6);
this.ctx.lineTo(46+i*54, 6);
this.ctx.stroke();
}
// add axis descritions
this.ctx.fillText("<-" + this.axisDescription[0] + "|" + this.axisDescription[1] + "->", 150, 95)
this.ctx.fillText(this.axisDescription[2], 5, 95);
this.ctx.fillText(this.axisDescription[3], 5, 11);
}
}
class LogDiv {
constructor(divId, numberOfLinesBeforeScroll) {
this.divElement = document.getElementById(divId); // name of div where values will be printed
this.numberOfLinesBeforeScroll = numberOfLinesBeforeScroll; // number of lines which print before scroll
this.linesPrintCounter = 0;
}
log(msg) { // function for printout of the messages with scroll functionality
var node=document.createElement("tr"); // we create the variable node as the a table row (table row)
var textnode=document.createTextNode(this.linesPrintCounter + " | " + msg); // we create element with the text
node.appendChild(textnode); // adding text to "node", t.j. vrstici tabele
this.divElement.insertBefore(node, this.divElement.childNodes[0]); // inserting into variable node
if (this.linesPrintCounter > this.numberOfLinesBeforeScroll-1) { // if the lines are more than limit -> start with scroll
this.divElement.removeChild(this.divElement.childNodes[this.numberOfLinesBeforeScroll], this.divElement.childNodes[this.numberOfLinesBeforeScroll]); // we remove the oldest printout
}
this.linesPrintCounter++; // increasing the number of printouts
}
}
function load() {
// ustvarimo objekt za izpis vrednosti (log)
divLog = new LogDiv("divZaIzpis", 10);
graf1 = new Graf("canvas1", 0, 200, 0, 1023, ["red", "green"], ["Želena", "Dejanska"], ["0", "200", "0", "1023"]); // argumenti: Arg1: canvasId, Arg2: maxX, Arg3: maxY, Arg4: [vektor barv]; ta določi velikost matrike yValue (če podamo eno barvo, npr. ["red"], predvidimo samo eno časovno vrsto, ["red", "green", "blue"] -> tri časovne vrste)
graf2 = new Graf("canvas2", 0, 200, -255, 255, ["red"], ["PWM"], ["0", "200", "-255", "255"]);
graf3 = new Graf("canvas3", 0, 200, -100, 100, ["red", "green", "blue"], ["KpE", "KiIedt", "KdDe_dt"], ["0", "200", "-100", "100"]);
graf4 = new Graf("canvas4", 0, 200, -1023, 1023, ["red"], ["Error"], ["0", "200", "-1023", "1023"]);
graf5 = new Graf("canvas5", 0, 200, -10000, 10000, ["red"], ["Integ(Err*dt)"], ["0", "200", "-10000", "10000"]);
graf6 = new Graf("canvas6", 0, 200, -150, 150, ["red"], ["dError/dt"], ["0", "200", "-150", "150"]);
graf7 = new Graf("canvas7", 0, 2000, 0, 500000, ["red"], ["Int(Abs(Error)dt)"], ["0", "2000", "0", "0.5M"]);
}
socket.on("klientBeriVrednosti", function(value){
potVrednost1 = value.želenaVrednost; // žel. vr. iz strežnika
potVrednost2 = value.dejanskaVrednost; // dej. vr. iz strežnika
graf1.plot([potVrednost1, potVrednost2]); // graf želene in dejanske vrednosti
graf2.plot([value.pwm]);
// narišemo sredinsko črto na grafu 2.
graf2.ctx.strokeStyle = "#add8e6";
graf2.ctx.beginPath(); // sredinska črta pri 0
graf2.ctx.lineTo(0, 50); // začetna točka
graf2.ctx.lineTo(200, 50); // končna točka
graf2.ctx.stroke();
graf3.plot([value.KpE, value.KiIedt, value.KdDe_dt]);
graf4.plot([value.err]); // graf za P del
graf5.plot([value.errVsota]); // graf za I del
graf6.plot([value.dErr]); // graf za D del
graf7.plot([value.vsotaAbsOdstopanj]); // graf za določitev učinkovitosti algoritmov
graf7.ctx.fillText(parseInt(value.vsotaAbsOdstopanj),70,27);
divLog.log(value.želenaVrednost + "|" + value.dejanskaVrednost + "|" + (value.želenaVrednost-value.dejanskaVrednost) + "|" + (value.pwm).toFixed(0));
});
socket.on("statičnoSporočiloKlientu", function(sporočilo){
document.getElementById("divZaStatičniIzpis").innerHTML = "Status: " + sporočilo;
});
function startKontrolniAlgoritem1() {
stopKontrolniAlgoritem(); // za vsak primer algoritem najprej zaustavimo
faktor = document.getElementById("faktor").value; // beremo vrednost faktorja iz vnosnega polja
socket.emit("startKontrolniAlgoritem", {"štKontrolnegaAlg": 1, "faktor": faktor}); /// beremo vrednost iz vnosnega polja
}
function startKontrolniAlgoritem2() {
stopKontrolniAlgoritem(); // za vsak primer algoritem najprej zaustavimo
Kp1 = document.getElementById("Kp1").value; // beremo vrednost iz vnosnega polja
Ki1 = document.getElementById("Ki1").value; // beremo vrednost iz vnosnega polja
Kd1 = document.getElementById("Kd1").value; // beremo vrednost iz vnosnega polja
socket.emit("startKontrolniAlgoritem", {"štKontrolnegaAlg": 2, "Kp1": Kp1, "Ki1": Ki1, "Kd1": Kd1}); // pošljemo json z vrednostmi na strežnik
}
function stopKontrolniAlgoritem() {
socket.emit("stopKontrolniAlgoritem");
}
</script>
</body>
</html>