Uporabniški vmesnik bomo dopolnili z možnostjo zagona različnih kontrolnih algoritmov na strani klienta. Dodali bomo gumba za zagon in zaustavitev P in PID kontrolnega algoritma. Tako želimo primerjati različne algoritme in njihovo učinkovitost.
Na strani klienta bomo dodali izpis statičnega sporočila v <div> s pomočjo funkcije innerHTML:
document.getElementById("divZaStatičniIzpis").innerHTML = "Status: " + sporočilo;Izhodišče za primer16 je primer15.
V .js datoteki bomo dodali funkcijo pošljiStatičnoSporočiloPrekoVtičnika , ki bo omogočila izpis statičnih sporočil v <div> na strani klienta (iz prejšnjega primera imamo deklarirano funkcijo pošljiVrednostPrekoVtičnika):
var pošljiVrednostPrekoVtičnika = function(){}; // spr. za pošiljanje sporočilvar pošljiStatičnoSporočiloPrekoVtičnika = function() {}; // funkcija za pošiljanje statičnega sporočilaV nadaljevanju primer15 spremenimo v primer16
fs.readFile(__dirname + "/primer16.html"Izpišemo dodatno sporočilo v statični <div>:
socket.emit("pošljiStatičnoSporočiloPrekoVtičnika", "Strežnik povezan, plošča pripravljena.")Funkcija startKontrolniAlgoritem ima tokrat argument štKontrolnegaAlg, ki sicer lahko vsebuje tudi json strukturo; v našem primeru za faktorje, ki jih upoštevamo v algoritmih. Spremenljivka štKontrolnegaAlg pove, katerega od algoritmov želimo zagnati:
socket.on("startKontrolniAlgoritem", function(štKontrolnegaAlg){ startKontrolniAlgoritem(štKontrolnegaAlg); });Znotraj vtičnika ("socket") dodamo dve novi funkciji (tokrat z argumentom "value"):
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); }Funkcija kontrolniAlgoritem ima prav tako v argumentu spremenljivko "parametri"; glede na spremenljivko štKontrolnegaAlg poženemo ustrezni algoritem:
function kontrolniAlgoritem (parametri) { if (parametri.štKontrolnegaAlg == 1) { 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 errVsota += err; // vsota napak (kot integral) dErr = err - zadnjiErr; // razlika odstopanj pwm = parametri.Kp1*err + parametri.Ki1*errVsota + parametri.Kd1*dErr; // izraz za PID kontroler (iz enačbe) 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(); } } }Vrstico s pwm v zgornjem zapisu kode spremenimo na naslednji način:
pwm = parametri.Kp1*err + parametri.Ki1*errVsota + parametri.Kd1*dErr; // izraz za PID kontroler (iz enačbe)Funkcija startKontrolniAlgoritem ima prav tako parametre v argumentu:
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)); }}V funkciji setInterval dodamo log in pošiljanje statičnega sporočila:
intervalCtrl = setInterval(function(){kontrolniAlgoritem(parametri);}, 30); // kličemo alg. na 30msconsole.log("Vključen kontrolni algoritem št. " + parametri.štKontrolnegaAlg)pošljiStatičnoSporočiloPrekoVtičnika("Kontrolni algoritem št. " + parametri.štKontrolnegaAlg + " zagnan | " + json2txt(parametri));V funkciji stopKontrolniAlgoritem dodamo izpis obvestila, ki se prenese na stran klienta:
console.log("Kontrolni algoritem zaustavljen.");pošljiStatičnoSporočiloPrekoVtičnika("Kontrolni algoritem zaustavljen.")Dodamo funkcijo za izpis vsebine json strukture v berljivi obliki:
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;}V .html datoteki moramo dodati vnosna polja ter ustrezne funkcije:
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>Deklariramo funkcijo, ki bo poslušala na vtičniku:
socket.on("statičnoSporočiloKlientu", function(sporočilo){ document.getElementById("divZaStatičniIzpis").innerHTML = "Status: " + sporočilo;});Funkcija mora imeti na koncu dodano št. 1 oz. št. 2(startKontrolniAlgoritem1 in startKontrolniAlgoritem2):
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}Uporabniški vmesnik je prikazan na naslednji sliki. Izbiramo lahko med P in PID algoritmom in podamo ustrezne parametre:
var http = require("http").createServer(handler); // ob zahtevi req -> handlervar 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 + "/primer16.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 8080var želenaVrednost = 0; // želeno vrednost postavimo na 0var dejanskaVrednost = 0; // dejansko vrednost postavimo na 0var faktor =0.4; // faktor, ki določa hitrost doseganja želenega stanjavar pwm = 0;// Spremenljivke PID algoritmavar Kp = 0.8; // proporcionalni faktorvar Ki = 0.008; // integralni faktorvar Kd = 0.15; // diferencialni faktorvar err = 0; // errorvar errVsota = 0; // vsota napakvar dErr = 0; // diferenca napakvar zadnjiErr = 0; // da obdržimo vrednost prejšnje napakevar kontrolniAlgoritemVključen = 0; // spremenljivka, ki določa ali je ctrl. alg. vključenvar intervalCtrl; // spremenljivka za setInterval v globalnem prostoruconsole.log("Zagon sistema"); // izpis sporočila o zagonuvar pošljiVrednostPrekoVtičnika = function(){}; // spr. za pošiljanje sporočilvar pošljiStatičnoSporočiloPrekoVtičnika = function() {}; // funkcija za pošiljanje statičnega sporočilaboard.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) { 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 errVsota += err; // vsota napak (kot integral) dErr = err - zadnjiErr; // razlika odstopanj pwm = parametri.Kp1*err + parametri.Ki1*errVsota + parametri.Kd1*dErr; // izraz za PID kontroler (iz enačbe) 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; 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 });};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="cv" 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 x1var y1 = new Array(); // polje za spremenljivko y1var x2 = new Array(); // polje za spremenljivko x2var y2 = new Array(); // polje za spremenljivko y2var canvas;var ctx;var potVrednost1; // vrednost prvega potenciometravar potVrednost2; // vrednost drugega potenciometravar divZaIzpis = document.getElementById("divZaIzpis"); // var za div el.var štVrsticPredPomikom = 10;var števecIzpisanihVrstic = 0;var socket = io.connect("192.168.1.136:8080"); // povezava na strež.function load() { canvas = document.getElementById("cv"); ctx = canvas.getContext("2d"); // izrišemo prvo časovno vrsto za želene vrednosti ctx.lineWidth = "1"; ctx.strokeStyle = "#ff0000"; for (var i = 0; i<200; i++) { x1[i] = i; // x1 gre od 0, 1, ... 199 y1[i] = 0; // za y podamo vrednost 0 } ctx.beginPath(); // za začetek izrisa črte for (var i=0; i<200; i++) { ctx.lineTo(x1[i],y1[i]); } ctx.stroke(); // izrišemo drugo časovno vrsto za dejanske vrednosti ctx.lineWidth = "1"; ctx.strokeStyle = "#00ff00"; for (var i = 0; i<200; i++) { x2[i] = i; // x2 gre od 0, 1, ... 199 y2[i] = 0; // za y podamo vrednost 0 } ctx.beginPath(); // za začetek izrisa črte for (var i=0; i<200; i++) { ctx.lineTo(x2[i],y2[i]); } ctx.stroke(); }socket.on("klientBeriVrednosti", function(value){ potVrednost1 = value.želenaVrednost; // žel. vr. iz strežnika potVrednost2 = value.dejanskaVrednost; // dej. vr. iz strežnika // Graf izris ********************************************************** ctx.clearRect(0, 0, canvas.width, canvas.height); // brišemo platno // izris prve črte ctx.strokeStyle = "#ff0000"; ctx.beginPath(); // za začetek izrisa črte y1.splice(0, 1); // na mestu 0 v polju y1 brišemo eno vrednost y1[199] = potVrednost1; // novo vrednst pa dodamo na koncu polja for (var i=0; i<200; i++) { ctx.lineTo(x1[i], (100 - (y1[i] / 1023) * 100)); // izris vrednosti } ctx.stroke(); // za izris črte // izris druge črte ctx.strokeStyle = "#00ff00"; ctx.beginPath(); // za začetek izrisa črte y2.splice(0, 1); // na mestu 0 v polju y2 brišemo eno vrednost y2[199] = potVrednost2; // novo vrednst pa dodamo na koncu polja for (var i=0; i<200; i++) { ctx.lineTo(x2[i], (100 - (y2[i] / 1023) * 100)); // izris vrednosti } ctx.stroke(); // za izris črte // Graf izris ********************************************************** // izris legende ctx.strokeStyle = "#ff0000"; ctx.font = "11px Arial"; ctx.fillText("Želena", 70, 10); ctx.beginPath(); ctx.lineTo(50,6); ctx.lineTo(65,6); ctx.stroke(); ctx.strokeStyle = "#00ff00"; ctx.font = "11px Arial"; ctx.fillText("Dejanska", 140, 10); ctx.beginPath(); ctx.lineTo(120,6); ctx.lineTo(135,6); ctx.stroke(); 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");}function log(sporočilo) { var node=document.createElement("tr"); // ustvarimo spremenljivko kot vrstico v tabeli "table row" (tr) var textnode=document.createTextNode(števecIzpisanihVrstic + " | " + sporočilo); // ustvarimo element z besedilom in dodamo števec node.appendChild(textnode); // dodamo besedilo k "node", t.j. k "table row" divZaIzpis.insertBefore(node, divZaIzpis.childNodes[0]); // vstavimo v spremenljivko "node" if (števecIzpisanihVrstic > štVrsticPredPomikom-1) { // če je vrstic več, kot je določeno -> pričnemo s pomikom ("scroll") divZaIzpis.removeChild(divZaIzpis.childNodes[štVrsticPredPomikom]); // odstranimo najstarejši izpis } števecIzpisanihVrstic++; // povečamo števec izpisov}</script></body></html>