From f9cb54a1cf9e96211c3b8d2bea45f7ca4b344ca7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 4 Dec 2024 17:46:40 +0100 Subject: [PATCH 001/107] start db connection --- src/Python/flask/web/app.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 3fc45f6..cb9978e 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,5 +1,6 @@ from flask import Flask, request, render_template, jsonify import paho.mqtt.client as mqtt +import mariadb app = Flask(__name__) @@ -17,6 +18,15 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use +conn = mariadb.connect( + host='127.0.0.1', + port= 3306, + user='admin', + password='kobuki', + database='kobuki') +cur = conn.cursor() + + @app.route('/') def index(): return render_template('index.html') @@ -50,7 +60,9 @@ def phpmyadmin_passthrough(path): # Laat Apache deze route direct afhandelen return "", 404 - +@app.route("/index") +def index(): + return "Connected to database" if __name__ == '__main__': app.run(debug=True, port=5000) From cd374dab812d30849ab2143f107a4357bee50744 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 5 Dec 2024 13:40:34 +0100 Subject: [PATCH 002/107] rename index route to database and update response message --- src/Python/flask/web/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index cb9978e..1eb18e6 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -60,8 +60,8 @@ def phpmyadmin_passthrough(path): # Laat Apache deze route direct afhandelen return "", 404 -@app.route("/index") -def index(): +@app.route("/database") +def database(): return "Connected to database" if __name__ == '__main__': From 14a62c022ca5bef4f1b3783fe22af3a88adfcfda Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Sat, 7 Dec 2024 22:11:03 +0100 Subject: [PATCH 003/107] start van visie op etische aspecten --- teamdocumentatie/Ishak/etische_aspecten.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 teamdocumentatie/Ishak/etische_aspecten.md diff --git a/teamdocumentatie/Ishak/etische_aspecten.md b/teamdocumentatie/Ishak/etische_aspecten.md new file mode 100644 index 0000000..77062a6 --- /dev/null +++ b/teamdocumentatie/Ishak/etische_aspecten.md @@ -0,0 +1,9 @@ +# Etische aspecten van het project + +## Visie op de ethische aspecten van het Kobuki-project + +Etische aspecten zijn heel belangrijk in het project, al ben ik wel van mening dat je niet alles kan voorkomen en ook kan waarborgen. Als je bijvoorbeeld kijkt naar het gedeelte privacy, dan is het heel moeilijk om te kijken wat je gaat doen met die gegevens. Ik ga een camera gebruiken op de robot om zo te kijken +waar de robot is en wat hij allemaal ziet. Als de robot in een brandende huis komt en dan een persoon ziet, is het wel belangrijk om die persoon goed te kunnen zien. Je zou dan niet kunnen zeggen dat je die persoon bijvoorbeeld moet vervagen, want je moet wel kunnen zien wat de status is van die persoon. +Ook is het dan belangrijk om te kijken wat je met die gegevens gaat doen, ga je ze opslaan voor eventuele later gebruik of verwijder je ze direct. Het is heel lastig te bepalen wanneer je op zo een moment privacy schendt. Ik vind dat je de betrouwbaarheid van de robot wel moet waarborgen, +want als ik de robot in een brandend huis stuur en hij valt uit, dan kan dat heel gevaarlijk zijn voor de persoon die in dat huis zit. Daar vind ik dat je meer rekening mee moet houden dan met de privacy van de persoon. Het is de bedoeling dat de robot hulpmedewerkers gaat helpen en niet hun werk moeilijker maakt. +Als meerdere hulpmedewerkers de robot gaan gebruiken en het word een soort van standaard, dan is het wel belangrijk dat de robot betrouwbaar is en dat je erop kan vertrouwen. Het gaat immers om mensenlevens en dat is wel het belangrijkste. \ No newline at end of file From f0637f4ba834413b358c09392cf41b44727fcd75 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Mon, 9 Dec 2024 10:13:53 +0100 Subject: [PATCH 004/107] final etische aspecten --- teamdocumentatie/Ishak/etische_aspecten.md | 22 +++++++++++++++++--- teamdocumentatie/Ishak/etische_aspecten.pdf | Bin 0 -> 33270 bytes 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 teamdocumentatie/Ishak/etische_aspecten.pdf diff --git a/teamdocumentatie/Ishak/etische_aspecten.md b/teamdocumentatie/Ishak/etische_aspecten.md index 77062a6..09dad7a 100644 --- a/teamdocumentatie/Ishak/etische_aspecten.md +++ b/teamdocumentatie/Ishak/etische_aspecten.md @@ -2,8 +2,24 @@ ## Visie op de ethische aspecten van het Kobuki-project -Etische aspecten zijn heel belangrijk in het project, al ben ik wel van mening dat je niet alles kan voorkomen en ook kan waarborgen. Als je bijvoorbeeld kijkt naar het gedeelte privacy, dan is het heel moeilijk om te kijken wat je gaat doen met die gegevens. Ik ga een camera gebruiken op de robot om zo te kijken +Etische aspecten zijn heel belangrijk in het project, al ben ik wel van mening dat je niet alles kan voorkomen en ook kan waarborgen. + +## Privacy + +Als je bijvoorbeeld kijkt naar het gedeelte privacy, dan is het heel moeilijk om te kijken wat je gaat doen met die gegevens. Ik ga een camera gebruiken op de robot om zo te kijken waar de robot is en wat hij allemaal ziet. Als de robot in een brandende huis komt en dan een persoon ziet, is het wel belangrijk om die persoon goed te kunnen zien. Je zou dan niet kunnen zeggen dat je die persoon bijvoorbeeld moet vervagen, want je moet wel kunnen zien wat de status is van die persoon. -Ook is het dan belangrijk om te kijken wat je met die gegevens gaat doen, ga je ze opslaan voor eventuele later gebruik of verwijder je ze direct. Het is heel lastig te bepalen wanneer je op zo een moment privacy schendt. Ik vind dat je de betrouwbaarheid van de robot wel moet waarborgen, +Ook is het dan belangrijk om te kijken wat je met die gegevens gaat doen, ga je ze opslaan voor eventuele later gebruik of verwijder je ze direct. Het is heel lastig te bepalen wanneer je op zo een moment privacy schendt. + +## Betrouwbaarheid + +Ik vind dat je de betrouwbaarheid van de robot wel moet waarborgen, want als ik de robot in een brandend huis stuur en hij valt uit, dan kan dat heel gevaarlijk zijn voor de persoon die in dat huis zit. Daar vind ik dat je meer rekening mee moet houden dan met de privacy van de persoon. Het is de bedoeling dat de robot hulpmedewerkers gaat helpen en niet hun werk moeilijker maakt. -Als meerdere hulpmedewerkers de robot gaan gebruiken en het word een soort van standaard, dan is het wel belangrijk dat de robot betrouwbaar is en dat je erop kan vertrouwen. Het gaat immers om mensenlevens en dat is wel het belangrijkste. \ No newline at end of file + +## Impact op hulpverleners & maatschappij + +Als meerdere hulpmedewerkers de robot gaan gebruiken en het word een soort van standaard, dan is het wel belangrijk dat de robot betrouwbaar is en dat je erop kan vertrouwen. Het gaat immers om mensenlevens en dat is wel het belangrijkste. Het is uiteindelijk de bedoeling dat de robot hulpverleners zal helpen en niet hun werk lastiger moet maken. Als de robot een standaard hulpmiddel wordt moet hij wel gebruiksvriendelijk zijn en goed kunnen helpen. De robot moet ook zo goed mogelijk werken om zo de vertrouwen te behouden van de mensen. Als de robot fouten blijft maken en niet betrouwbaar is zullen minder mensen het gebruiken. Ik vind dan ook dat de gebruik van de robot heel transparant moet zijn. Hoe word de robot aangestuurd, hoe vergelijkt hij situaties en hoe hij daarmee omgaat. +Als je daar al heel duidelijk in bent bouw je al wat sneller vertrouwen van de mensen op. + +Ik vind dat in dit project de ethische aspecten heel belangrijk zijn en dat je daar ook rekening mee moet houden. +Bij betrouwbaarheid en de impact die de robot kan hebben op de maatschappij en de hulpverleners moet je wel goed over nadenken. +Je werkt immers met mensenlevens en dat is wel het belangrijkste. Privacy is ook heel belangrijk, maar ik vind dat je daar wel wat soepeler mee om kan gaan. \ No newline at end of file diff --git a/teamdocumentatie/Ishak/etische_aspecten.pdf b/teamdocumentatie/Ishak/etische_aspecten.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ec21a8c6acb558c63bfa64f3330f1fbe40a47908 GIT binary patch literal 33270 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≺wmo0_^5%deN%CNH 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 Date: Mon, 9 Dec 2024 10:14:10 +0100 Subject: [PATCH 005/107] removed mariadb --- src/Python/flask/web/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 1eb18e6..85b5fb3 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,6 +1,6 @@ from flask import Flask, request, render_template, jsonify import paho.mqtt.client as mqtt -import mariadb +# import mariadb app = Flask(__name__) @@ -18,13 +18,13 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use -conn = mariadb.connect( - host='127.0.0.1', - port= 3306, - user='admin', - password='kobuki', - database='kobuki') -cur = conn.cursor() +# conn = mariadb.connect( +# host='127.0.0.1', +# port= 3306, +# user='admin', +# password='kobuki', +# database='kobuki') +# cur = conn.cursor() @app.route('/') From 9689d7010433cb3b18449bc0581c51f079d5904a Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Mon, 9 Dec 2024 13:12:51 +0100 Subject: [PATCH 006/107] Uncommented mariadb conn --- src/Python/flask/web/app.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 85b5fb3..f187235 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,6 +1,6 @@ from flask import Flask, request, render_template, jsonify import paho.mqtt.client as mqtt -# import mariadb +import mariadb app = Flask(__name__) @@ -18,13 +18,12 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use -# conn = mariadb.connect( -# host='127.0.0.1', -# port= 3306, -# user='admin', -# password='kobuki', -# database='kobuki') -# cur = conn.cursor() +conn = mariadb.connect( + host='127.0.0.1', + user='admin', + password='kobuki', + database='kobuki') +cur = conn.cursor() @app.route('/') From 3bb40d592965bf1ea726076ee8a328859e1275e4 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Mon, 9 Dec 2024 14:59:42 +0100 Subject: [PATCH 007/107] Replace MariaDB connection with Flask-MySQLdb integration --- src/Python/flask/web/app.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index f187235..f66a3ee 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,6 +1,6 @@ from flask import Flask, request, render_template, jsonify import paho.mqtt.client as mqtt -import mariadb +from flask_mysqldb import MySQL app = Flask(__name__) @@ -18,12 +18,12 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use -conn = mariadb.connect( - host='127.0.0.1', - user='admin', - password='kobuki', - database='kobuki') -cur = conn.cursor() +app.config['MYSQL_HOST'] = 'localhost' +app.config["MYSQL_USER"] = 'admin' +app.config["MYSQL_PASSWORD"] = 'kobuki' +app.config["MYSQL_DB"] = 'kobuki' + +mysql = MySQL(app) @app.route('/') @@ -61,7 +61,8 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): - return "Connected to database" + cur = mysql.connection.cursor() + cur.execute("SELECT * FROM kobuki") if __name__ == '__main__': app.run(debug=True, port=5000) From 93167e67f6c319bd40484d4f8343ada23a566e47 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Mon, 9 Dec 2024 15:01:29 +0100 Subject: [PATCH 008/107] changed table name --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index f66a3ee..fa3e2aa 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -62,7 +62,7 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): cur = mysql.connection.cursor() - cur.execute("SELECT * FROM kobuki") + cur.execute("SELECT * FROM kobuki_data") if __name__ == '__main__': app.run(debug=True, port=5000) From b2432ab9cd26c0b29391ad009e571a5d80f49e88 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 11 Dec 2024 14:39:47 +0100 Subject: [PATCH 009/107] removed execute to test if page works --- src/Python/flask/web/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index fa3e2aa..7f21317 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -62,7 +62,8 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): cur = mysql.connection.cursor() - cur.execute("SELECT * FROM kobuki_data") + # cur.execute("SELECT * FROM kobuki_data") + return "hi",404 if __name__ == '__main__': app.run(debug=True, port=5000) From e77aa4b2dc338c508172bfeed0a6167771d41890 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 11 Dec 2024 14:54:33 +0100 Subject: [PATCH 010/107] try to get data from database --- src/Python/flask/web/app.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 7f21317..8fe6d4d 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -61,9 +61,16 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): - cur = mysql.connection.cursor() - # cur.execute("SELECT * FROM kobuki_data") - return "hi",404 + try: + cur = mysql.connection.cursor() + cur.execute("SELECT * FROM kobuki_data") + rows = cur.fetchall() # Haal alle rijen op uit het resultaat + cur.close() + + return str(rows) + except Exception as e: + print(f"Database error: {e}") + return "Er is een fout opgetreden bij het ophalen van de databasegegevens.", 500 if __name__ == '__main__': app.run(debug=True, port=5000) From 5c4a0f1e9d93c0178aac63ada177fbaa68b094e2 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 11 Dec 2024 18:45:22 +0100 Subject: [PATCH 011/107] busy with sending command to database --- docs/scrum/retrospective/retro_sprint_4.md | 1 + src/Python/flask/web/app.py | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 docs/scrum/retrospective/retro_sprint_4.md diff --git a/docs/scrum/retrospective/retro_sprint_4.md b/docs/scrum/retrospective/retro_sprint_4.md new file mode 100644 index 0000000..ec560ba --- /dev/null +++ b/docs/scrum/retrospective/retro_sprint_4.md @@ -0,0 +1 @@ +# retro sprint 4 \ No newline at end of file diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 8fe6d4d..c9a37a1 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -71,6 +71,9 @@ def database(): except Exception as e: print(f"Database error: {e}") return "Er is een fout opgetreden bij het ophalen van de databasegegevens.", 500 + +def add_command(command): + command_query = "INSERT INTO kobuki (command) VALUES ();" if __name__ == '__main__': app.run(debug=True, port=5000) From 820cb397818a00235ada51edd94eb530b4067596 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 12 Dec 2024 14:00:26 +0100 Subject: [PATCH 012/107] changed mysql package --- src/Python/flask/web/app.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index c9a37a1..2f606b7 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,6 +1,6 @@ from flask import Flask, request, render_template, jsonify import paho.mqtt.client as mqtt -from flask_mysqldb import MySQL +import mysql.connector app = Flask(__name__) @@ -18,13 +18,15 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use -app.config['MYSQL_HOST'] = 'localhost' -app.config["MYSQL_USER"] = 'admin' -app.config["MYSQL_PASSWORD"] = 'kobuki' -app.config["MYSQL_DB"] = 'kobuki' -mysql = MySQL(app) +cnx = mysql.connector.connect( + host="127.0.0.1", + port=3306, + user="admin", + password="kobuki!", + database="kobuki") +cnx.close() @app.route('/') def index(): @@ -62,7 +64,7 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): try: - cur = mysql.connection.cursor() + cur = cnx.cursor() cur.execute("SELECT * FROM kobuki_data") rows = cur.fetchall() # Haal alle rijen op uit het resultaat cur.close() From 869f320446072a342ced11389596412d3c7f7f9f Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 12 Dec 2024 14:01:35 +0100 Subject: [PATCH 013/107] fixed typo --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 2f606b7..0266bf5 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -23,7 +23,7 @@ cnx = mysql.connector.connect( host="127.0.0.1", port=3306, user="admin", - password="kobuki!", + password="kobuki", database="kobuki") cnx.close() From c0186f935d67402638b48ee77a5fd025fc4325ca Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 13:37:16 +0100 Subject: [PATCH 014/107] start verslag --- teamdocumentatie/Ishak/images/image.png | Bin 0 -> 10268 bytes teamdocumentatie/Ishak/motivatie.md | 21 +++++++++ teamdocumentatie/Ishak/verslag.md | 54 ++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 teamdocumentatie/Ishak/images/image.png create mode 100644 teamdocumentatie/Ishak/motivatie.md create mode 100644 teamdocumentatie/Ishak/verslag.md diff --git a/teamdocumentatie/Ishak/images/image.png b/teamdocumentatie/Ishak/images/image.png new file mode 100644 index 0000000000000000000000000000000000000000..915097824367120fcd0aadd9fff97a39fe6fffd6 GIT binary patch literal 10268 zcmbVy%qNvEr z=y;;;y^&Dn{7Jbetni@26ekYDRFF%Cvq+9A_!vTTPZk2?$=Ve$ z(JKT(+Q-Pb-v5?PA(N@IC1=vZ;8G=Rx4vJ`xR!EQVD0v`T`4-oX=>itA4pzITTByp zJ2b@m;Pzmg`DKaZm*g)rW=GE^FYgxqz_%4i@GhJ5ICP69HZ&sf$PG-PSea;n`g;mw3>aS>oqfBo zujI&oXNdv?Ea>xnGAN;5!!1tfXF4$MwlxR9qLwimZ8bF zHsbt3zo#MuGk0$FcW_A8P`BL{TrkmI(&oJ^2}GwZ9rt8o6V8d#5%y7fbMkTY$#x(y zl?S7XVEhWLNzf$r!s%+77t&fJc%K%~VFK1jYz)r|&--H~@88sz z{H^5ace$$e95o4%({_)F7ep+G6<7*=4QWXx7h7ohx9^IitFL@NHzd3Dv=S#jqTXL) z7PcXg-Rv9RdH$!cHS68=7(4Ph53S5bYE&7IA`rWZWI~y(dVZO9*+jcdaBTx!#k%1- z&&$sz`QE{hwR$3g9UM=*`eH-Asp8~) zA^I%3ndAn}Bl!K=k9GKVsNyd&ZFy@Xd0-{XMI3M)jRpZ!ttTd`fc+o*&{D)@b!00&NpeZ$6Fs~mhj8Sm5c z#IBHF_0_yPcN>edyH`V`lU7Z~E@F9Fc~$CHXByftk9@Q|tq>FOPh-sqHR%u141&Su ziS#XK`YVr!FAx>Bo>%!_8{R3uyHzxvCYxSt+|B;ja>@A%=8=Q4Is|>wzAci6y8OQ* zM&lu>JP1~|YW3e8AGy&u2>VQ_o%>#UK#`3)ffI>xjdrW456j*S*3JpTUJ*6!raIiy zn7e)9c*!j_rtF!ix3|VD8eMDPzgy|ha%=NxVjkJncQ{ebKEgJ5H`F!82tR**xuMp- zoq>{;Hl2$!=qHjERGv&w)PkK8843G2Ur z+AzZwNUXK3Q}{M(ddr}}TCb6)U#j{dt40~f$aiZQ=l#Ilq;)Lu%{6=Ij-gvUd0Wxtl02t38>5!8`7c>e9M!|PSwxXsfk^+I%~qKUVf zzP%O)@!zIEOb?&)6JFX)8XHlo2O*vpX3N7%7(XF7!}{%CVO?82g15@-1flcsQ`Wa< zs=+yQ!H2yPmnofrtW)Y4(auCSRYfJ)pD(cS&bmZ+BD|MCj2G&doZ{yJLZ(dD8*3Ii^V{s1b8MA-kH-p1U|OgR_^Is`LZyY%JJhw= z7g@Q|=d9r4dPaJ%$y6H@MLSawNT*vuch)02Ds` zug@*{?qvchucREIryuqAFwcd9=o~32A2^IY$X44=zOt&L5a1d2r=p?i9-nREeC(Ck z-?yPK?L;m6Cyiy`S%;Cs*(lqI=#$Tt4cb3u{UlxDg3<`5batP}wpfHd;h}44Po#C$vPb!=bf9#9m>op-lwgqS=2L726*s@p$wL(5mUBw@{!M{K!J5V zN#X}@2G4pSh-WaL=<*S<=Sicmga$Gh+aY9_L&OpeB#DNXp zK<_)P&H;OK0p`rb4f*_qXmTp1rgmspOQhbYbt0j1cEj+Bxl0>%-ppcC{pGg##Rc?z zRD?)#sDR~#bY+jSR{c$QrXi@}PwT_2dWX0Did*iyy35(6{f#6uirV%>T3AqT>GEBdGo0tpi&=~Y>}JAC9Vn{RH<8yB5@?qGe6U>WxoN5(q*j z6lYdIed`3dv5u~i-W@4OPj`7ut~9P-HT}fcXQ!zeV`le_VElW%S33#KPnD>!$bN^7 zn68u8uBEXQo7_lX9qWrE-&HGt)%sxkJ=g(91jvvD;d{Q|vZtW#Grjn3;7==zsVqee z_50l5h4j|+omN9+rR5{KC2-^Z?k8UWCT4@@eRM)oOS^`yk{3=$h&XNeUAE}(LG1vE zirR0D>1`qsXF^3BOHrw`zTw`x8|QpED!8Tu>l9F98>^muvCfRmKCU)zJ3TNUxCVIA zbi&{H!2wY9NX-)JN`7Qm-&PD*=YfQkzM9EV)2}Ln?)40&((1^sx9gk&D72NHb zPm^4Pq&05sxPh|n<^b%W*F!3oc|l<{Nm4X_IwTMKA-0=P@<#rxoA}4 zb|yeo>^mff+eq@1meEyQvGVacdYgP4-77f;OW)H*f+;MBiVzA>v(O}qTipL#b9(FQ zx3|gVrp>i5-X}1lmbsZy+3<5n^u@ffF`%6MghCpBKhc$KtT87%Ox4R{Bm-MTpr2{G zjgR$^>lm#4b*7`~v&u-*!wh1GNm49n(hb}6yYPatQre6>d#u(c6{^lx8VV#?#rs&@ z04~Ji7fjdR&L8IA@m?khjeKwko) z$^Y(Wek@U?NNt&5HIn#!FCzWvDf+t1l|BE;!4q|m^B4ZV-T zH*|39x4@z6)#uQb6xVIaW(D{>tOv%S0ajT4Du2F)?^(r6N?3^prc$L_^G{;m`59(< ztki<<3peE@pU1-_nZTe29yBj$2+9U&(%gC$33=-CO_M~Pr&u!1t;lA7IpLCl1}v%% z4OCEE_}hS4S92cvS8YAaqyUROZ`@zIh%gaf9V+M=sI6N5%t)r!d~-o0n9&?tqUH6T z#ABSv>^C)?FeZ>W`|BGA=rWlX5S*8=+6hhRU11Hg#K8il(#gg{)eJ*{sBc>_m&OIu z-Bqm~Gccb`IU^b{gnOe%dW+xGAy~4$UpI!<5UFtG9o(ZyX_Y;K7-*lkTag6XTf4e| zftvgefA)kwN}h*+K!LRgEYA`264+FnOjvX+v3@7|1{BX5?1fpfAjz{2|2i+q&+Nki7BQ=(pNT(dLvC0>qiRe~wLog4idd zjn2K0b_Np8-@ZrUp@&)*E1R&n)Rd0xEV;|@kBtp?P+l&jA4rZ-1f$({%jI2-YH{^% zfBx(Hg8@0Q0YF5UxLwg(?(C^8fh|q^0in1Bar-DNjb(R@xsu^`Fn0IuY3G%(kq*nw zlvR`B^jDMoVz6i_n`X z)#3AEp%IBFfff1nj~RNc;S)J5rz}3^sm)L<)F;Zgpe80tYkuWNFqr+^%q@B+s}*f~ zjU8f~)pZg93PTwV$F(Gb^2+DXqHL!e-oH#7gFc!tR5JGHN_|o)a?V_RKl=t;6P7~# z#dbPn6H1e({Igl7%NjKsRy$)0SDa~hlP}a8P1{D_9RYxzXio$VG7w*>&w}7(A%A(9hOmy4VaP>}+53E9~!!3V8k2 zLTBH(5^kPYw|AQ z;qxL7&z_gtY_v`QZ^M}{3R{@|VX$nfpuO$(cg{(l-t>uU`NOXjj@}zNASUjhYm0>* z?+)UBr!dr#ght~5KrBs;A_oGV+u*iaGj!rQ#lAb7d^8f3MH(()+$L6F?`-;zC>&Jw z$mEZTTK(O5;OJk!6C6`s>K0Sy=C>do)~e+moMM8IZ)q#i`RaFM8`pGZk6A*KT<6Wikz5axlIiF4X%$t|Z3(h_t7V=I9qF5uk7dUmy&nQ$;!c3weNqKmQQ)LdG1kvN|wiuPzQF~pv=bhy;SB{Ulmq@tZv zSz|VQb5M8*J13m|$o-T?rP}3}{Wr)4?eb{ch>}Rm9(_`_3@N7Px(EmN~^y?(1Ds%_P&D$hrXzJ zb+#i>4X+FoTAkPTx)T)=rnTr(PB&*`_i3V;`J&>0hllP&NExWM@wJhUdn?@5--`Fq z1j(D3BZ!L^rmiy<)a*`^Bm|{ubFwG3G-u@kDjOuybZ&l~O?qV#{T8V7Z}4l4@VQeP zg(4S&*Q;)13enFI>nRi00OT?;F*w*hpqLAN=ziNQ2~>IPhOQp>0B3KXK3R)Ixus5#f!|Vl z7fMirm+aLr9&I+$cY)AH_xk6 zdtK8h)Q7(uJa9d2R)m+dNN%YyMNwLMPL5@cz{OGTkpWRKUzJiuEJxexu>mpyn?+6y$|nF=Cs$F-Yrb z$7NqI&OP~^b^2JNYy!fPs9*w;j2ufRr0e!3u6ymTi8dpRO5saoj`;TgVmWd0pGOwK zGL@8MfikYb0{Vy}F+L*i|L4L={Mgp((8IcXQ`fMEyzN0#KE#t4}ZzgP60(z)t*M{u<~oxD5WC|tMIB6_^QR;91)`@53dKBx&|~FP0{IXf#`C+bebhcM{Q{{8TDl>9QV{s{32@XRszdB_8QGP zohg<{@4jZ*h?uk!+7AuPkqk>P@1Vi%J6y2X!A1Q;+4Zwta>xm7yIw7nC?}-IS|eg8=uw* zT(uX69w&FFYo-d-HM?=kNo7*TP265{d$i@S&nQ|WRRq23Gu(QXFC-JwrU2d7AGiT| zZoF3nTw6k9m|7skzI@)14!2YbKEnWnGm*^`7(~xr%&V%~(!g7xlD#^3{kFHsn$=eZ z+)^t74!9^Eh1~ET!3mG;xU^Zx@2cnymzF1&PKMDPMy+iz4Pv89bTU%V9{VVq4VxY= zNIWz$P;TR!1^!rtoOxL<$K!ikjTX)i1bUU{0KWS^4D$KhKGcCXuSv}XhjS(S(v`_n zXd)NQ-LCtCbmhqRvUDG~;tYFr|0@E^}~N8eiR7l*G4w~4a^00 z1228(C6M}nfr4Yor>}!b7MIb%2X0nm(Oi9;O zRX$6e2T6bFR*nd+-ukEt(WzL=VhB85>5pL# z=3_w^Gb5LMQJ&hnQj4@LT}0!p~ZXVRUSYI1408_m}xT0?*uqcy)+BOw*(FKCg1!0>p-~X z*6%bnI9u`^=ddu1%!9L*fetjR;#}A>SGBl*8eCWo07vf}eknk5Be&goxA3_0M(#*B zy#=VCwEvnhuTgzRteQ}8sDy(i+2)>8Q{kAcz_#h z3N%nx10)eE=;bII^;;(;ai@>q@657(36WR*>*W{=#;0V?seE$)T($3m+7K!5dL(0m zPpg2J=C_BFT|%&mD)FUCKR+cNg8Gm~!PgE2BN?$#FWn^?O<7`4s1xXt7+uWZfG3M8 zyHns>utFi=Ky$QOheETu#*9F?Ih!6&$h5WNE=atXkKGLT!xV&RsRB0nIc|}_eiT4L zZw$tTI)Q#(`gmLrE&nXEJI)MqkwQbdG_{STvzsB&+Jkv-{}L!`jl zRaCmWGuB7Th*FiOvY?Uc0pVnJMlwSPPR^?L0Bos7TMF2(=jv^3`@oy_(vF90>XT&| z_#;kFPo;lBHm9a;umwA+XYsquDl+e;kKgGve>7;5d#x6#+eV0bIP!eyZ?Kv8FQ3m1 z)WVo;#KB^1(v~<@Q0cEm5@cFVbBJn)%90G&YZwv7dy_9|qCgxOlPKFc4&PJ<7vTLB ztpqG@L<)P;n2d3cX>!_=&lYcKXS%A=V&!$Ob*fPKQkMVEZmuaEKXK+)Z;jhP*dkce zL0#On{QFsE3Lu4L@1$!MC1M&4sUoDDVaU??SOtyJY=B<&qiTGv?HQ3Pu@J>95fST; zjAf!4pbPs{e_hRzmIEYI*epkYytV#-Z#A8LU!Edpqes%+X3m_QsmQUpgLr_&F+|#( zOGGQQXfE`ZfeWJVrV`)wv7?<_kFXO%RUG51v?OtF2hA84V@)j57^SsX>u~wyaCjnCI)R~x@f4(F+o1g~ zt?~)}MN~b)+YnifKImMG3N7U9|kowUnZV0tyQD)W5p`K3{0^OVddt6Cr&P4;=*AN$f-V zG^c84Sk(ZcU)IyzEMHOdf2ceivL=}`U1zExKEWDBlS|s9cvc@i0L!TdB^WdS9=Q=H zLP@CLYFAFv6sn7r@SweN8VW8(Yr=7C0(~Ltt)6n4G|`zT*?0(|dXVPFnVd$Oe%H=6 z5p8NqgL637V;S3%wm#?H9S~2T@PjIq&m@&(&vGq-{O~;@S4pXbXlk#D=89^?9r286 zOU8rlVzFR6*G|4Rn+3M&h%SJCu1aes8Ggb>vm4hXcC`@*n&2Y(W-9T<@3E@LXQC>q zvDITd)a--=fxAc%+yOk-?@0Q;*!BWZ*qVMnVP-!N(`1Yr%ZRWQHyaqhQ%Ci>0o?cK zDkfP>Y)a+tm5ETFmcj6mCU;!NB*unzwRXMCj{&6HaC}x@1O*0^!K-B)27@eimW}8O zRlq%{fKr0!#Y4a^fC}KipmaZ++xNm|zS!=fF!5E+OCmMJL5EBj_2Bh@_;0Bf^KF$S zT?08c@emV(Xo5i3AJ)80OBqY?JaEhbb@Q+C)JP{or5OI{?6VwmFmmXn$D1HBbiRr? zVT8+fdU*P9WTi=iUQ^gp=AnR;S!=30(3gxtCh)nCv>5n9Rt-zwgiZ!}oh)8x8u^PB`+ZA>sRqF`VzmH&9fEfp_ii~b20X}+`~ ztmWkPhvbIhuP@>NtQr8@4^oS}vP`)?(K471=mWG64PjEZ`*Z5Ukj-treOb36rUZSD z0HRi5Iptv}**J#~(@{qzvjuUcSJKE~t@V?j;GjAx-#u1?N(bun;M#pG_K)uCGtFbf z3jS>zb2cd(pz6YFe^OdvuV2VW#z>4IK`GtWr@-sDniA3elIUjw!ho)8$Y4#`zvZX( zuGpA@l2jZ)?=o-C_C2A|PrdZ((?{g!tWNyM%l#%A(PsK5o!O7%j1Wl+7KAJDtXbs^ z&QEResNhl!5{|0DgESBp4XU?*K!A3ORL~^2-O%hG8i5hS^x0(1=0mxE=d+HNJjJ|m z`uOX!StV@9EZ%ainWO~3=$JOLpT1@l!(tN$G>jZG>Pe?(?}9hxUm*`nW_LZI0eYr~ z;RXSLjEkGfID{;!iObq-{IegtCp}Sj5{ye#HkVYM<_D!wn|yzuNBBYTuOsN2tm=3i z13ZF0;#0BWd(Qkwk12yAH6zO};WC?-K8|3(U=4wP$J;7kxpS!t4PYcAn7w*lY1K=N z$Vi*Un0}EL5m&OY&3^O7t--2cYx`{>f`4VQ@n8Gqs=}u{@gc8s>&cw2D~SxhfiJN zT%1Ye3H1hf?`<8@BUj{rSmRn4ZdfH)Wugmyh0X5e9qq&dg z_c~;_V6#*#)M3I5Aj7d`EtIIV>1Fuglzzh*WzO3VCcySzw~wRYOEm(f#U8ECNg|dc|;LDr$E3zvSM&8w>xtbhb7yy&R{C5|ty2Tx%ZvYex{`RaO88u*a zfw}rJs*D+uF(3#*xB`Oc%9nS+B-`U=N36~HI4?20WsFIRL;r>*MD|)l&FrS{ z=JXos%P!`DuTTPI0I&}G-zhidZ>L%T{akJYTHY-9`eTM)|NX1d(z`6ZC=nTZp;K_{ P4@E^mUA|P-`2GI?DTVCR literal 0 HcmV?d00001 diff --git a/teamdocumentatie/Ishak/motivatie.md b/teamdocumentatie/Ishak/motivatie.md new file mode 100644 index 0000000..e317226 --- /dev/null +++ b/teamdocumentatie/Ishak/motivatie.md @@ -0,0 +1,21 @@ +Motivation Letter + +16/12/2024 + +Cognizant Digital StudioAttn. Hayo Rubingh + +Subject: Internship Application Cognizant Digital Studio + +Dear Mr. Rubingh, + +With great enthusiasm, I am applying for the internship position at Cognizant Digital Studio in Amsterdam. As a second-year bachelor’s student in Technische Informatica(Computer Science) at Hogeschool Van Amsterdam, I am seeking a challenging internship where I can combine my technical skills with my passion for innovation. Cognizant’s focus IoT, and technology prototypes fits perfectly with my interests . + +Throughout my studies, I have gained experience in software development, including Python and JavaScript, and have worked with IoT devices such as Arduino/ESP. What drives me is the opportunity to create and develop new solutions that can make life easier and more efficient. I am particularly interested in the field of IoT and the possibilities it offers for creating smart solutions. I am eager to learn more about the latest technologies and how they can be applied in real-world projects. + +I am available to start in February 2025 and look forward to contributing to innovative projects. + +I would be delighted to discuss my motivation and experience further in a personal interview. You can find my contact details in my CV. Thank you for considering my application. I am looking forward to hearing from you. + +Yours sincerely, + +Ishak Jmilou \ No newline at end of file diff --git a/teamdocumentatie/Ishak/verslag.md b/teamdocumentatie/Ishak/verslag.md new file mode 100644 index 0000000..fb8d7b2 --- /dev/null +++ b/teamdocumentatie/Ishak/verslag.md @@ -0,0 +1,54 @@ +![alt text](images/image.png) + +# Welke communicatieprotocol geeft de mogelijkheid om veilig en betrouwbaar te communiceren tussen IoT apparaten? + +Auteur: Ishak Jmilou + +Datum: 17-12-2024 + + + +--- + + +## Inleiding + +In dit verslag wordt er gekeken naar de verschillende communicatieprotocollen die gebruikt kunnen worden om veilig en betrouwbaar te communiceren tussen IoT apparaten. Er wordt gekeken naar de verschillende protocollen en de voor- en nadelen van elk protocol. + +--- + +## 1. Wat houdt veilige en betrouwbare communicatie tussen apparaten in? + +Als je werkt met IoT-apparaten, is het belangrijk dat de communicatie tussen deze apparaten veilig en betrouwbaar is. Iot-apparaten verzamelen gegeven over de omgeving en communiceert deze tussen apparaten over het internet. Als deze communicatie niet veilig is, kunnen hackers deze gegevens onderscheppen en gebruiken(Ministerie van Algemene Zaken, 2022). Je wilt voorkomen dat hackers toegang krijgen tot gevoelige informatie zoals persoonlijke gegevens of bedrijfsgeheimen. Daarom is het belangrijk dat de communicatie tussen apparaten veilig en betrouwbaar is. + +## 2. Welke protocollen zijn er om veilig en betrouwbaar te communiceren tussen apparaten? + +Er zijn verschillende soorten protocollen die + + +## 3. Wat zijn de voor- en nadelen van de verschillende protocollen? + + +## literatuurlijst + +- Singh, S., & Jyoti. (2024, June 7). Secure Communications Protocols for IoT networks: a survey. https://journal.ijprse.com/index.php/ijprse/article/view/1082 + +- Nguyen, K. T., Laurent, M., Oualha, N., CEA, & Institut Mines-Telecom. (2015). Survey on secure communication protocols for the Internet of Things. In Ad Hoc Networks (Vol. 32, pp. 17–31) [Journal-article]. http://dx.doi.org/10.1016/j.adhoc.2015.01.006 + +- Miorandi, D., Sicari, S., De Pellegrini, F., & Imrich Chlamtac. (2012). Internet of things: Vision, applications and research challenges. In Ad Hoc Networks (Vol. 10, pp. 1497–1516) [Journal-article]. Elsevier B.V. http://dx.doi.org/10.1016/j.adhoc.2012.02.016 + +- Christiano, P. (2023, November 5). Top 9 IoT communication protocols & their features in 2024: An In-Depth guide - ExpertBeacon. Expertbeacon. https://expertbeacon.com/iot-communication-protocol/ + +- Yugha, R., & Chithra, S. (2020). A survey on technologies and security protocols: Reference for future generation IoT. Journal of Network and Computer Applications, 169, 102763. https://doi.org/10.1016/j.jnca.2020.102763 + +- De Mendizábal, I. (2022, June 16). IoT Communication Protocols—IoT Data Protocols. Technical Articles. https://www.allaboutcircuits.com/technical-articles/internet-of-things-communication-protocols-iot-data-protocols/ + +- IoT-technologieën en -protocollen | Microsoft Azure. (n.d.). https://azure.microsoft.com/nl-nl/solutions/iot/iot-technology-protocols + +- Het IoT verbinden: wat is MQTT en waarin verschilt het van CoAP? (n.d.). https://www.onlogic.com/nl/blog/het-iot-verbinden-wat-is-mqtt-en-waarin-verschilt-het-van-coap/ + +- Nader, K. (2023, October 30). Wat zijn de voordelen van het gebruik van WebSocket voor IoT-communicatie? AppMaster - Ultimate All-in No-code Platform. https://appmaster.io/nl/blog/websocket-voor-iot-communicatie + +- Sidna, J., Amine, B., Abdallah, N., & Alami, H. E. (2020). Analysis and evaluation of communication Protocols for IoT Applications. Karbala International Journal of Modern Science. https://doi.org/10.1145/3419604.3419754 + +- Ministerie van Algemene Zaken. (2022, February 8). Hoe kan ik slimme apparaten veilig gebruiken? Rijksoverheid.nl. https://www.rijksoverheid.nl/onderwerpen/bescherming-van-consumenten/vraag-en-antwoord/hoe-kan-ik-slimme-apparaten-veilig-gebruiken \ No newline at end of file From df6a49bbaaa18a098ef00624bf6c12e8943d4206 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 13:42:14 +0100 Subject: [PATCH 015/107] test to get db info --- src/Python/flask/web/app.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 0266bf5..7c85020 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -64,11 +64,9 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): try: - cur = cnx.cursor() - cur.execute("SELECT * FROM kobuki_data") - rows = cur.fetchall() # Haal alle rijen op uit het resultaat - cur.close() - + with cnx.cursor() as cur: + cur.execute("SELECT * FROM kobuki_data") + rows = cur.fetchall() # Haal alle rijen op uit het resultaat return str(rows) except Exception as e: print(f"Database error: {e}") From d6c3383ef01556639a5e9346a682d06d89249379 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 13:44:35 +0100 Subject: [PATCH 016/107] check to see if website is updated --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 7c85020..6d34408 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -70,7 +70,7 @@ def database(): return str(rows) except Exception as e: print(f"Database error: {e}") - return "Er is een fout opgetreden bij het ophalen van de databasegegevens.", 500 + return "je hebt iets fout gedaan.", 500 def add_command(command): command_query = "INSERT INTO kobuki (command) VALUES ();" From 3c3f8b93db3ee148492f2aefa59d53194c3662e7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 13:58:18 +0100 Subject: [PATCH 017/107] changed data i want to execute --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 6d34408..18c12b1 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -65,7 +65,7 @@ def phpmyadmin_passthrough(path): def database(): try: with cnx.cursor() as cur: - cur.execute("SELECT * FROM kobuki_data") + cur.execute("SELECT DATABASE()") rows = cur.fetchall() # Haal alle rijen op uit het resultaat return str(rows) except Exception as e: From 651dcbc6a5e10ac5ea7d8c6a9d6123f2cd7885b6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:01:15 +0100 Subject: [PATCH 018/107] see if connection is actually made --- src/Python/flask/web/app.py | 50 +++++++++++++++---------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 18c12b1..1904e3e 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -6,7 +6,7 @@ app = Flask(__name__) kobuki_message = "empty" def on_message(client, userdata, message): - global kobuki_message #set scope for this variable + global kobuki_message kobuki_message = str(message.payload.decode("utf-8")) print(kobuki_message) @@ -16,24 +16,13 @@ mqtt_client.username_pw_set("server", "serverwachtwoordofzo") mqtt_client.connect("localhost", 80, 60) mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") -mqtt_client.on_message = on_message # this lines needs to be under the function definition otherwise it cant find which function it needs to use - - -cnx = mysql.connector.connect( - host="127.0.0.1", - port=3306, - user="admin", - password="kobuki", - database="kobuki") - -cnx.close() +mqtt_client.on_message = on_message @app.route('/') def index(): return render_template('index.html') - -@app.route('/control', methods=["GET","POST"]) +@app.route('/control', methods=["GET", "POST"]) def control(): if request.authorization and request.authorization.username == 'ishak' and request.authorization.password == 'kobuki': return render_template('control.html') @@ -47,15 +36,10 @@ def move(): # Verstuur de richting via MQTT if direction: - mqtt_client.publish("home/commands", direction) # Het topic kan aangepast worden + mqtt_client.publish("home/commands", direction) return jsonify({"status": "success", "direction": direction}) - -@app.route('/data', methods=['GET']) -def data(): - return kobuki_message - @app.route('/phpmyadmin/') def phpmyadmin_passthrough(path): # Laat Apache deze route direct afhandelen @@ -64,16 +48,22 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): try: - with cnx.cursor() as cur: - cur.execute("SELECT DATABASE()") - rows = cur.fetchall() # Haal alle rijen op uit het resultaat + cnx = mysql.connector.connect( + host="127.0.0.1", + port=3306, + user="admin", + password="kobuki", + database="kobuki" + ) + cursor = cnx.cursor() + cursor.execute("SELECT * FROM kobuki_data") + rows = cursor.fetchall() + cursor.close() + cnx.close() return str(rows) - except Exception as e: - print(f"Database error: {e}") - return "je hebt iets fout gedaan.", 500 - -def add_command(command): - command_query = "INSERT INTO kobuki (command) VALUES ();" + except mysql.connector.Error as err: + print(f"Database error: {err}") + return f"Database error: {err}", 500 if __name__ == '__main__': - app.run(debug=True, port=5000) + app.run(debug=True, port=5000) \ No newline at end of file From 10a7a2b98c666425695635b8f0dbdf36c17f5f47 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:08:42 +0100 Subject: [PATCH 019/107] add direction to db when pressed button --- src/Python/flask/web/app.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 1904e3e..1c9ecc5 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -18,6 +18,14 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message +cnx = mysql.connector.connect( + host="127.0.0.1", + port=3306, + user="admin", + password="kobuki", + database="kobuki" +) + @app.route('/') def index(): return render_template('index.html') @@ -33,6 +41,12 @@ def control(): def move(): data = request.get_json() direction = data.get("direction") + + cursor = cnx.cursor() + cursor.execute("INSERT INTO kobuki_data (command) VALUES (%s)", (direction,)) + cnx.commit() + cursor.close() + cnx.close() # Verstuur de richting via MQTT if direction: @@ -47,23 +61,12 @@ def phpmyadmin_passthrough(path): @app.route("/database") def database(): - try: - cnx = mysql.connector.connect( - host="127.0.0.1", - port=3306, - user="admin", - password="kobuki", - database="kobuki" - ) - cursor = cnx.cursor() - cursor.execute("SELECT * FROM kobuki_data") - rows = cursor.fetchall() - cursor.close() - cnx.close() - return str(rows) - except mysql.connector.Error as err: - print(f"Database error: {err}") - return f"Database error: {err}", 500 + cursor = cnx.cursor() + cursor.execute("SELECT * FROM kobuki_data") + rows = cursor.fetchall() + cursor.close() + cnx.close() + return str(rows) if __name__ == '__main__': app.run(debug=True, port=5000) \ No newline at end of file From ef572c6539edc1e0ace13b102e0fbb82a7f72745 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:38:16 +0100 Subject: [PATCH 020/107] made function for db connection --- src/Python/flask/web/app.py | 51 +++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 1c9ecc5..c06e1cd 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,16 +1,16 @@ -from flask import Flask, request, render_template, jsonify +from flask import Flask, request, render_template, jsonify, g import paho.mqtt.client as mqtt -import mysql.connector +import mysql.connector app = Flask(__name__) +# Globale MQTT setup kobuki_message = "empty" def on_message(client, userdata, message): global kobuki_message kobuki_message = str(message.payload.decode("utf-8")) print(kobuki_message) -# Create an MQTT client instance mqtt_client = mqtt.Client() mqtt_client.username_pw_set("server", "serverwachtwoordofzo") mqtt_client.connect("localhost", 80, 60) @@ -18,13 +18,24 @@ mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") mqtt_client.on_message = on_message -cnx = mysql.connector.connect( - host="127.0.0.1", - port=3306, - user="admin", - password="kobuki", - database="kobuki" -) +# Database connectie-functie +def get_db(): + if 'db' not in g: # 'g' is specifiek voor een request en leeft zolang een request duurt + g.db = mysql.connector.connect( + host="127.0.0.1", + port=3306, + user="admin", + password="kobuki", + database="kobuki" + ) + return g.db + +# Sluit de database na elke request +@app.teardown_appcontext +def close_db(error): + db = g.pop('db', None) + if db is not None: + db.close() @app.route('/') def index(): @@ -42,11 +53,12 @@ def move(): data = request.get_json() direction = data.get("direction") - cursor = cnx.cursor() - cursor.execute("INSERT INTO kobuki_data (command) VALUES (%s)", (direction,)) - cnx.commit() + # Haal databaseverbinding op + db = get_db() + cursor = db.cursor() + cursor.execute("INSERT INTO command VALUES (%s)", (direction,)) + db.commit() cursor.close() - cnx.close() # Verstuur de richting via MQTT if direction: @@ -54,19 +66,14 @@ def move(): return jsonify({"status": "success", "direction": direction}) -@app.route('/phpmyadmin/') -def phpmyadmin_passthrough(path): - # Laat Apache deze route direct afhandelen - return "", 404 - @app.route("/database") def database(): - cursor = cnx.cursor() + db = get_db() + cursor = db.cursor() cursor.execute("SELECT * FROM kobuki_data") rows = cursor.fetchall() cursor.close() - cnx.close() return str(rows) if __name__ == '__main__': - app.run(debug=True, port=5000) \ No newline at end of file + app.run(debug=True, port=5000) From 92992288b50db0354a79c445b86d5dab1b91d97a Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:41:13 +0100 Subject: [PATCH 021/107] returned function i removed by accident --- src/Python/flask/web/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index c06e1cd..ec7f8b8 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -66,6 +66,10 @@ def move(): return jsonify({"status": "success", "direction": direction}) +@app.route('/data', methods=['GET']) +def data(): + return kobuki_message + @app.route("/database") def database(): db = get_db() From 9ea6ed5e2d79f02bcf4ad9dee46d4ee8a42f3313 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:43:13 +0100 Subject: [PATCH 022/107] removed in /move --- src/Python/flask/web/app.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index ec7f8b8..9a7b4df 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -52,13 +52,6 @@ def control(): def move(): data = request.get_json() direction = data.get("direction") - - # Haal databaseverbinding op - db = get_db() - cursor = db.cursor() - cursor.execute("INSERT INTO command VALUES (%s)", (direction,)) - db.commit() - cursor.close() # Verstuur de richting via MQTT if direction: From 12c4e6302266ba3237c01787d3d72441e8a21bbb Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:48:39 +0100 Subject: [PATCH 023/107] test --- src/Python/flask/web/app.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 9a7b4df..4a5ef76 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -52,6 +52,13 @@ def control(): def move(): data = request.get_json() direction = data.get("direction") + + db_connection = get_db() + cursor = db_connection.cursor() + cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) + db_connection.commit() + cursor.close() + db_connection.close() # Verstuur de richting via MQTT if direction: From 4307d0a8d5d3048ff1a1503b04f3ed62317f7faf Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:57:01 +0100 Subject: [PATCH 024/107] test if i still get error --- src/Python/flask/web/app.py | 42 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 4a5ef76..365ff8f 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -50,21 +50,39 @@ def control(): @app.route('/move', methods=['POST']) def move(): - data = request.get_json() - direction = data.get("direction") - - db_connection = get_db() - cursor = db_connection.cursor() - cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) - db_connection.commit() - cursor.close() - db_connection.close() + try: + # Haal de richting op van de request + data = request.get_json() + direction = data.get("direction") - # Verstuur de richting via MQTT - if direction: + if not direction: + return jsonify({"status": "error", "message": "Direction is required"}), 400 + + # Maak verbinding met de database + conn = get_db() + cursor = conn.cursor() + + # Sla de richting op in de database + cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) + conn.commit() + + # Sluit de cursor en de verbinding + cursor.close() + conn.close() + + # Verstuur de richting via MQTT mqtt_client.publish("home/commands", direction) - return jsonify({"status": "success", "direction": direction}) + return jsonify({"status": "success", "direction": direction}) + + except mysql.connector.Error as e: + # Foutafhandeling voor databasefouten + return jsonify({"status": "error", "message": str(e)}), 500 + + except Exception as e: + # Algemene foutafhandeling + return jsonify({"status": "error", "message": "An error occurred", "details": str(e)}), 500 + @app.route('/data', methods=['GET']) def data(): From c4d2888fbfde786749c5861834b4897bc4ba09ac Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 14:58:31 +0100 Subject: [PATCH 025/107] changed order --- src/Python/flask/web/app.py | 42 +++++++++++-------------------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 365ff8f..32bdbe3 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -50,39 +50,21 @@ def control(): @app.route('/move', methods=['POST']) def move(): - try: - # Haal de richting op van de request - data = request.get_json() - direction = data.get("direction") + data = request.get_json() + direction = data.get("direction") - if not direction: - return jsonify({"status": "error", "message": "Direction is required"}), 400 - - # Maak verbinding met de database - conn = get_db() - cursor = conn.cursor() - - # Sla de richting op in de database - cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) - conn.commit() - - # Sluit de cursor en de verbinding - cursor.close() - conn.close() - - # Verstuur de richting via MQTT + # Verstuur de richting via MQTT + if direction: mqtt_client.publish("home/commands", direction) - return jsonify({"status": "success", "direction": direction}) - - except mysql.connector.Error as e: - # Foutafhandeling voor databasefouten - return jsonify({"status": "error", "message": str(e)}), 500 - - except Exception as e: - # Algemene foutafhandeling - return jsonify({"status": "error", "message": "An error occurred", "details": str(e)}), 500 - + db_connection = get_db() + cursor = db_connection.cursor() + cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) + db_connection.commit() + cursor.close() + db_connection.close() + + return jsonify({"status": "success", "direction": direction}) @app.route('/data', methods=['GET']) def data(): From 4da91f22ca6a2ff09e47320b361d30612697fbf6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 15:03:21 +0100 Subject: [PATCH 026/107] changed --- src/Python/flask/web/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 32bdbe3..267a6e9 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -59,7 +59,9 @@ def move(): db_connection = get_db() cursor = db_connection.cursor() - cursor.execute("INSERT INTO command (direction) VALUES (%s)", (direction,)) + sql = "INSERT INTO command (command) VALUES (%s)" + value = direction + cursor.execute(sql, (value,)) db_connection.commit() cursor.close() db_connection.close() From b1d5e8548cf432719efcf19c992e468d4d993cbf Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 15:26:19 +0100 Subject: [PATCH 027/107] got code from other branch --- src/C++/Driver/src/main.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index b1ba02f..af68be6 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -1,7 +1,6 @@ #include #include #include -#include "KobukiDriver/graph.h" #include "MQTT/MqttClient.h" #include "KobukiDriver/CKobuki.h" @@ -10,7 +9,7 @@ CKobuki robot; std::string readMQTT(); void parseMQTT(std::string message); //ip, clientID, username, password -MqttClient client("mqtt://145.92.224.21:1884", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object +MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object std::string message = "stop"; std::string serializeKobukiData(const TKobukiData &data); void sendKobukiData(TKobukiData &data); @@ -18,24 +17,27 @@ void sendKobukiData(TKobukiData &data); void setup() { unsigned char *null_ptr(0); - robot.startCommunication("/dev/ttyUSB0", true, null_ptr); + // robot.startCommunication("/dev/ttyUSB0", true, null_ptr); //connect mqtt server and sub to commands + client.connect(); client.subscribe("home/commands"); } int main() { + // Unset the http_proxy environment variable + + setup(); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); while(true){ - parseMQTT(readMQTT()); + parseMQTT(readMQTT()); } sendMqtt.join(); safety.join(); - return 0; } std::string readMQTT() @@ -270,6 +272,7 @@ std::string serializeKobukiData(const TKobukiData &data) { void sendKobukiData(TKobukiData &data) { while (true) { client.publishMessage("kobuki/data", serializeKobukiData(data)); + std::cout << "Sent data" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } From e0560d71620f6c5147e8cae27812cb57f6814a92 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 15:43:05 +0100 Subject: [PATCH 028/107] changed mqtt port --- src/Python/flask/web/app.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 267a6e9..84b5dc1 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -5,18 +5,20 @@ import mysql.connector app = Flask(__name__) # Globale MQTT setup -kobuki_message = "empty" -def on_message(client, userdata, message): - global kobuki_message - kobuki_message = str(message.payload.decode("utf-8")) - print(kobuki_message) +def on_message(client, message): + global kobuki_message, latest_image + if message.topic == "kobuki/data": + kobuki_message = str(message.payload.decode("utf-8")) + elif message.topic == "kobuki/cam": + latest_image = message.payload + +# Create an MQTT client instance mqtt_client = mqtt.Client() mqtt_client.username_pw_set("server", "serverwachtwoordofzo") -mqtt_client.connect("localhost", 80, 60) +mqtt_client.connect("localhost", 1884, 60) mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") -mqtt_client.on_message = on_message # Database connectie-functie def get_db(): From 072b54af049584405d73e8185490ca06d11a2cb6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 17 Dec 2024 15:49:32 +0100 Subject: [PATCH 029/107] added kobuki_message --- src/Python/flask/web/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 84b5dc1..bb3d3ab 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -5,6 +5,7 @@ import mysql.connector app = Flask(__name__) # Globale MQTT setup +kobuki_message = "empty" def on_message(client, message): global kobuki_message, latest_image if message.topic == "kobuki/data": From 1ecd474ca1c1bf915ff6ad60a7573ae59b49bbc4 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 18 Dec 2024 12:28:59 +0100 Subject: [PATCH 030/107] switched to this branch --- src/Python/flask/web/static/script.js | 55 +++++++++++++++++---------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 109916d..cdd56e4 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,32 +1,36 @@ -// Selecteer alle knoppen en voeg een event listener toe aan elke knop -document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", function(event) { - event.preventDefault(); // voorkomt pagina-verversing +document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", function(event) { + event.preventDefault(); // prevents page refresh - // Haal de waarde van de knop op - const direction = event.target.value; + // Get the value of the button + const direction = event.target.value; - fetch("/move", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ direction: direction }) - }) - .then(response => response.json()) - .then(data => { - console.log("Success:", data); - }) - .catch(error => { - console.error("Error:", error); + fetch("/move", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ direction: direction }) + }) + .then(response => response.json()) + .then(data => {script + console.log("Success:", data); + }) + .catch(error => { + console.error("Error:", error); + }); }); }); // Fetch data from the server async function fetchData() { + try{ const response = await fetch("/data"); const data = await response.json(); return data; + } catch (error) { + console.error("Error:", error); } // Parse the data and show it on the website @@ -34,7 +38,7 @@ document.querySelectorAll(".btn").forEach(button => { const data = await fetchData(); const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - //for each object in json array create a new paragraph element and append it to the sensorDataContainer + // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer for (const [key, value] of Object.entries(data)) { const dataElement = document.createElement("p"); dataElement.textContent = `${key}: ${value}`; @@ -42,6 +46,15 @@ document.querySelectorAll(".btn").forEach(button => { } } + // Update the image + function updateImage() { + var img = document.getElementById("robot-image"); + img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching + } + // Fetch and display sensor data every 5 seconds - setInterval(parseData, 5000); + setInterval(parseData, 1000); + + // Update the image every 5 seconds + setInterval(updateImage, 200); }); \ No newline at end of file From f0b87de63dedaa39b28359d5e5dfb484bc7921e2 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 18 Dec 2024 12:30:41 +0100 Subject: [PATCH 031/107] switched to older version --- src/Python/flask/web/static/script.js | 55 ++++++++++----------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index cdd56e4..109916d 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,36 +1,32 @@ -document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", function(event) { - event.preventDefault(); // prevents page refresh +// Selecteer alle knoppen en voeg een event listener toe aan elke knop +document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", function(event) { + event.preventDefault(); // voorkomt pagina-verversing - // Get the value of the button - const direction = event.target.value; + // Haal de waarde van de knop op + const direction = event.target.value; - fetch("/move", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ direction: direction }) - }) - .then(response => response.json()) - .then(data => {script - console.log("Success:", data); - }) - .catch(error => { - console.error("Error:", error); - }); + fetch("/move", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ direction: direction }) + }) + .then(response => response.json()) + .then(data => { + console.log("Success:", data); + }) + .catch(error => { + console.error("Error:", error); }); }); // Fetch data from the server async function fetchData() { - try{ const response = await fetch("/data"); const data = await response.json(); return data; - } catch (error) { - console.error("Error:", error); } // Parse the data and show it on the website @@ -38,7 +34,7 @@ document.addEventListener("DOMContentLoaded", function() { const data = await fetchData(); const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer + //for each object in json array create a new paragraph element and append it to the sensorDataContainer for (const [key, value] of Object.entries(data)) { const dataElement = document.createElement("p"); dataElement.textContent = `${key}: ${value}`; @@ -46,15 +42,6 @@ document.addEventListener("DOMContentLoaded", function() { } } - // Update the image - function updateImage() { - var img = document.getElementById("robot-image"); - img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching - } - // Fetch and display sensor data every 5 seconds - setInterval(parseData, 1000); - - // Update the image every 5 seconds - setInterval(updateImage, 200); + setInterval(parseData, 5000); }); \ No newline at end of file From 29cfa86b5f4844634bc6d48171daed58554f2ef9 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 18 Dec 2024 12:36:38 +0100 Subject: [PATCH 032/107] returned try catch --- src/Python/flask/web/static/script.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 109916d..de56ad4 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -24,11 +24,15 @@ document.querySelectorAll(".btn").forEach(button => { // Fetch data from the server async function fetchData() { + try{ const response = await fetch("/data"); const data = await response.json(); return data; + } catch (error) { + console.error("Error:", error); } + // Parse the data and show it on the website async function parseData() { const data = await fetchData(); From ddeeb379cf1460d2626aee262392db08e3064c27 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 18 Dec 2024 12:42:15 +0100 Subject: [PATCH 033/107] fixed error from braces --- src/Python/flask/web/static/script.js | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index de56ad4..57938f4 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,6 +1,6 @@ // Selecteer alle knoppen en voeg een event listener toe aan elke knop -document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", function(event) { +document.querySelectorAll(".btn").forEach((button) => { + button.addEventListener("click", function (event) { event.preventDefault(); // voorkomt pagina-verversing // Haal de waarde van de knop op @@ -9,30 +9,30 @@ document.querySelectorAll(".btn").forEach(button => { fetch("/move", { method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", }, - body: JSON.stringify({ direction: direction }) + body: JSON.stringify({ direction: direction }), }) - .then(response => response.json()) - .then(data => { - console.log("Success:", data); - }) - .catch(error => { - console.error("Error:", error); - }); + .then((response) => response.json()) + .then((data) => { + console.log("Success:", data); + }) + .catch((error) => { + console.error("Error:", error); + }); }); // Fetch data from the server async function fetchData() { - try{ - const response = await fetch("/data"); - const data = await response.json(); - return data; - } catch (error) { - console.error("Error:", error); + try { + const response = await fetch("/data"); + const data = await response.json(); + return data; + } catch (error) { + console.error("Error:", error); + } } - // Parse the data and show it on the website async function parseData() { const data = await fetchData(); @@ -48,4 +48,4 @@ document.querySelectorAll(".btn").forEach(button => { // Fetch and display sensor data every 5 seconds setInterval(parseData, 5000); -}); \ No newline at end of file +}); From 810309c37d0ada44873191d93e8789c3d10426cc Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 18 Dec 2024 12:45:25 +0100 Subject: [PATCH 034/107] front to see if data is correct --- src/Python/flask/web/static/script.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 57938f4..9cef094 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -36,6 +36,11 @@ document.querySelectorAll(".btn").forEach((button) => { // Parse the data and show it on the website async function parseData() { const data = await fetchData(); + + if(!data){ + console.error("No data received"); + return; + } const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data //for each object in json array create a new paragraph element and append it to the sensorDataContainer From 9cd6d3aa79bcc7fe98654f06411bd9c3a5b6ddd2 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:01:09 +0100 Subject: [PATCH 035/107] removed variable --- src/Python/flask/web/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index bb3d3ab..84b5dc1 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -5,7 +5,6 @@ import mysql.connector app = Flask(__name__) # Globale MQTT setup -kobuki_message = "empty" def on_message(client, message): global kobuki_message, latest_image if message.topic == "kobuki/data": From a6b1c04ea35ec2cd40d16493bac7fb607146971a Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:03:36 +0100 Subject: [PATCH 036/107] returned global variables --- src/Python/flask/web/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 84b5dc1..f69355f 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -4,6 +4,10 @@ import mysql.connector app = Flask(__name__) +# Globale variabelen +kobuki_message = "" +latest_image = None + # Globale MQTT setup def on_message(client, message): global kobuki_message, latest_image From d8ce5de8f72203bce2441847946a588fe81c98c3 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:05:48 +0100 Subject: [PATCH 037/107] removed try catch --- src/Python/flask/web/static/script.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 9cef094..2fb6c50 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -24,13 +24,10 @@ document.querySelectorAll(".btn").forEach((button) => { // Fetch data from the server async function fetchData() { - try { const response = await fetch("/data"); const data = await response.json(); return data; - } catch (error) { - console.error("Error:", error); - } + } // Parse the data and show it on the website From 45247b5574b743c26c2ecbf57f43f1f80d1eabdd Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:22:43 +0100 Subject: [PATCH 038/107] added code from main branch --- src/C++/Driver/src/main.cpp | 57 +++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index af68be6..3d7e147 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -3,11 +3,15 @@ #include #include "MQTT/MqttClient.h" #include "KobukiDriver/CKobuki.h" +#include +#include using namespace std; +using namespace cv; CKobuki robot; std::string readMQTT(); void parseMQTT(std::string message); +void CapnSend(); //ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object std::string message = "stop"; @@ -17,7 +21,7 @@ void sendKobukiData(TKobukiData &data); void setup() { unsigned char *null_ptr(0); - // robot.startCommunication("/dev/ttyUSB0", true, null_ptr); + robot.startCommunication("/dev/ttyUSB0", true, null_ptr); //connect mqtt server and sub to commands client.connect(); @@ -26,26 +30,33 @@ void setup() int main() { - // Unset the http_proxy environment variable - - setup(); + std::thread image (CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); while(true){ - parseMQTT(readMQTT()); + std::string message = readMQTT(); + if (!message.empty()){ + parseMQTT(message); + } + } + sendMqtt.join(); safety.join(); + image.join(); } std::string readMQTT() { - message = client.getLastMessage(); - if (!message.empty()) + static std::string lastMessage; + + std::string message = client.getLastMessage(); + if (!message.empty() && message != lastMessage) { std::cout << "MQTT Message: " << message << std::endl; + lastMessage = message; } // Add a small delay to avoid busy-waiting @@ -57,7 +68,7 @@ void parseMQTT(std::string message) { if (message == "up") { - robot.forward(1024); + robot.forward(350); } else if (message == "left") { @@ -69,7 +80,7 @@ void parseMQTT(std::string message) } else if (message == "down") { - robot.forward(-800); + robot.forward(-350); } else if (message == "stop") { @@ -276,3 +287,31 @@ void sendKobukiData(TKobukiData &data) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } + +void CapnSend() { + VideoCapture cap(0); + if (!cap.isOpened()) { + cerr << "Error: Could not open camera" << endl; + return; + } + + Mat frame; + while (true) { + cap >> frame; // Capture a new image frame + if (frame.empty()) { + cerr << "Error: Could not capture image" << endl; + continue; + } + + // Convert the image to a byte array + vector buf; + imencode(".jpg", frame, buf); + auto* enc_msg = reinterpret_cast(buf.data()); + + // Publish the image data + client.publishMessage("kobuki/cam", string(enc_msg, enc_msg + buf.size())); + cout << "Sent image" << endl; + + std::this_thread::sleep_for(std::chrono::milliseconds(300)); // Send image every 1000ms + } +} \ No newline at end of file From c5981f763b7dc8220f155a1fbfa79232cca1f219 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:30:55 +0100 Subject: [PATCH 039/107] fixed error --- src/Python/flask/web/static/script.js | 64 +++++++++++++++------------ 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 2fb6c50..c71f095 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,46 +1,45 @@ -// Selecteer alle knoppen en voeg een event listener toe aan elke knop -document.querySelectorAll(".btn").forEach((button) => { - button.addEventListener("click", function (event) { - event.preventDefault(); // voorkomt pagina-verversing +document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".btn").forEach((button) => { + button.addEventListener("click", function (event) { + event.preventDefault(); // prevents page refresh - // Haal de waarde van de knop op - const direction = event.target.value; + // Get the value of the button + const direction = event.target.value; - fetch("/move", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ direction: direction }), - }) - .then((response) => response.json()) - .then((data) => { - console.log("Success:", data); + fetch("/move", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ direction: direction }), }) - .catch((error) => { - console.error("Error:", error); - }); + .then((response) => response.json()) + .then((data) => { + script; + console.log("Success:", data); + }) + .catch((error) => { + console.error("Error:", error); + }); + }); }); // Fetch data from the server async function fetchData() { + try { const response = await fetch("/data"); const data = await response.json(); return data; - + } catch (error) { + console.error("Error:", error); + } } - // Parse the data and show it on the website async function parseData() { const data = await fetchData(); - - if(!data){ - console.error("No data received"); - return; - } const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - //for each object in json array create a new paragraph element and append it to the sensorDataContainer + // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer for (const [key, value] of Object.entries(data)) { const dataElement = document.createElement("p"); dataElement.textContent = `${key}: ${value}`; @@ -48,6 +47,15 @@ document.querySelectorAll(".btn").forEach((button) => { } } + // Update the image + function updateImage() { + var img = document.getElementById("robot-image"); + img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching + } + // Fetch and display sensor data every 5 seconds - setInterval(parseData, 5000); + setInterval(parseData, 1000); + + // Update the image every 5 seconds + setInterval(updateImage, 200); }); From fa1aa6965df8c904384d22275e28729d00974470 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 20:31:50 +0100 Subject: [PATCH 040/107] updated cmakelist --- src/C++/Driver/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/CMakeLists.txt b/src/C++/Driver/CMakeLists.txt index e6c35e2..78d5be1 100644 --- a/src/C++/Driver/CMakeLists.txt +++ b/src/C++/Driver/CMakeLists.txt @@ -6,7 +6,10 @@ set(CMAKE_CXX_STANDARD 23) find_library(PAHO_MQTTPP_LIBRARY paho-mqttpp3 PATHS /usr/local/lib) find_library(PAHO_MQTT_LIBRARY paho-mqtt3a PATHS /usr/local/lib) -include_directories(/usr/local/include) +# Find OpenCV package +find_package(OpenCV REQUIRED) +find_package(OpenEXR REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) set(SOURCE_FILES src/KobukiDriver/KobukiParser.cpp @@ -20,4 +23,4 @@ set(SOURCE_FILES add_executable(kobuki_control ${SOURCE_FILES}) # Link the static libraries -target_link_libraries(kobuki_control ${PAHO_MQTTPP_LIBRARY} ${PAHO_MQTT_LIBRARY} pthread) \ No newline at end of file +target_link_libraries(kobuki_control ${PAHO_MQTTPP_LIBRARY} ${PAHO_MQTT_LIBRARY} ${OpenCV_LIBS} pthread OpenEXR::OpenEXR) From cf1350a3c04378a8267f74b8cc7a20bdb1542056 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 21:55:51 +0100 Subject: [PATCH 041/107] zeker weten dat data word gestuurd --- src/C++/Driver/src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 3d7e147..604a5bc 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -166,6 +166,7 @@ void logToFile() void sendIndividualKobukiData(const TKobukiData &data) { while (true) { + std::cout << "Kobuki Data wordt gepubliceerd naar kobuki/data/timestamp: " << data.timestamp << std::endl; client.publishMessage("kobuki/data/timestamp", std::to_string(data.timestamp)); client.publishMessage("kobuki/data/BumperCenter", std::to_string(data.BumperCenter)); client.publishMessage("kobuki/data/BumperLeft", std::to_string(data.BumperLeft)); From 52eff9ec399f52dc578feef3e0ccd5be1d8f79bc Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 22:02:36 +0100 Subject: [PATCH 042/107] return JSON response for /data endpoint --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index f69355f..dc68273 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -76,7 +76,7 @@ def move(): @app.route('/data', methods=['GET']) def data(): - return kobuki_message + return jsonify(kobuki_message) @app.route("/database") def database(): From bc5f52922b64931f77f43b09aabd6cb11ff2ce6b Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 19 Dec 2024 22:04:58 +0100 Subject: [PATCH 043/107] removed image for now --- src/Python/flask/web/templates/control.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/templates/control.html b/src/Python/flask/web/templates/control.html index 519402f..02b1ec2 100644 --- a/src/Python/flask/web/templates/control.html +++ b/src/Python/flask/web/templates/control.html @@ -12,7 +12,7 @@
- Kobuki Robot +
From 5dbbad5fff4dab204d1b5591694af0e0fe7ceab0 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Fri, 20 Dec 2024 14:08:45 +0100 Subject: [PATCH 044/107] refactor data display to use table rows for sensor data --- src/Python/flask/web/static/script.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index c71f095..d1c250d 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -37,16 +37,19 @@ document.addEventListener("DOMContentLoaded", function () { // Parse the data and show it on the website async function parseData() { const data = await fetchData(); - const sensorDataContainer = document.getElementById("sensor-data"); + const sensorDataContainer = document.getElementById("sensor-data").querySelector("tbody"); sensorDataContainer.innerHTML = ""; // Clear previous data - // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer - for (const [key, value] of Object.entries(data)) { - const dataElement = document.createElement("p"); - dataElement.textContent = `${key}: ${value}`; - sensorDataContainer.appendChild(dataElement); - } + // For each object in JSON array, create a new row and append it to the sensorDataContainer + data.forEach(sensor => { + const row = document.createElement("tr"); + Object.entries(sensor).forEach(([key, value]) => { + const cell = document.createElement("td"); + cell.textContent = `${key}: ${value}`; + row.appendChild(cell); + }); + sensorDataContainer.appendChild(row); + }); } - // Update the image function updateImage() { var img = document.getElementById("robot-image"); From 2f06927550332c3ade14e2beb80add8db9e7213f Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Fri, 20 Dec 2024 14:10:14 +0100 Subject: [PATCH 045/107] fix: correct interval for fetching sensor data to every 5 seconds --- src/Python/flask/web/static/script.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index d1c250d..625b4c9 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -50,6 +50,9 @@ document.addEventListener("DOMContentLoaded", function () { sensorDataContainer.appendChild(row); }); } + + setInterval(parseData, 1000); + // Update the image function updateImage() { var img = document.getElementById("robot-image"); @@ -57,7 +60,6 @@ document.addEventListener("DOMContentLoaded", function () { } // Fetch and display sensor data every 5 seconds - setInterval(parseData, 1000); // Update the image every 5 seconds setInterval(updateImage, 200); From 490e0536ca85895502bc098c391288db0f40900f Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Fri, 20 Dec 2024 14:31:25 +0100 Subject: [PATCH 046/107] took older version of the fetch --- src/Python/flask/web/static/script.js | 83 ++++++++++----------------- 1 file changed, 29 insertions(+), 54 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 625b4c9..e5b4fee 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,66 +1,41 @@ -document.addEventListener("DOMContentLoaded", function () { - document.querySelectorAll(".btn").forEach((button) => { - button.addEventListener("click", function (event) { - event.preventDefault(); // prevents page refresh +document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", async function(event) { // Maak de functie async + event.preventDefault(); // voorkomt pagina-verversing - // Get the value of the button - const direction = event.target.value; + // Haal de waarde van de knop op + const direction = event.target.value; - fetch("/move", { + try { + const response = await fetch("/move", { method: "POST", headers: { - "Content-Type": "application/json", + "Content-Type": "application/json" }, - body: JSON.stringify({ direction: direction }), - }) - .then((response) => response.json()) - .then((data) => { - script; - console.log("Success:", data); - }) - .catch((error) => { - console.error("Error:", error); - }); - }); - }); - - // Fetch data from the server - async function fetchData() { - try { - const response = await fetch("/data"); + body: JSON.stringify({ direction: direction }) + }); const data = await response.json(); - return data; + console.log("Success:", data); } catch (error) { console.error("Error:", error); } - } - // Parse the data and show it on the website - async function parseData() { + + // Fetch data from the server + async function fetchData() { + const response = await fetch("/data"); + const data = await response.json(); + return data; + } + + // Parse the data and show it on the website const data = await fetchData(); - const sensorDataContainer = document.getElementById("sensor-data").querySelector("tbody"); + const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - // For each object in JSON array, create a new row and append it to the sensorDataContainer - data.forEach(sensor => { - const row = document.createElement("tr"); - Object.entries(sensor).forEach(([key, value]) => { - const cell = document.createElement("td"); - cell.textContent = `${key}: ${value}`; - row.appendChild(cell); - }); - sensorDataContainer.appendChild(row); - }); - } - - setInterval(parseData, 1000); - - // Update the image - function updateImage() { - var img = document.getElementById("robot-image"); - img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching - } - - // Fetch and display sensor data every 5 seconds - - // Update the image every 5 seconds - setInterval(updateImage, 200); + //for each object in json array create a new paragraph element and append it to the sensorDataContainer + for (const [key, value] of Object.entries(data)) { + const dataElement = document.createElement("p"); + dataElement.textContent = `${key}: ${value}`; + sensorDataContainer.appendChild(dataElement); // Voeg het element toe aan de container + } + }); }); + From ae03800c23801cf269b00c91ff094e025499ba09 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 2 Jan 2025 22:47:58 +0100 Subject: [PATCH 047/107] bijna klaar met deelvragen --- teamdocumentatie/Ishak/verslag.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/teamdocumentatie/Ishak/verslag.md b/teamdocumentatie/Ishak/verslag.md index fb8d7b2..641ea32 100644 --- a/teamdocumentatie/Ishak/verslag.md +++ b/teamdocumentatie/Ishak/verslag.md @@ -19,15 +19,21 @@ In dit verslag wordt er gekeken naar de verschillende communicatieprotocollen di ## 1. Wat houdt veilige en betrouwbare communicatie tussen apparaten in? -Als je werkt met IoT-apparaten, is het belangrijk dat de communicatie tussen deze apparaten veilig en betrouwbaar is. Iot-apparaten verzamelen gegeven over de omgeving en communiceert deze tussen apparaten over het internet. Als deze communicatie niet veilig is, kunnen hackers deze gegevens onderscheppen en gebruiken(Ministerie van Algemene Zaken, 2022). Je wilt voorkomen dat hackers toegang krijgen tot gevoelige informatie zoals persoonlijke gegevens of bedrijfsgeheimen. Daarom is het belangrijk dat de communicatie tussen apparaten veilig en betrouwbaar is. +Als je werkt met IoT-apparaten, is het belangrijk dat de communicatie tussen deze apparaten veilig en betrouwbaar is. Iot-apparaten verzamelen gegevens over de omgeving en communiceert deze tussen apparaten over het internet. Als deze communicatie niet veilig is, kunnen hackers deze gegevens onderscheppen en gebruiken(Ministerie van Algemene Zaken, 2022). Je wilt voorkomen dat hackers toegang krijgen tot gevoelige informatie zoals persoonlijke gegevens of bedrijfsgeheimen. Daarom is het belangrijk dat de communicatie tussen apparaten veilig en betrouwbaar is. Een protocol is een set regels die bepalen hoe apparaten met elkaar communiceren. Er zijn verschillende protocollen die gebruikt kunnen worden om veilig en betrouwbaar te communiceren tussen IoT-apparaten. ## 2. Welke protocollen zijn er om veilig en betrouwbaar te communiceren tussen apparaten? -Er zijn verschillende soorten protocollen die +Er zijn verschillende soorten protocollen die gebruikt kunnen worden om veilig en betrouwbaar te communiceren tussen IoT-apparaten. Enkele van de meest gebruikte protocollen zijn: MQTT, HTTP, WebSockets en CoAP. Bij het kiezen van een protocol is het belangrijk om te kijken naar de veiligheid en betrouwbaarheid van het protocol. Bij veiligheid kan je denken aan de mogelijkheid om gegevens te versleutelen en te authenticeren. Bij betrouwbaarheid kan je denken aan de mogelijkheid om gegevens te verzenden en te ontvangen zonder verlies. ## 3. Wat zijn de voor- en nadelen van de verschillende protocollen? +| Protocol | Voordeel | Nadeel | +| ---------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | +| MQTT | Lichtgewicht, betrouwbaar, ondersteunt QoS, heeft geen limit aan afstand | Niet geschikt voor grote bestanden(256mb), kan wat vertraging hebben | +| HTTP | Eenvoudig te implementeren, ondersteunt SSL, ondersteunt REST | Voor elke interactie nieuwe verbinding, | +| WebSockets | Weinig vertraging | Niet geschikt voor lichtgewicht apparaten | +| CoAP | Lichtgewicht, ondersteunt QoS, ondersteunt REST | Minder bekend, minder ondersteuning | ## literatuurlijst From 1fb70107738afc56c00d4ab394aa1d617fcdd3ed Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Fri, 3 Jan 2025 17:07:46 +0100 Subject: [PATCH 048/107] enhance verslag.md with detailed analysis of IoT communication protocols and their reliability --- teamdocumentatie/Ishak/verslag.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/teamdocumentatie/Ishak/verslag.md b/teamdocumentatie/Ishak/verslag.md index 641ea32..0f34dc0 100644 --- a/teamdocumentatie/Ishak/verslag.md +++ b/teamdocumentatie/Ishak/verslag.md @@ -23,17 +23,23 @@ Als je werkt met IoT-apparaten, is het belangrijk dat de communicatie tussen dez ## 2. Welke protocollen zijn er om veilig en betrouwbaar te communiceren tussen apparaten? -Er zijn verschillende soorten protocollen die gebruikt kunnen worden om veilig en betrouwbaar te communiceren tussen IoT-apparaten. Enkele van de meest gebruikte protocollen zijn: MQTT, HTTP, WebSockets en CoAP. Bij het kiezen van een protocol is het belangrijk om te kijken naar de veiligheid en betrouwbaarheid van het protocol. Bij veiligheid kan je denken aan de mogelijkheid om gegevens te versleutelen en te authenticeren. Bij betrouwbaarheid kan je denken aan de mogelijkheid om gegevens te verzenden en te ontvangen zonder verlies. +Er zijn verschillende soorten protocollen die gebruikt kunnen worden om veilig en betrouwbaar te communiceren tussen IoT-apparaten. Enkele van de meest gebruikte protocollen zijn: MQTT, HTTP, WebSockets en CoAP. Bij het kiezen van een protocol is het belangrijk om te kijken naar de veiligheid en betrouwbaarheid van het protocol. Bij veiligheid kan je denken aan de mogelijkheid om gegevens te versleutelen en te authenticeren. Bij betrouwbaarheid kan je denken aan de mogelijkheid om gegevens te verzenden en te ontvangen zonder verlies. Waar ik voornamelijk bij ga opletten is de manier waarop de protocollen omgaan met veiligheid en betrouwbaarheid. Ik zal ook kijken naar de manieren waarop de protocollen omgaan met de verschillende niveaus van kwaliteit van de berichten. ## 3. Wat zijn de voor- en nadelen van de verschillende protocollen? -| Protocol | Voordeel | Nadeel | -| ---------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------- | -| MQTT | Lichtgewicht, betrouwbaar, ondersteunt QoS, heeft geen limit aan afstand | Niet geschikt voor grote bestanden(256mb), kan wat vertraging hebben | -| HTTP | Eenvoudig te implementeren, ondersteunt SSL, ondersteunt REST | Voor elke interactie nieuwe verbinding, | -| WebSockets | Weinig vertraging | Niet geschikt voor lichtgewicht apparaten | -| CoAP | Lichtgewicht, ondersteunt QoS, ondersteunt REST | Minder bekend, minder ondersteuning | +| Protocol | Voordeel | Nadeel | +| ---------- | ------------------------------------------------------- | -------------------------------------------------------------------- | +| MQTT | Lichtgewicht, betrouwbaar, ondersteunt QoS,TCP, TSL/SSL | Niet geschikt voor grote bestanden(256mb), kan wat vertraging hebben | +| HTTP | Eenvoudig te implementeren, TCP | Voor elke interactie nieuwe verbinding, | +| WebSockets | Weinig vertraging, TCP | Niet geschikt voor lichtgewicht apparaten | +| CoAP | Lichtgewicht | Minder bekend, minder ondersteuning, UDP | + +Bij MQTT zij met QoS wat inhoud dat er verschillende niveaus van kwaliteit van de berichten zijn. Dit betekent dat je kunt kiezen hoe betrouwbaar je wilt dat je bericht aankomt. het verschil tussen TCP en UDP is het volgende: TCP is betrouwbaar en UDP is snel. Bij TCP worden de gegevens in de juiste volgorde afgeleverd en wordt er gecontroleerd of de gegevens correct zijn afgeleverd. Bij UDP worden de gegevens sneller afgeleverd, maar is er geen garantie dat de gegevens correct zijn afgeleverd. MQTT maakt hier goed gebruik van in tegenstelling tot CoAP. + +## 4. Conclusie + +Er zijn verschillende protocollen die goed gebruikt kunnen worden voor IoT apparaten. Aangezien voor mijn project veiligheid en betrouwbaarheid op één staat heb ik gekozen voor MQTT. Dit protocol is lichtgewicht, betrouwbaar en ondersteunt verschillende niveaus van kwaliteit van de berichten. Het is ook mogelijk om gegevens te versleutelen en te authenticeren met MQTT. Ik zal geen last krijgen van vertragingen en heb geen grote bestanden die ik moet verzenden. Dit maakt MQTT een goede keuze voor mijn project. ## literatuurlijst From b29a615681e705590843bad0d78caf7ba9bae72c Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Mon, 6 Jan 2025 09:52:50 +0100 Subject: [PATCH 049/107] verslag def versie --- teamdocumentatie/Ishak/image.png | Bin 0 -> 20906 bytes teamdocumentatie/Ishak/verslag.md | 20 +++++++++----------- teamdocumentatie/Ishak/verslag.pdf | Bin 0 -> 65188 bytes 3 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 teamdocumentatie/Ishak/image.png create mode 100644 teamdocumentatie/Ishak/verslag.pdf diff --git a/teamdocumentatie/Ishak/image.png b/teamdocumentatie/Ishak/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4abef9799ac9ae790860c211f7500ce6134764 GIT binary patch literal 20906 zcmce-S5#A77%hrjKtV-Bq^Sr1#b2Wy-+%T zaMC{dLb~M#7?vBCHiinqkF4Dwt09&v%+Dr#0zP=7^y~As9&c5q4fop1i&qB2uho}9 z)m$xITwE+i3kpZ67#BN>H1_1=y%wT?%_#Por3)CxWE@95&AX!d{cMm&qa zzbJQ=Q{!KJaaZi$Ei(NwG5`B7e$M~8`R{}WkDkt-I*u=6zI2}Z_rXQ2|L+}C{z%(_ z`}Y2f$xC!uL3M}N>~3A}Pqt~!%G(N^kiNzn#Ca;rdkPyXI~j<-=-t+F;*XTd3ryJ=Cja(v+FL)$ zRCymJKPCD7EWE{GI;p-OOzaIHer)e|GqO!&4qi$+(L!W%QJp^EJ05GxvZmeZ;brjA zJ#ylU3czO^`8xx}_zAI0`^cb&qn=}iA9k=Pc4w#2ocfCw_@;M4>wl682%ZFW+gvr0l@uX))t*R3-9GCnc8{H?~1+3vi-LcU&m4wD=Y@GH$+|E zPE`BZYCJkT%arlkRy@*50N3}5ZpQ{H+&FAtb3x=Dd1fRW(dl50@3VazUPV5Ec}cLe5;>xTaoFT+VSLL z&cvdezZUm(M;Z@9B`vA%ksUZMkMjNrR%p}&yZ-^hdR^Vj!oS`8%t*UB_g6BS66MOL zd{{-`>&2==2!Ro%%&Wk*-3ST+G2vbB|nI$cDBZ? zq16eTj!ix zC58%$`+2T2ecZ|lMJZ=A2;Z+NJko%M!W74LfX)r)|IOfs%1xic*<87l9aY59$^fE* zTW_Zuo1)lchmW*a!-kzxuwy-Som1tzskW}4MXi7c|#=5~sDzMUWnhBx#7w>Er( zQ-+7!Px(F8N%Dv?_5SpQU~$I&v7KX5`(?CRacKh11^?U2;>0k`HZ0WcdBpBeO_l0* zs7y&(c2KcEm*Euss^3o+?M!+DY*f02;RWVvpLg9EZp%|t>C9!Bp?Lq|mkoD}+{X?y z+&l3ye>;V}NX>`<=?>H(fNPN-4=ys@OS;AVj@zH}Th$lCL!>5D1ScF(zSl%PRj%T}-~xwEy!Q_P@V_ z<0Poqxw#SJjpS=H9s=#wit`xL1MwQtIFm#Lg{74Qdf2(SM!7SyXDNBGaof)JprOL(Bqs zNx=07Gf5?g&6~GWTudBoS10r^H@`D>#uX1r^v&W4 z6d?|GtJZv0oDm234Rt##!ixuAMtX)eD-GXZj8X@Zy*pO>_Ww3EjOnyj+rbfm%KQV? z#<--Sg6)izeQ203VXJr|Y1WUs@$dtkzU{w)c5iOHM%{Dyyr;S6=#Q#m=G3ToVCPsq z=)CETf%u~Ks>SLL8bm@PSenf#7jwJFxDb4RF3cOzfiJ&glj9UFX-!!kt z9Jj|4K#6tQ%{J454Ro&im5(0We#b!u&0K@pImoPuoWbMF(iV%Z>lMfttu3l2otjho zAwdNEℑ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 ubbor;6l`o9DeeuvzZCxgp}W8E-KT Date: Mon, 6 Jan 2025 09:53:01 +0100 Subject: [PATCH 050/107] commentaar naar engels --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 41 ++++++++++----------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 429e7f9..1d82a4e 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -68,7 +68,7 @@ int CKobuki::connect(char *comportT) { HCom = open(comportT, O_RDWR | O_NOCTTY | O_NONBLOCK); if (HCom == -1) { - printf("Kobuki nepripojeny\n"); + printf("Kobuki connected\n"); return HCom; } else { set_interface_attribs2(HCom, B115200, @@ -89,7 +89,7 @@ int CKobuki::connect(char *comportT) { tcsetattr(HCom, TCSANOW, &settings); // apply the settings*/ tcflush(HCom, TCOFLUSH); - printf("Kobuki pripojeny\n"); + printf("Kobuki connected\n"); return HCom; } } @@ -99,27 +99,26 @@ unsigned char *CKobuki::readKobukiMessage() { ssize_t Pocet; buffer[0] = 0; unsigned char *null_buffer(0); - // citame kym nezachytime zaciatok spravy + + // Read until the start of the message is detected do { Pocet = read(HCom, buffer, 1); } while (buffer[0] != 0xAA); - // mame zaciatok spravy (asi) + + // We have the start of the message (possibly) if (Pocet == 1 && buffer[0] == 0xAA) { - // citame dalsi byte + // Read the next byte do { - Pocet = read(HCom, buffer, 1); + } while (Pocet != 1); // On Linux: -1, on Windows: 0 - } while (Pocet != 1); // na linuxe -1 na windowse 0 - - // a ak je to druhy byte hlavicky + // If it is the second byte of the header if (Pocet == 1 && buffer[0] == 0x55) { - // precitame dlzku + // Read the length Pocet = read(HCom, buffer, 1); - // ReadFile(hCom, buffer, 1, &Pocet, NULL); if (Pocet == 1) { - // mame dlzku.. nastavime vektor a precitame ho cely + // We have the length; initialize a buffer and read the entire message int readLenght = buffer[0]; unsigned char *outputBuffer = (unsigned char *)calloc(readLenght + 4, sizeof(char)); @@ -134,7 +133,7 @@ unsigned char *CKobuki::readKobukiMessage() { pct = pct + (Pocet == -1 ? 0 : Pocet); } while (pct != (readLenght + 1)); - // tu si mozeme ceknut co chodi zo serial intefejsu Kobukiho + // Here we can check what data is received from the Kobuki's serial interface // for(int i=0;i Date: Tue, 7 Jan 2025 14:28:06 +0100 Subject: [PATCH 051/107] functions for auto reconnect with pi and kobuki --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 4 + src/C++/Driver/src/KobukiDriver/CKobuki.h | 1 + src/C++/Driver/src/main.cpp | 611 +++++++++++--------- 3 files changed, 345 insertions(+), 271 deletions(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 1d82a4e..4636a5a 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -94,6 +94,10 @@ int CKobuki::connect(char *comportT) { } } +bool CKobuki::isConnected() { + return (HCom != -1 && tcflush(HCom, TCOFLUSH) == 0); +} + unsigned char *CKobuki::readKobukiMessage() { unsigned char buffer[1]; ssize_t Pocet; diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.h b/src/C++/Driver/src/KobukiDriver/CKobuki.h index 0567df1..31db87e 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.h +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.h @@ -80,6 +80,7 @@ public: void robotSafety(); //overload void sendNullMessage(); bool safetyActive = false; + bool isConnected(); KobukiParser parser; diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 604a5bc..dd57c19 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -1,318 +1,387 @@ -#include -#include -#include -#include "MQTT/MqttClient.h" #include "KobukiDriver/CKobuki.h" -#include +#include "MQTT/MqttClient.h" +#include +#include #include +#include +#include using namespace std; using namespace cv; CKobuki robot; + std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); -//ip, clientID, username, password -MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object +void reconnectKobuki(); +void monitorKobukiConnection(); +// ip, clientID, username, password +MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", + "rpiwachtwoordofzo"); // create a client object std::string message = "stop"; std::string serializeKobukiData(const TKobukiData &data); void sendKobukiData(TKobukiData &data); -void setup() -{ - unsigned char *null_ptr(0); - robot.startCommunication("/dev/ttyUSB0", true, null_ptr); - //connect mqtt server and sub to commands +void setup() { + unsigned char *null_ptr(0); + robot.startCommunication("/dev/ttyUSB0", true, null_ptr); + // connect mqtt server and sub to commands - client.connect(); - client.subscribe("home/commands"); + client.connect(); + client.subscribe("home/commands"); } -int main() -{ - setup(); - std::thread image (CapnSend); - std::thread safety([&]() { robot.robotSafety(&message); }); - std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); - - while(true){ - std::string message = readMQTT(); - if (!message.empty()){ - parseMQTT(message); - } - - } +int main() { + setup(); + reconnectKobuki(); - sendMqtt.join(); - safety.join(); - image.join(); + std::thread connectionMonitor(monitorKobukiConnection); + std::thread image(CapnSend); + std::thread safety([&]() { robot.robotSafety(&message); }); + std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); + + while (true) { + std::string message = readMQTT(); + if (!message.empty()) { + parseMQTT(message); + } + } + + sendMqtt.join(); + safety.join(); + image.join(); } -std::string readMQTT() -{ - static std::string lastMessage; - - std::string message = client.getLastMessage(); - if (!message.empty() && message != lastMessage) - { - std::cout << "MQTT Message: " << message << std::endl; - lastMessage = message; +void reconnectKobuki() { + while (true) { + if (robot.startCommunication("/dev/ttyUSB0") != -1) { + std::cout << "Kobuki opnieuw verbonden!" << std::endl; + break; // Verlaat de loop als de verbinding succesvol is + } else { + std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(3)); } - - // Add a small delay to avoid busy-waiting - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - return message; + } } -void parseMQTT(std::string message) -{ - if (message == "up") - { - robot.forward(350); - } - else if (message == "left") - { - robot.setRotationSpeed(4); - } - else if (message == "right") - { - robot.setRotationSpeed(-4); - } - else if (message == "down") - { - robot.forward(-350); - } - else if (message == "stop") - { - robot.sendNullMessage(); - robot.sendNullMessage(); - } - else if (message == "estop") - { - robot.forward(-400); - } - else - { - std::cout << "Invalid command" << std::endl; +void monitorKobukiConnection() { + while (true) { + // Check regelmatig of de verbinding actief is + if (!robot.isConnected()) { + std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; + reconnectKobuki(); } + std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden + } } -void logToFile() -{ - while (true) - { - TKobukiData robotData = robot.parser.data; - std::ofstream outputFile("log", std::ios_base::app); // Open file in append mode to not overwrite own content - if (outputFile.is_open()) - { // check if the file was opened successfully - // Get current time - std::time_t now = std::time(nullptr); - outputFile << "Timestamp: " << std::ctime(&now); - // Write data to the file - outputFile << "analogInputCh0: " << robotData.analogInputCh0 << "\n"; - outputFile << "analogInputCh1: " << robotData.analogInputCh1 << "\n"; - outputFile << "analogInputCh2: " << robotData.analogInputCh2 << "\n"; - outputFile << "analogInputCh3: " << robotData.analogInputCh3 << "\n"; - outputFile << "digitalInput: " << robotData.digitalInput << "\n"; - outputFile << "timestamp: " << robotData.timestamp << "\n"; - outputFile << "BumperCenter: " << robotData.BumperCenter << "\n"; - outputFile << "BumperLeft: " << robotData.BumperLeft << "\n"; - outputFile << "BumperRight: " << robotData.BumperRight << "\n"; - outputFile << "WheelDropLeft: " << robotData.WheelDropLeft << "\n"; - outputFile << "WheelDropRight: " << robotData.WheelDropRight << "\n"; - outputFile << "CliffCenter: " << robotData.CliffCenter << "\n"; - outputFile << "CliffLeft: " << robotData.CliffLeft << "\n"; - outputFile << "CliffRight: " << robotData.CliffRight << "\n"; - outputFile << "EncoderLeft: " << robotData.EncoderLeft << "\n"; - outputFile << "EncoderRight: " << robotData.EncoderRight << "\n"; - outputFile << "PWMleft: " << robotData.PWMleft << "\n"; - outputFile << "PWMright: " << robotData.PWMright << "\n"; - outputFile << "ButtonPress: " << robotData.ButtonPress1 << "\n"; - outputFile << "ButtonPress: " << robotData.ButtonPress2 << "\n"; - outputFile << "ButtonPress: " << robotData.ButtonPress3 << "\n"; - outputFile << "Charger: " << robotData.Charger << "\n"; - outputFile << "Battery: " << robotData.Battery << "\n"; - outputFile << "overCurrent: " << robotData.overCurrent << "\n"; - outputFile << "IRSensorRight: " << robotData.IRSensorRight << "\n"; - outputFile << "IRSensorCenter: " << robotData.IRSensorCenter << "\n"; - outputFile << "IRSensorLeft: " << robotData.IRSensorLeft << "\n"; - outputFile << "GyroAngle: " << robotData.GyroAngle << "\n"; - outputFile << "GyroAngleRate: " << robotData.GyroAngleRate << "\n"; - outputFile << "CliffSensorRight: " << robotData.CliffSensorRight << "\n"; - outputFile << "CliffSensorCenter: " << robotData.CliffSensorCenter << "\n"; - outputFile << "CliffSensorLeft: " << robotData.CliffSensorLeft << "\n"; - outputFile << "wheelCurrentLeft: " << robotData.wheelCurrentLeft << "\n"; - outputFile << "wheelCurrentRight: " << robotData.wheelCurrentRight << "\n"; - outputFile << "frameId: " << robotData.frameId << "\n"; - outputFile << "HardwareVersionPatch: " << robotData.extraInfo.HardwareVersionPatch << "\n"; - outputFile << "HardwareVersionMinor: " << robotData.extraInfo.HardwareVersionMinor << "\n"; - outputFile << "HardwareVersionMajor: " << robotData.extraInfo.HardwareVersionMajor << "\n"; - outputFile << "FirmwareVersionPatch: " << robotData.extraInfo.FirmwareVersionPatch << "\n"; - outputFile << "FirmwareVersionMinor: " << robotData.extraInfo.FirmwareVersionMinor << "\n"; - outputFile << "FirmwareVersionMajor: " << robotData.extraInfo.FirmwareVersionMajor << "\n"; - outputFile << "UDID0: " << robotData.extraInfo.UDID0 << "\n"; - outputFile << "UDID1: " << robotData.extraInfo.UDID1 << "\n"; - outputFile << "UDID2: " << robotData.extraInfo.UDID2 << "\n"; - outputFile.close(); - } - else - { - std::cerr << "Error opening file\n"; - } - std::this_thread::sleep_for(std::chrono::seconds(2)); // Sleep for 2 seconds +std::string readMQTT() { + static std::string lastMessage; + + std::string message = client.getLastMessage(); + if (!message.empty() && message != lastMessage) { + std::cout << "MQTT Message: " << message << std::endl; + lastMessage = message; + } + + // Add a small delay to avoid busy-waiting + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return message; +} + +void parseMQTT(std::string message) { + if (message == "up") { + robot.forward(350); + } else if (message == "left") { + robot.setRotationSpeed(4); + } else if (message == "right") { + robot.setRotationSpeed(-4); + } else if (message == "down") { + robot.forward(-350); + } else if (message == "stop") { + robot.sendNullMessage(); + robot.sendNullMessage(); + } else if (message == "estop") { + robot.forward(-400); + } else { + std::cout << "Invalid command" << std::endl; + } +} + +void logToFile() { + while (true) { + TKobukiData robotData = robot.parser.data; + std::ofstream outputFile("log", + std::ios_base::app); // Open file in append mode to + // not overwrite own content + if (outputFile.is_open()) { // check if the file was opened successfully + // Get current time + std::time_t now = std::time(nullptr); + outputFile << "Timestamp: " << std::ctime(&now); + // Write data to the file + outputFile << "analogInputCh0: " << robotData.analogInputCh0 << "\n"; + outputFile << "analogInputCh1: " << robotData.analogInputCh1 << "\n"; + outputFile << "analogInputCh2: " << robotData.analogInputCh2 << "\n"; + outputFile << "analogInputCh3: " << robotData.analogInputCh3 << "\n"; + outputFile << "digitalInput: " << robotData.digitalInput << "\n"; + outputFile << "timestamp: " << robotData.timestamp << "\n"; + outputFile << "BumperCenter: " << robotData.BumperCenter << "\n"; + outputFile << "BumperLeft: " << robotData.BumperLeft << "\n"; + outputFile << "BumperRight: " << robotData.BumperRight << "\n"; + outputFile << "WheelDropLeft: " << robotData.WheelDropLeft << "\n"; + outputFile << "WheelDropRight: " << robotData.WheelDropRight << "\n"; + outputFile << "CliffCenter: " << robotData.CliffCenter << "\n"; + outputFile << "CliffLeft: " << robotData.CliffLeft << "\n"; + outputFile << "CliffRight: " << robotData.CliffRight << "\n"; + outputFile << "EncoderLeft: " << robotData.EncoderLeft << "\n"; + outputFile << "EncoderRight: " << robotData.EncoderRight << "\n"; + outputFile << "PWMleft: " << robotData.PWMleft << "\n"; + outputFile << "PWMright: " << robotData.PWMright << "\n"; + outputFile << "ButtonPress: " << robotData.ButtonPress1 << "\n"; + outputFile << "ButtonPress: " << robotData.ButtonPress2 << "\n"; + outputFile << "ButtonPress: " << robotData.ButtonPress3 << "\n"; + outputFile << "Charger: " << robotData.Charger << "\n"; + outputFile << "Battery: " << robotData.Battery << "\n"; + outputFile << "overCurrent: " << robotData.overCurrent << "\n"; + outputFile << "IRSensorRight: " << robotData.IRSensorRight << "\n"; + outputFile << "IRSensorCenter: " << robotData.IRSensorCenter << "\n"; + outputFile << "IRSensorLeft: " << robotData.IRSensorLeft << "\n"; + outputFile << "GyroAngle: " << robotData.GyroAngle << "\n"; + outputFile << "GyroAngleRate: " << robotData.GyroAngleRate << "\n"; + outputFile << "CliffSensorRight: " << robotData.CliffSensorRight << "\n"; + outputFile << "CliffSensorCenter: " << robotData.CliffSensorCenter + << "\n"; + outputFile << "CliffSensorLeft: " << robotData.CliffSensorLeft << "\n"; + outputFile << "wheelCurrentLeft: " << robotData.wheelCurrentLeft << "\n"; + outputFile << "wheelCurrentRight: " << robotData.wheelCurrentRight + << "\n"; + outputFile << "frameId: " << robotData.frameId << "\n"; + outputFile << "HardwareVersionPatch: " + << robotData.extraInfo.HardwareVersionPatch << "\n"; + outputFile << "HardwareVersionMinor: " + << robotData.extraInfo.HardwareVersionMinor << "\n"; + outputFile << "HardwareVersionMajor: " + << robotData.extraInfo.HardwareVersionMajor << "\n"; + outputFile << "FirmwareVersionPatch: " + << robotData.extraInfo.FirmwareVersionPatch << "\n"; + outputFile << "FirmwareVersionMinor: " + << robotData.extraInfo.FirmwareVersionMinor << "\n"; + outputFile << "FirmwareVersionMajor: " + << robotData.extraInfo.FirmwareVersionMajor << "\n"; + outputFile << "UDID0: " << robotData.extraInfo.UDID0 << "\n"; + outputFile << "UDID1: " << robotData.extraInfo.UDID1 << "\n"; + outputFile << "UDID2: " << robotData.extraInfo.UDID2 << "\n"; + outputFile.close(); + } else { + std::cerr << "Error opening file\n"; } + + std::this_thread::sleep_for(std::chrono::seconds(2)); // Sleep for 2 seconds + } } void sendIndividualKobukiData(const TKobukiData &data) { - while (true) { - std::cout << "Kobuki Data wordt gepubliceerd naar kobuki/data/timestamp: " << data.timestamp << std::endl; - client.publishMessage("kobuki/data/timestamp", std::to_string(data.timestamp)); - client.publishMessage("kobuki/data/BumperCenter", std::to_string(data.BumperCenter)); - client.publishMessage("kobuki/data/BumperLeft", std::to_string(data.BumperLeft)); - client.publishMessage("kobuki/data/BumperRight", std::to_string(data.BumperRight)); - client.publishMessage("kobuki/data/WheelDropLeft", std::to_string(data.WheelDropLeft)); - client.publishMessage("kobuki/data/WheelDropRight", std::to_string(data.WheelDropRight)); - client.publishMessage("kobuki/data/CliffCenter", std::to_string(data.CliffCenter)); - client.publishMessage("kobuki/data/CliffLeft", std::to_string(data.CliffLeft)); - client.publishMessage("kobuki/data/CliffRight", std::to_string(data.CliffRight)); - client.publishMessage("kobuki/data/EncoderLeft", std::to_string(data.EncoderLeft)); - client.publishMessage("kobuki/data/EncoderRight", std::to_string(data.EncoderRight)); - client.publishMessage("kobuki/data/PWMleft", std::to_string(data.PWMleft)); - client.publishMessage("kobuki/data/PWMright", std::to_string(data.PWMright)); - client.publishMessage("kobuki/data/ButtonPress1", std::to_string(data.ButtonPress1)); - client.publishMessage("kobuki/data/ButtonPress2", std::to_string(data.ButtonPress2)); - client.publishMessage("kobuki/data/ButtonPress3", std::to_string(data.ButtonPress3)); - client.publishMessage("kobuki/data/Charger", std::to_string(data.Charger)); - client.publishMessage("kobuki/data/Battery", std::to_string(data.Battery)); - client.publishMessage("kobuki/data/overCurrent", std::to_string(data.overCurrent)); - client.publishMessage("kobuki/data/IRSensorRight", std::to_string(data.IRSensorRight)); - client.publishMessage("kobuki/data/IRSensorCenter", std::to_string(data.IRSensorCenter)); - client.publishMessage("kobuki/data/IRSensorLeft", std::to_string(data.IRSensorLeft)); - client.publishMessage("kobuki/data/GyroAngle", std::to_string(data.GyroAngle)); - client.publishMessage("kobuki/data/GyroAngleRate", std::to_string(data.GyroAngleRate)); - client.publishMessage("kobuki/data/CliffSensorRight", std::to_string(data.CliffSensorRight)); - client.publishMessage("kobuki/data/CliffSensorCenter", std::to_string(data.CliffSensorCenter)); - client.publishMessage("kobuki/data/CliffSensorLeft", std::to_string(data.CliffSensorLeft)); - client.publishMessage("kobuki/data/wheelCurrentLeft", std::to_string(data.wheelCurrentLeft)); - client.publishMessage("kobuki/data/wheelCurrentRight", std::to_string(data.wheelCurrentRight)); - client.publishMessage("kobuki/data/digitalInput", std::to_string(data.digitalInput)); - client.publishMessage("kobuki/data/analogInputCh0", std::to_string(data.analogInputCh0)); - client.publishMessage("kobuki/data/analogInputCh1", std::to_string(data.analogInputCh1)); - client.publishMessage("kobuki/data/analogInputCh2", std::to_string(data.analogInputCh2)); - client.publishMessage("kobuki/data/analogInputCh3", std::to_string(data.analogInputCh3)); - client.publishMessage("kobuki/data/frameId", std::to_string(data.frameId)); - client.publishMessage("kobuki/data/extraInfo/HardwareVersionPatch", std::to_string(data.extraInfo.HardwareVersionPatch)); - client.publishMessage("kobuki/data/extraInfo/HardwareVersionMinor", std::to_string(data.extraInfo.HardwareVersionMinor)); - client.publishMessage("kobuki/data/extraInfo/HardwareVersionMajor", std::to_string(data.extraInfo.HardwareVersionMajor)); - client.publishMessage("kobuki/data/extraInfo/FirmwareVersionPatch", std::to_string(data.extraInfo.FirmwareVersionPatch)); - client.publishMessage("kobuki/data/extraInfo/FirmwareVersionMinor", std::to_string(data.extraInfo.FirmwareVersionMinor)); - client.publishMessage("kobuki/data/extraInfo/FirmwareVersionMajor", std::to_string(data.extraInfo.FirmwareVersionMajor)); - client.publishMessage("kobuki/data/extraInfo/UDID0", std::to_string(data.extraInfo.UDID0)); - client.publishMessage("kobuki/data/extraInfo/UDID1", std::to_string(data.extraInfo.UDID1)); - client.publishMessage("kobuki/data/extraInfo/UDID2", std::to_string(data.extraInfo.UDID2)); + while (true) { + std::cout << "Kobuki Data wordt gepubliceerd naar kobuki/data/timestamp: " + << data.timestamp << std::endl; + client.publishMessage("kobuki/data/timestamp", + std::to_string(data.timestamp)); + client.publishMessage("kobuki/data/BumperCenter", + std::to_string(data.BumperCenter)); + client.publishMessage("kobuki/data/BumperLeft", + std::to_string(data.BumperLeft)); + client.publishMessage("kobuki/data/BumperRight", + std::to_string(data.BumperRight)); + client.publishMessage("kobuki/data/WheelDropLeft", + std::to_string(data.WheelDropLeft)); + client.publishMessage("kobuki/data/WheelDropRight", + std::to_string(data.WheelDropRight)); + client.publishMessage("kobuki/data/CliffCenter", + std::to_string(data.CliffCenter)); + client.publishMessage("kobuki/data/CliffLeft", + std::to_string(data.CliffLeft)); + client.publishMessage("kobuki/data/CliffRight", + std::to_string(data.CliffRight)); + client.publishMessage("kobuki/data/EncoderLeft", + std::to_string(data.EncoderLeft)); + client.publishMessage("kobuki/data/EncoderRight", + std::to_string(data.EncoderRight)); + client.publishMessage("kobuki/data/PWMleft", std::to_string(data.PWMleft)); + client.publishMessage("kobuki/data/PWMright", + std::to_string(data.PWMright)); + client.publishMessage("kobuki/data/ButtonPress1", + std::to_string(data.ButtonPress1)); + client.publishMessage("kobuki/data/ButtonPress2", + std::to_string(data.ButtonPress2)); + client.publishMessage("kobuki/data/ButtonPress3", + std::to_string(data.ButtonPress3)); + client.publishMessage("kobuki/data/Charger", std::to_string(data.Charger)); + client.publishMessage("kobuki/data/Battery", std::to_string(data.Battery)); + client.publishMessage("kobuki/data/overCurrent", + std::to_string(data.overCurrent)); + client.publishMessage("kobuki/data/IRSensorRight", + std::to_string(data.IRSensorRight)); + client.publishMessage("kobuki/data/IRSensorCenter", + std::to_string(data.IRSensorCenter)); + client.publishMessage("kobuki/data/IRSensorLeft", + std::to_string(data.IRSensorLeft)); + client.publishMessage("kobuki/data/GyroAngle", + std::to_string(data.GyroAngle)); + client.publishMessage("kobuki/data/GyroAngleRate", + std::to_string(data.GyroAngleRate)); + client.publishMessage("kobuki/data/CliffSensorRight", + std::to_string(data.CliffSensorRight)); + client.publishMessage("kobuki/data/CliffSensorCenter", + std::to_string(data.CliffSensorCenter)); + client.publishMessage("kobuki/data/CliffSensorLeft", + std::to_string(data.CliffSensorLeft)); + client.publishMessage("kobuki/data/wheelCurrentLeft", + std::to_string(data.wheelCurrentLeft)); + client.publishMessage("kobuki/data/wheelCurrentRight", + std::to_string(data.wheelCurrentRight)); + client.publishMessage("kobuki/data/digitalInput", + std::to_string(data.digitalInput)); + client.publishMessage("kobuki/data/analogInputCh0", + std::to_string(data.analogInputCh0)); + client.publishMessage("kobuki/data/analogInputCh1", + std::to_string(data.analogInputCh1)); + client.publishMessage("kobuki/data/analogInputCh2", + std::to_string(data.analogInputCh2)); + client.publishMessage("kobuki/data/analogInputCh3", + std::to_string(data.analogInputCh3)); + client.publishMessage("kobuki/data/frameId", std::to_string(data.frameId)); + client.publishMessage("kobuki/data/extraInfo/HardwareVersionPatch", + std::to_string(data.extraInfo.HardwareVersionPatch)); + client.publishMessage("kobuki/data/extraInfo/HardwareVersionMinor", + std::to_string(data.extraInfo.HardwareVersionMinor)); + client.publishMessage("kobuki/data/extraInfo/HardwareVersionMajor", + std::to_string(data.extraInfo.HardwareVersionMajor)); + client.publishMessage("kobuki/data/extraInfo/FirmwareVersionPatch", + std::to_string(data.extraInfo.FirmwareVersionPatch)); + client.publishMessage("kobuki/data/extraInfo/FirmwareVersionMinor", + std::to_string(data.extraInfo.FirmwareVersionMinor)); + client.publishMessage("kobuki/data/extraInfo/FirmwareVersionMajor", + std::to_string(data.extraInfo.FirmwareVersionMajor)); + client.publishMessage("kobuki/data/extraInfo/UDID0", + std::to_string(data.extraInfo.UDID0)); + client.publishMessage("kobuki/data/extraInfo/UDID1", + std::to_string(data.extraInfo.UDID1)); + client.publishMessage("kobuki/data/extraInfo/UDID2", + std::to_string(data.extraInfo.UDID2)); - if (!data.gyroData.empty()) { - const auto& latestGyro = data.gyroData.back(); - client.publishMessage("kobuki/data/gyroData/x", std::to_string(latestGyro.x)); - client.publishMessage("kobuki/data/gyroData/y", std::to_string(latestGyro.y)); - client.publishMessage("kobuki/data/gyroData/z", std::to_string(latestGyro.z)); - } - - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + if (!data.gyroData.empty()) { + const auto &latestGyro = data.gyroData.back(); + client.publishMessage("kobuki/data/gyroData/x", + std::to_string(latestGyro.x)); + client.publishMessage("kobuki/data/gyroData/y", + std::to_string(latestGyro.y)); + client.publishMessage("kobuki/data/gyroData/z", + std::to_string(latestGyro.z)); } + + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } } std::string serializeKobukiData(const TKobukiData &data) { - std::string json = "{\"timestamp\":" + std::to_string(data.timestamp) + - ",\"BumperCenter\":" + std::to_string(data.BumperCenter) + - ",\"BumperLeft\":" + std::to_string(data.BumperLeft) + - ",\"BumperRight\":" + std::to_string(data.BumperRight) + - ",\"WheelDropLeft\":" + std::to_string(data.WheelDropLeft) + - ",\"WheelDropRight\":" + std::to_string(data.WheelDropRight) + - ",\"CliffCenter\":" + std::to_string(data.CliffCenter) + - ",\"CliffLeft\":" + std::to_string(data.CliffLeft) + - ",\"CliffRight\":" + std::to_string(data.CliffRight) + - ",\"EncoderLeft\":" + std::to_string(data.EncoderLeft) + - ",\"EncoderRight\":" + std::to_string(data.EncoderRight) + - ",\"PWMleft\":" + std::to_string(data.PWMleft) + - ",\"PWMright\":" + std::to_string(data.PWMright) + - ",\"ButtonPress1\":" + std::to_string(data.ButtonPress1) + - ",\"ButtonPress2\":" + std::to_string(data.ButtonPress2) + - ",\"ButtonPress3\":" + std::to_string(data.ButtonPress3) + - ",\"Charger\":" + std::to_string(data.Charger) + - ",\"Battery\":" + std::to_string(data.Battery) + - ",\"overCurrent\":" + std::to_string(data.overCurrent) + - ",\"IRSensorRight\":" + std::to_string(data.IRSensorRight) + - ",\"IRSensorCenter\":" + std::to_string(data.IRSensorCenter) + - ",\"IRSensorLeft\":" + std::to_string(data.IRSensorLeft) + - ",\"GyroAngle\":" + std::to_string(data.GyroAngle) + - ",\"GyroAngleRate\":" + std::to_string(data.GyroAngleRate) + - ",\"CliffSensorRight\":" + std::to_string(data.CliffSensorRight) + - ",\"CliffSensorCenter\":" + std::to_string(data.CliffSensorCenter) + - ",\"CliffSensorLeft\":" + std::to_string(data.CliffSensorLeft) + - ",\"wheelCurrentLeft\":" + std::to_string(data.wheelCurrentLeft) + - ",\"wheelCurrentRight\":" + std::to_string(data.wheelCurrentRight) + - ",\"digitalInput\":" + std::to_string(data.digitalInput) + - ",\"analogInputCh0\":" + std::to_string(data.analogInputCh0) + - ",\"analogInputCh1\":" + std::to_string(data.analogInputCh1) + - ",\"analogInputCh2\":" + std::to_string(data.analogInputCh2) + - ",\"analogInputCh3\":" + std::to_string(data.analogInputCh3) + - ",\"frameId\":" + std::to_string(data.frameId) + - ",\"extraInfo\":{\"HardwareVersionPatch\":" + std::to_string(data.extraInfo.HardwareVersionPatch) + - ",\"HardwareVersionMinor\":" + std::to_string(data.extraInfo.HardwareVersionMinor) + - ",\"HardwareVersionMajor\":" + std::to_string(data.extraInfo.HardwareVersionMajor) + - ",\"FirmwareVersionPatch\":" + std::to_string(data.extraInfo.FirmwareVersionPatch) + - ",\"FirmwareVersionMinor\":" + std::to_string(data.extraInfo.FirmwareVersionMinor) + - ",\"FirmwareVersionMajor\":" + std::to_string(data.extraInfo.FirmwareVersionMajor) + - ",\"UDID0\":" + std::to_string(data.extraInfo.UDID0) + - ",\"UDID1\":" + std::to_string(data.extraInfo.UDID1) + - ",\"UDID2\":" + std::to_string(data.extraInfo.UDID2) + "},\"gyroData\":["; + std::string json = + "{\"timestamp\":" + std::to_string(data.timestamp) + + ",\"BumperCenter\":" + std::to_string(data.BumperCenter) + + ",\"BumperLeft\":" + std::to_string(data.BumperLeft) + + ",\"BumperRight\":" + std::to_string(data.BumperRight) + + ",\"WheelDropLeft\":" + std::to_string(data.WheelDropLeft) + + ",\"WheelDropRight\":" + std::to_string(data.WheelDropRight) + + ",\"CliffCenter\":" + std::to_string(data.CliffCenter) + + ",\"CliffLeft\":" + std::to_string(data.CliffLeft) + + ",\"CliffRight\":" + std::to_string(data.CliffRight) + + ",\"EncoderLeft\":" + std::to_string(data.EncoderLeft) + + ",\"EncoderRight\":" + std::to_string(data.EncoderRight) + + ",\"PWMleft\":" + std::to_string(data.PWMleft) + + ",\"PWMright\":" + std::to_string(data.PWMright) + + ",\"ButtonPress1\":" + std::to_string(data.ButtonPress1) + + ",\"ButtonPress2\":" + std::to_string(data.ButtonPress2) + + ",\"ButtonPress3\":" + std::to_string(data.ButtonPress3) + + ",\"Charger\":" + std::to_string(data.Charger) + + ",\"Battery\":" + std::to_string(data.Battery) + + ",\"overCurrent\":" + std::to_string(data.overCurrent) + + ",\"IRSensorRight\":" + std::to_string(data.IRSensorRight) + + ",\"IRSensorCenter\":" + std::to_string(data.IRSensorCenter) + + ",\"IRSensorLeft\":" + std::to_string(data.IRSensorLeft) + + ",\"GyroAngle\":" + std::to_string(data.GyroAngle) + + ",\"GyroAngleRate\":" + std::to_string(data.GyroAngleRate) + + ",\"CliffSensorRight\":" + std::to_string(data.CliffSensorRight) + + ",\"CliffSensorCenter\":" + std::to_string(data.CliffSensorCenter) + + ",\"CliffSensorLeft\":" + std::to_string(data.CliffSensorLeft) + + ",\"wheelCurrentLeft\":" + std::to_string(data.wheelCurrentLeft) + + ",\"wheelCurrentRight\":" + std::to_string(data.wheelCurrentRight) + + ",\"digitalInput\":" + std::to_string(data.digitalInput) + + ",\"analogInputCh0\":" + std::to_string(data.analogInputCh0) + + ",\"analogInputCh1\":" + std::to_string(data.analogInputCh1) + + ",\"analogInputCh2\":" + std::to_string(data.analogInputCh2) + + ",\"analogInputCh3\":" + std::to_string(data.analogInputCh3) + + ",\"frameId\":" + std::to_string(data.frameId) + + ",\"extraInfo\":{\"HardwareVersionPatch\":" + + std::to_string(data.extraInfo.HardwareVersionPatch) + + ",\"HardwareVersionMinor\":" + + std::to_string(data.extraInfo.HardwareVersionMinor) + + ",\"HardwareVersionMajor\":" + + std::to_string(data.extraInfo.HardwareVersionMajor) + + ",\"FirmwareVersionPatch\":" + + std::to_string(data.extraInfo.FirmwareVersionPatch) + + ",\"FirmwareVersionMinor\":" + + std::to_string(data.extraInfo.FirmwareVersionMinor) + + ",\"FirmwareVersionMajor\":" + + std::to_string(data.extraInfo.FirmwareVersionMajor) + + ",\"UDID0\":" + std::to_string(data.extraInfo.UDID0) + + ",\"UDID1\":" + std::to_string(data.extraInfo.UDID1) + + ",\"UDID2\":" + std::to_string(data.extraInfo.UDID2) + "},\"gyroData\":["; - if (!data.gyroData.empty()) { - const auto& latestGyro = data.gyroData.back(); - json += "{\"x\":" + std::to_string(latestGyro.x) + - ",\"y\":" + std::to_string(latestGyro.y) + - ",\"z\":" + std::to_string(latestGyro.z) + "}"; - } + if (!data.gyroData.empty()) { + const auto &latestGyro = data.gyroData.back(); + json += "{\"x\":" + std::to_string(latestGyro.x) + + ",\"y\":" + std::to_string(latestGyro.y) + + ",\"z\":" + std::to_string(latestGyro.z) + "}"; + } - json += "]}"; - return json; + json += "]}"; + return json; } -//create extra function to send the message every 100ms -//needed it so it can be threaded +// create extra function to send the message every 100ms +// needed it so it can be threaded void sendKobukiData(TKobukiData &data) { - while (true) { - client.publishMessage("kobuki/data", serializeKobukiData(data)); - std::cout << "Sent data" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - } + while (true) { + client.publishMessage("kobuki/data", serializeKobukiData(data)); + std::cout << "Sent data" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } } void CapnSend() { - VideoCapture cap(0); - if (!cap.isOpened()) { - cerr << "Error: Could not open camera" << endl; - return; + VideoCapture cap(0); + if (!cap.isOpened()) { + cerr << "Error: Could not open camera" << endl; + return; + } + + Mat frame; + while (true) { + cap >> frame; // Capture a new image frame + if (frame.empty()) { + cerr << "Error: Could not capture image" << endl; + continue; } - Mat frame; - while (true) { - cap >> frame; // Capture a new image frame - if (frame.empty()) { - cerr << "Error: Could not capture image" << endl; - continue; - } + // Convert the image to a byte array + vector buf; + imencode(".jpg", frame, buf); + auto *enc_msg = reinterpret_cast(buf.data()); - // Convert the image to a byte array - vector buf; - imencode(".jpg", frame, buf); - auto* enc_msg = reinterpret_cast(buf.data()); + // Publish the image data + client.publishMessage("kobuki/cam", string(enc_msg, enc_msg + buf.size())); + cout << "Sent image" << endl; - // Publish the image data - client.publishMessage("kobuki/cam", string(enc_msg, enc_msg + buf.size())); - cout << "Sent image" << endl; - - std::this_thread::sleep_for(std::chrono::milliseconds(300)); // Send image every 1000ms - } + std::this_thread::sleep_for( + std::chrono::milliseconds(300)); // Send image every 1000ms + } } \ No newline at end of file From 56f085b73db3882fd0d026d314844e422f979706 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 7 Jan 2025 14:33:12 +0100 Subject: [PATCH 052/107] added parameters --- src/C++/Driver/src/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index dd57c19..df4cac1 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -53,11 +53,12 @@ int main() { } void reconnectKobuki() { + unsigned char *null_ptr(0); while (true) { - if (robot.startCommunication("/dev/ttyUSB0") != -1) { + if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { std::cout << "Kobuki opnieuw verbonden!" << std::endl; break; // Verlaat de loop als de verbinding succesvol is - } else { + } else { std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(3)); } From 806bb166626dc69af211d942725106276ae6a9fe Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 7 Jan 2025 14:38:38 +0100 Subject: [PATCH 053/107] changed startcom function --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 3 ++- src/C++/Driver/src/KobukiDriver/CKobuki.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 4636a5a..187fe42 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -254,7 +254,7 @@ void CKobuki::setSound(int noteinHz, int duration) { void CKobuki::startCommunication(char *portname, bool CommandsEnabled, void *userDataL) { - connect(portname); + if(connect(portname) != -1){ enableCommands(CommandsEnabled); userData = userDataL; @@ -265,6 +265,7 @@ void CKobuki::startCommunication(char *portname, bool CommandsEnabled, std::cerr << "Error creating thread: " << pthread_result << std::endl; } } +} int CKobuki::measure() { while (stopVlakno == 0) { diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.h b/src/C++/Driver/src/KobukiDriver/CKobuki.h index 31db87e..ab66055 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.h +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.h @@ -60,7 +60,7 @@ public: long loop(void *user_data, TKobukiData &Kobuki_data); - void startCommunication(char *portname,bool CommandsEnabled,void *userDataL); + bool startCommunication(char *portname,bool CommandsEnabled,void *userDataL); int measure(); //thread function, contains an infinite loop and reads data void setLed(int led1 = 0, int led2 = 0); //led1 green/red 2/1, //led2 green/red 2/1 void setTranslationSpeed(int mmpersec); From bc0b878230eda34281af9caaf8c22161f5915555 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 7 Jan 2025 14:40:55 +0100 Subject: [PATCH 054/107] changed to bool --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 187fe42..a0a3abe 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -252,7 +252,7 @@ void CKobuki::setSound(int noteinHz, int duration) { pocet = write(HCom, &message, 9); } -void CKobuki::startCommunication(char *portname, bool CommandsEnabled, +bool CKobuki::startCommunication(char *portname, bool CommandsEnabled, void *userDataL) { if(connect(portname) != -1){ enableCommands(CommandsEnabled); From 2bfd11276a9e001cdc8e34eed890d41a003bbd55 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Tue, 7 Jan 2025 15:00:13 +0100 Subject: [PATCH 055/107] importlist changed --- src/C++/Driver/src/main.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index df4cac1..8d61bb2 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -1,9 +1,11 @@ -#include "KobukiDriver/CKobuki.h" -#include "MQTT/MqttClient.h" -#include #include -#include +#include +#include +#include "MQTT/MqttClient.h" +#include "KobukiDriver/CKobuki.h" #include +#include + #include using namespace std; From 184e723379aa01951834b5edafd96399e239e97f Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:05:19 +0100 Subject: [PATCH 056/107] added new thread --- src/C++/Driver/src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 8d61bb2..16416b5 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -31,6 +31,9 @@ void setup() { client.connect(); client.subscribe("home/commands"); + + std::thread monitorThread(monitorKobukiConnection); + monitorThread.detach(); } int main() { From a979d15a6efafb2f394d026f8fcfdfb1959c9f3a Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:27:34 +0100 Subject: [PATCH 057/107] commented my new code to check if bug is still here --- src/C++/Driver/src/main.cpp | 44 ++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 16416b5..6444df8 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -57,29 +57,29 @@ int main() { image.join(); } -void reconnectKobuki() { - unsigned char *null_ptr(0); - while (true) { - if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { - std::cout << "Kobuki opnieuw verbonden!" << std::endl; - break; // Verlaat de loop als de verbinding succesvol is - } else { - std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(3)); - } - } -} +// void reconnectKobuki() { +// unsigned char *null_ptr(0); +// while (true) { +// if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { +// std::cout << "Kobuki opnieuw verbonden!" << std::endl; +// break; // Verlaat de loop als de verbinding succesvol is +// } else { +// std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; +// std::this_thread::sleep_for(std::chrono::seconds(3)); +// } +// } +// } -void monitorKobukiConnection() { - while (true) { - // Check regelmatig of de verbinding actief is - if (!robot.isConnected()) { - std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; - reconnectKobuki(); - } - std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden - } -} +// void monitorKobukiConnection() { +// while (true) { +// // Check regelmatig of de verbinding actief is +// if (!robot.isConnected()) { +// std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; +// reconnectKobuki(); +// } +// std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden +// } +// } std::string readMQTT() { From b12e4c7539a40e2f1c60be9753015f805277b0de Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:29:43 +0100 Subject: [PATCH 058/107] removed thread as well --- src/C++/Driver/src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 6444df8..2f368b1 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -32,13 +32,13 @@ void setup() { client.connect(); client.subscribe("home/commands"); - std::thread monitorThread(monitorKobukiConnection); - monitorThread.detach(); +// std::thread monitorThread(monitorKobukiConnection); +// monitorThread.detach(); } int main() { setup(); - reconnectKobuki(); +// reconnectKobuki(); std::thread connectionMonitor(monitorKobukiConnection); std::thread image(CapnSend); From bcac062cdfbcdafca1479206927335ef62514935 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:31:57 +0100 Subject: [PATCH 059/107] comment out monitorKobukiConnection thread for debugging --- src/C++/Driver/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 2f368b1..e3b5797 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -16,7 +16,7 @@ std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); void reconnectKobuki(); -void monitorKobukiConnection(); +// void monitorKobukiConnection(); // ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object @@ -40,7 +40,7 @@ int main() { setup(); // reconnectKobuki(); - std::thread connectionMonitor(monitorKobukiConnection); +// std::thread connectionMonitor(monitorKobukiConnection); std::thread image(CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); From fe3fe2b8cfa34aa2d619241a88f1b5616163d706 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:40:38 +0100 Subject: [PATCH 060/107] restored all my code --- src/C++/Driver/src/main.cpp | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index e3b5797..16416b5 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -16,7 +16,7 @@ std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); void reconnectKobuki(); -// void monitorKobukiConnection(); +void monitorKobukiConnection(); // ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object @@ -32,15 +32,15 @@ void setup() { client.connect(); client.subscribe("home/commands"); -// std::thread monitorThread(monitorKobukiConnection); -// monitorThread.detach(); + std::thread monitorThread(monitorKobukiConnection); + monitorThread.detach(); } int main() { setup(); -// reconnectKobuki(); + reconnectKobuki(); -// std::thread connectionMonitor(monitorKobukiConnection); + std::thread connectionMonitor(monitorKobukiConnection); std::thread image(CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); @@ -57,29 +57,29 @@ int main() { image.join(); } -// void reconnectKobuki() { -// unsigned char *null_ptr(0); -// while (true) { -// if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { -// std::cout << "Kobuki opnieuw verbonden!" << std::endl; -// break; // Verlaat de loop als de verbinding succesvol is -// } else { -// std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; -// std::this_thread::sleep_for(std::chrono::seconds(3)); -// } -// } -// } +void reconnectKobuki() { + unsigned char *null_ptr(0); + while (true) { + if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { + std::cout << "Kobuki opnieuw verbonden!" << std::endl; + break; // Verlaat de loop als de verbinding succesvol is + } else { + std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + } +} -// void monitorKobukiConnection() { -// while (true) { -// // Check regelmatig of de verbinding actief is -// if (!robot.isConnected()) { -// std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; -// reconnectKobuki(); -// } -// std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden -// } -// } +void monitorKobukiConnection() { + while (true) { + // Check regelmatig of de verbinding actief is + if (!robot.isConnected()) { + std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; + reconnectKobuki(); + } + std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden + } +} std::string readMQTT() { From 361c17fbdbc286750bfe727287c1b9a65a642abe Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 13:59:06 +0100 Subject: [PATCH 061/107] comment everything again --- src/C++/Driver/src/main.cpp | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 16416b5..1531fe3 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -32,15 +32,15 @@ void setup() { client.connect(); client.subscribe("home/commands"); - std::thread monitorThread(monitorKobukiConnection); - monitorThread.detach(); +// std::thread monitorThread(monitorKobukiConnection); +// monitorThread.detach(); } int main() { setup(); - reconnectKobuki(); +// reconnectKobuki(); - std::thread connectionMonitor(monitorKobukiConnection); +// std::thread connectionMonitor(monitorKobukiConnection); std::thread image(CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); @@ -57,29 +57,29 @@ int main() { image.join(); } -void reconnectKobuki() { - unsigned char *null_ptr(0); - while (true) { - if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { - std::cout << "Kobuki opnieuw verbonden!" << std::endl; - break; // Verlaat de loop als de verbinding succesvol is - } else { - std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(3)); - } - } -} +// void reconnectKobuki() { +// unsigned char *null_ptr(0); +// while (true) { +// if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { +// std::cout << "Kobuki opnieuw verbonden!" << std::endl; +// break; // Verlaat de loop als de verbinding succesvol is +// } else { +// std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; +// std::this_thread::sleep_for(std::chrono::seconds(3)); +// } +// } +// } -void monitorKobukiConnection() { - while (true) { - // Check regelmatig of de verbinding actief is - if (!robot.isConnected()) { - std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; - reconnectKobuki(); - } - std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden - } -} +// void monitorKobukiConnection() { +// while (true) { +// // Check regelmatig of de verbinding actief is +// if (!robot.isConnected()) { +// std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; +// reconnectKobuki(); +// } +// std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden +// } +// } std::string readMQTT() { From 4bf3cd6d37015d952ecd224aaef18a625a748ead Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 14:15:06 +0100 Subject: [PATCH 062/107] added mqtt connection safety --- src/C++/Driver/src/main.cpp | 65 ++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 1531fe3..adff2e0 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -17,6 +17,7 @@ void parseMQTT(std::string message); void CapnSend(); void reconnectKobuki(); void monitorKobukiConnection(); +void monitorMQTTConnection(); // ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object @@ -32,15 +33,16 @@ void setup() { client.connect(); client.subscribe("home/commands"); -// std::thread monitorThread(monitorKobukiConnection); -// monitorThread.detach(); + std::thread monitorThread(monitorKobukiConnection); + std::thread mqttMonitorThread(monitorMQTTConnection); + mqttMonitorThread.detach(); + monitorThread.detach(); } int main() { setup(); -// reconnectKobuki(); + reconnectKobuki(); -// std::thread connectionMonitor(monitorKobukiConnection); std::thread image(CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); @@ -57,29 +59,40 @@ int main() { image.join(); } -// void reconnectKobuki() { -// unsigned char *null_ptr(0); -// while (true) { -// if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { -// std::cout << "Kobuki opnieuw verbonden!" << std::endl; -// break; // Verlaat de loop als de verbinding succesvol is -// } else { -// std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; -// std::this_thread::sleep_for(std::chrono::seconds(3)); -// } -// } -// } +void reconnectKobuki() { + unsigned char *null_ptr(0); + while (true) { + if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { + std::cout << "Kobuki opnieuw verbonden!" << std::endl; + break; // Verlaat de loop als de verbinding succesvol is + } else { + std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + } +} -// void monitorKobukiConnection() { -// while (true) { -// // Check regelmatig of de verbinding actief is -// if (!robot.isConnected()) { -// std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; -// reconnectKobuki(); -// } -// std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden -// } -// } +void monitorKobukiConnection() { + while (true) { + // Check regelmatig of de verbinding actief is + if (!robot.isConnected()) { + std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; + reconnectKobuki(); + } + std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden + } +} + +void monitorMQTTConnection() { + while (true) { + if (!client.isConnected()) { // Controleer of de client nog verbonden is + std::cerr << "MQTT verbinding verloren. Probeer opnieuw te verbinden..." << std::endl; + client.connect(); + client.subscribe("home/commands"); + } + std::this_thread::sleep_for(std::chrono::seconds(5)); // Controleer iedere 5 seconden + } +} std::string readMQTT() { From fa85be2df546cb11428c81f2c663f78ccc7976b6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 14:18:46 +0100 Subject: [PATCH 063/107] removed mqtt check --- src/C++/Driver/src/main.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index adff2e0..22d8669 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -17,7 +17,6 @@ void parseMQTT(std::string message); void CapnSend(); void reconnectKobuki(); void monitorKobukiConnection(); -void monitorMQTTConnection(); // ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object @@ -34,8 +33,6 @@ void setup() { client.subscribe("home/commands"); std::thread monitorThread(monitorKobukiConnection); - std::thread mqttMonitorThread(monitorMQTTConnection); - mqttMonitorThread.detach(); monitorThread.detach(); } @@ -83,17 +80,6 @@ void monitorKobukiConnection() { } } -void monitorMQTTConnection() { - while (true) { - if (!client.isConnected()) { // Controleer of de client nog verbonden is - std::cerr << "MQTT verbinding verloren. Probeer opnieuw te verbinden..." << std::endl; - client.connect(); - client.subscribe("home/commands"); - } - std::this_thread::sleep_for(std::chrono::seconds(5)); // Controleer iedere 5 seconden - } -} - std::string readMQTT() { static std::string lastMessage; From 71091f57dd240a6e3e1724f0200927a934f53dea Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 15:25:30 +0100 Subject: [PATCH 064/107] removed code that didnt work --- src/C++/Driver/src/main.cpp | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 22d8669..fdf3f94 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -15,8 +15,6 @@ CKobuki robot; std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); -void reconnectKobuki(); -void monitorKobukiConnection(); // ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", "rpiwachtwoordofzo"); // create a client object @@ -32,13 +30,10 @@ void setup() { client.connect(); client.subscribe("home/commands"); - std::thread monitorThread(monitorKobukiConnection); - monitorThread.detach(); } int main() { setup(); - reconnectKobuki(); std::thread image(CapnSend); std::thread safety([&]() { robot.robotSafety(&message); }); @@ -56,31 +51,6 @@ int main() { image.join(); } -void reconnectKobuki() { - unsigned char *null_ptr(0); - while (true) { - if (robot.startCommunication("/dev/ttyUSB0", true, null_ptr) != -1) { - std::cout << "Kobuki opnieuw verbonden!" << std::endl; - break; // Verlaat de loop als de verbinding succesvol is - } else { - std::cerr << "Kobuki niet verbonden. Probeer opnieuw over 3 seconden..." << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(3)); - } - } -} - -void monitorKobukiConnection() { - while (true) { - // Check regelmatig of de verbinding actief is - if (!robot.isConnected()) { - std::cerr << "Kobuki verbinding verloren. Reconnectie starten..." << std::endl; - reconnectKobuki(); - } - std::this_thread::sleep_for(std::chrono::seconds(5)); // Check iedere 5 seconden - } -} - - std::string readMQTT() { static std::string lastMessage; From d0bfef22960f7221ea4cf6e1ec0a3a30816464b1 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Wed, 8 Jan 2025 16:40:34 +0100 Subject: [PATCH 065/107] add feedback document with coding suggestions and improvements --- teamdocumentatie/Ishak/expert.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 teamdocumentatie/Ishak/expert.md diff --git a/teamdocumentatie/Ishak/expert.md b/teamdocumentatie/Ishak/expert.md new file mode 100644 index 0000000..f73fc22 --- /dev/null +++ b/teamdocumentatie/Ishak/expert.md @@ -0,0 +1,7 @@ +# feedback + +- schrijf altijd in je code waarvan je de library importeert. Dit is handig voor jezelf en voor anderen die je code lezen. +- Meer comments in je code zouden handig zijn om te begrijpen wat je code doet. +- Database: timestamp voor wanneer je een record toevoegt in je command. +- Kobuki sensor data ook opslaan in de database. +- \ No newline at end of file From 72a0fadef8273dba41c15c1d93590f0f3d0f7729 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 11:45:19 +0100 Subject: [PATCH 066/107] add UML diagram for system architecture and clean up MQTT client initialization --- docs/Infrastructure/uml.md | 51 +++++++++++++++++++++++++++++++++++++ src/C++/Driver/src/main.cpp | 3 +-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 docs/Infrastructure/uml.md diff --git a/docs/Infrastructure/uml.md b/docs/Infrastructure/uml.md new file mode 100644 index 0000000..1d82f2a --- /dev/null +++ b/docs/Infrastructure/uml.md @@ -0,0 +1,51 @@ +```mermaid +classDiagram + class CKobuki { + +enableCommands(bool commands) + +loop(void *user_data, TKobukiData &Kobuki_data) + +startCommunication(char *portname, bool CommandsEnabled, void *userDataL) + +measure() + +setLed(int led1, int led2) + +setTranslationSpeed(int mmpersec) + +setRotationSpeed(double radpersec) + +setArcSpeed(int mmpersec, int radius) + +setSound(int noteinHz, int duration) + +setPower(int value) + +goStraight(long double distance) + +forward(int speedvalue) + +doRotation(long double th) + } + + class FlaskApp { + +on_message(client, message) + +get_db() + +close_db(error) + +index() + +control() + +move() + } + + class MQTTClient { + +connect() + +subscribe(topic) + +getLastMessage() + +isConnected() + } + + FlaskApp --> MQTTClient : uses + FlaskApp --> CKobuki : controls + + class RPI { + +KobukiCommunication() + +ESP32Communication() + +Camera() + } + + class ESP32 { + +TVOC() + +DHT11() + } + + RPI --> MQTTClient : communicates + MQTTClient --> CKobuki : communicates + RPI --> ESP32 : communicates \ No newline at end of file diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index fdf3f94..6a888b7 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -16,8 +16,7 @@ std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); // ip, clientID, username, password -MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi", - "rpiwachtwoordofzo"); // create a client object +MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi","rpiwachtwoordofzo"); // create a client object std::string message = "stop"; std::string serializeKobukiData(const TKobukiData &data); void sendKobukiData(TKobukiData &data); From fe642673075b7554890bd65697c37c3df5b8564c Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 11:56:54 +0100 Subject: [PATCH 067/107] added js from main branch --- src/Python/flask/web/static/script.js | 118 +++++++++++++++++++++----- 1 file changed, 99 insertions(+), 19 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index e5b4fee..ceaecef 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,41 +1,121 @@ -document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", async function(event) { // Maak de functie async - event.preventDefault(); // voorkomt pagina-verversing +document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", function(event) { + event.preventDefault(); // prevents page refresh - // Haal de waarde van de knop op - const direction = event.target.value; + // Get the value of the button + const direction = event.target.value; - try { - const response = await fetch("/move", { + fetch("/move", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ direction: direction }) + }) + .then(response => response.json()) + .then(data => { + console.log("Success:", data); + }) + .catch(error => { + console.error("Error:", error); }); - const data = await response.json(); - console.log("Success:", data); - } catch (error) { - console.error("Error:", error); - } + }); + }); - // Fetch data from the server - async function fetchData() { + // Fetch data from the server + async function fetchData() { + try { const response = await fetch("/data"); const data = await response.json(); return data; + } catch (error) { + console.error("Error:", error); } + } - // Parse the data and show it on the website + // Parse the data and show it on the website + async function parseData() { const data = await fetchData(); const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - //for each object in json array create a new paragraph element and append it to the sensorDataContainer + // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer for (const [key, value] of Object.entries(data)) { const dataElement = document.createElement("p"); dataElement.textContent = `${key}: ${value}`; - sensorDataContainer.appendChild(dataElement); // Voeg het element toe aan de container + sensorDataContainer.appendChild(dataElement); } - }); -}); + } + // Update the image + function updateImage() { + var img = document.getElementById("robot-image"); + img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching + } + + // Fetch and display sensor data every 1 second + setInterval(parseData, 1000); + + // Update the image every 200 milliseconds + setInterval(updateImage, 100); +});document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", function(event) { + event.preventDefault(); // prevents page refresh + + // Get the value of the button + const direction = event.target.value; + + fetch("/move", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ direction: direction }) + }) + .then(response => response.json()) + .then(data => { + console.log("Success:", data); + }) + .catch(error => { + console.error("Error:", error); + }); + }); + }); + + // Fetch data from the server + async function fetchData() { + try { + const response = await fetch("/data"); + const data = await response.json(); + return data; + } catch (error) { + console.error("Error:", error); + } + } + + // Parse the data and show it on the website + async function parseData() { + const data = await fetchData(); + const sensorDataContainer = document.getElementById("sensor-data"); + sensorDataContainer.innerHTML = ""; // Clear previous data + // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer + for (const [key, value] of Object.entries(data)) { + const dataElement = document.createElement("p"); + dataElement.textContent = `${key}: ${value}`; + sensorDataContainer.appendChild(dataElement); + } + } + + // Update the image + function updateImage() { + var img = document.getElementById("robot-image"); + img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching + } + + // Fetch and display sensor data every 1 second + setInterval(parseData, 1000); + + // Update the image every 200 milliseconds + setInterval(updateImage, 100); +}); \ No newline at end of file From 9eb9822cfffe7fadab41acc3d6489c1d68abca98 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 11:58:48 +0100 Subject: [PATCH 068/107] remove unused event listeners and redundant data fetching logic from script.js --- src/Python/flask/web/static/script.js | 60 --------------------------- 1 file changed, 60 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index ceaecef..0445a32 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -56,66 +56,6 @@ document.addEventListener("DOMContentLoaded", function() { // Fetch and display sensor data every 1 second setInterval(parseData, 1000); - // Update the image every 200 milliseconds - setInterval(updateImage, 100); -});document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", function(event) { - event.preventDefault(); // prevents page refresh - - // Get the value of the button - const direction = event.target.value; - - fetch("/move", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ direction: direction }) - }) - .then(response => response.json()) - .then(data => { - console.log("Success:", data); - }) - .catch(error => { - console.error("Error:", error); - }); - }); - }); - - // Fetch data from the server - async function fetchData() { - try { - const response = await fetch("/data"); - const data = await response.json(); - return data; - } catch (error) { - console.error("Error:", error); - } - } - - // Parse the data and show it on the website - async function parseData() { - const data = await fetchData(); - const sensorDataContainer = document.getElementById("sensor-data"); - sensorDataContainer.innerHTML = ""; // Clear previous data - // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer - for (const [key, value] of Object.entries(data)) { - const dataElement = document.createElement("p"); - dataElement.textContent = `${key}: ${value}`; - sensorDataContainer.appendChild(dataElement); - } - } - - // Update the image - function updateImage() { - var img = document.getElementById("robot-image"); - img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching - } - - // Fetch and display sensor data every 1 second - setInterval(parseData, 1000); - // Update the image every 200 milliseconds setInterval(updateImage, 100); }); \ No newline at end of file From 092e4f5aebaa13abcc0b2380f3d7db04adcb6613 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 11:59:34 +0100 Subject: [PATCH 069/107] remove camera code --- src/Python/flask/web/static/script.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 0445a32..1a052a1 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -48,10 +48,10 @@ document.addEventListener("DOMContentLoaded", function() { } // Update the image - function updateImage() { - var img = document.getElementById("robot-image"); - img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching - } + // function updateImage() { + // var img = document.getElementById("robot-image"); + // img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching + // } // Fetch and display sensor data every 1 second setInterval(parseData, 1000); From 8517a0d5589f37f99cf7318c353529979821cc7e Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:00:02 +0100 Subject: [PATCH 070/107] comment out image update interval in script.js --- src/Python/flask/web/static/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 1a052a1..00dd445 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -57,5 +57,5 @@ document.addEventListener("DOMContentLoaded", function() { setInterval(parseData, 1000); // Update the image every 200 milliseconds - setInterval(updateImage, 100); + // setInterval(updateImage, 100); }); \ No newline at end of file From 1a4056ff77c3a0a7a4edc9e8a400022fd1067846 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:02:36 +0100 Subject: [PATCH 071/107] return raw kobuki_message instead of jsonify in data endpoint --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index dc68273..f69355f 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -76,7 +76,7 @@ def move(): @app.route('/data', methods=['GET']) def data(): - return jsonify(kobuki_message) + return kobuki_message @app.route("/database") def database(): From 3ff1b2234660580a0c78ef3b5bebf112abb8a5c4 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:35:17 +0100 Subject: [PATCH 072/107] uncomment robot image in control.html --- src/Python/flask/web/templates/control.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/templates/control.html b/src/Python/flask/web/templates/control.html index 02b1ec2..519402f 100644 --- a/src/Python/flask/web/templates/control.html +++ b/src/Python/flask/web/templates/control.html @@ -12,7 +12,7 @@

- + Kobuki Robot
From 612af45f594f8ef75d87c497abf149f5be3a1032 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:39:45 +0100 Subject: [PATCH 073/107] refactor control.html to update robot image source and improve structure --- src/Python/flask/web/templates/control.html | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Python/flask/web/templates/control.html b/src/Python/flask/web/templates/control.html index 519402f..93b9b46 100644 --- a/src/Python/flask/web/templates/control.html +++ b/src/Python/flask/web/templates/control.html @@ -1,6 +1,8 @@ -{% extends 'base.html' %} {% block head %} +{% extends 'base.html' %} +{% block head %} -{% endblock %} {% block content %} +{% endblock %} +{% block content %} @@ -11,8 +13,8 @@
-
- Kobuki Robot +
+ Kobuki Camera Feed
@@ -42,7 +44,8 @@
+ -{% endblock %} +{% endblock %} \ No newline at end of file From a2aa80804e1dbcae07736649f769fa029c133ad7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:47:39 +0100 Subject: [PATCH 074/107] fix: move mqtt_client.on_message assignment under function definition --- src/Python/flask/web/app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index f69355f..60741f8 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -24,6 +24,9 @@ mqtt_client.connect("localhost", 1884, 60) mqtt_client.loop_start() mqtt_client.subscribe("kobuki/data") +mqtt_client.on_message = on_message # this line needs to be under the function definition otherwise it can't find which function it needs to use + + # Database connectie-functie def get_db(): if 'db' not in g: # 'g' is specifiek voor een request en leeft zolang een request duurt From d90ac7259159e2eee05b2ea27af5002923099539 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:52:11 +0100 Subject: [PATCH 075/107] test older version --- src/Python/flask/web/static/script.js | 60 +++++++++------------------ 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/Python/flask/web/static/script.js b/src/Python/flask/web/static/script.js index 00dd445..e5b4fee 100644 --- a/src/Python/flask/web/static/script.js +++ b/src/Python/flask/web/static/script.js @@ -1,61 +1,41 @@ -document.addEventListener("DOMContentLoaded", function() { - document.querySelectorAll(".btn").forEach(button => { - button.addEventListener("click", function(event) { - event.preventDefault(); // prevents page refresh +document.querySelectorAll(".btn").forEach(button => { + button.addEventListener("click", async function(event) { // Maak de functie async + event.preventDefault(); // voorkomt pagina-verversing - // Get the value of the button - const direction = event.target.value; + // Haal de waarde van de knop op + const direction = event.target.value; - fetch("/move", { + try { + const response = await fetch("/move", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ direction: direction }) - }) - .then(response => response.json()) - .then(data => { - console.log("Success:", data); - }) - .catch(error => { - console.error("Error:", error); }); - }); - }); - - // Fetch data from the server - async function fetchData() { - try { - const response = await fetch("/data"); const data = await response.json(); - return data; + console.log("Success:", data); } catch (error) { console.error("Error:", error); } - } - // Parse the data and show it on the website - async function parseData() { + // Fetch data from the server + async function fetchData() { + const response = await fetch("/data"); + const data = await response.json(); + return data; + } + + // Parse the data and show it on the website const data = await fetchData(); const sensorDataContainer = document.getElementById("sensor-data"); sensorDataContainer.innerHTML = ""; // Clear previous data - // For each object in JSON array, create a new paragraph element and append it to the sensorDataContainer + //for each object in json array create a new paragraph element and append it to the sensorDataContainer for (const [key, value] of Object.entries(data)) { const dataElement = document.createElement("p"); dataElement.textContent = `${key}: ${value}`; - sensorDataContainer.appendChild(dataElement); + sensorDataContainer.appendChild(dataElement); // Voeg het element toe aan de container } - } + }); +}); - // Update the image - // function updateImage() { - // var img = document.getElementById("robot-image"); - // img.src = "/image?" + new Date().getTime(); // Add timestamp to avoid caching - // } - - // Fetch and display sensor data every 1 second - setInterval(parseData, 1000); - - // Update the image every 200 milliseconds - // setInterval(updateImage, 100); -}); \ No newline at end of file From d066f68ad2a60e75998994f720fb80e6c664a67f Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 12:56:25 +0100 Subject: [PATCH 076/107] replaced /data endpoint --- src/Python/flask/web/app.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 60741f8..3393168 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -57,6 +57,10 @@ def control(): else: return ('Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'}) +@app.route('/data', methods=['GET']) +def data(): + return kobuki_message + @app.route('/move', methods=['POST']) def move(): data = request.get_json() @@ -77,10 +81,6 @@ def move(): return jsonify({"status": "success", "direction": direction}) -@app.route('/data', methods=['GET']) -def data(): - return kobuki_message - @app.route("/database") def database(): db = get_db() From 07d88eef7ddad0ec6f0558488f0a6a3a105ddfe9 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:19:00 +0100 Subject: [PATCH 077/107] fix: improve connection handling and logging in KobukiDriver --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 20 +++----------------- src/C++/Driver/src/main.cpp | 7 +++++++ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index a0a3abe..7b5e0b4 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -68,28 +68,14 @@ int CKobuki::connect(char *comportT) { HCom = open(comportT, O_RDWR | O_NOCTTY | O_NONBLOCK); if (HCom == -1) { - printf("Kobuki connected\n"); + printf("unable to connect\n"); return HCom; } else { - set_interface_attribs2(HCom, B115200, - 0); // set speed to 115,200 bps, 8n1 (no parity) + set_interface_attribs2(HCom, B115200,0); // set speed to 115,200 bps, 8n1 (no parity) set_blocking2(HCom, 0); // set no blocking - /* struct termios settings; - tcgetattr(HCom, &settings); - - cfsetospeed(&settings, B115200); // baud rate - settings.c_cflag &= ~PARENB; // no parity - settings.c_cflag &= ~CSTOPB; // 1 stop bit - settings.c_cflag &= ~CSIZE; - settings.c_cflag |= CS8 | CLOCAL; // 8 bits - settings.c_lflag &= ~ICANON; // canonical mode - settings.c_cc[VTIME]=1; - settings.c_oflag &= ~OPOST; // raw output - - tcsetattr(HCom, TCSANOW, &settings); // apply the settings*/ tcflush(HCom, TCOFLUSH); - printf("Kobuki connected\n"); + std::cout<<"Kobuki connected" << std::endl; return HCom; } } diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 6a888b7..13b00f1 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -325,6 +325,13 @@ std::string serializeKobukiData(const TKobukiData &data) { // needed it so it can be threaded void sendKobukiData(TKobukiData &data) { while (true) { + if(!robot.connected()){ + std::cout << "Kobuki is not connected anymore" << std::endl; + while(!robot.connected()){ + robot.startCommunication("/dev/ttyUSB0", true, nullptr); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } client.publishMessage("kobuki/data", serializeKobukiData(data)); std::cout << "Sent data" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); From fa17893b1b6b2246f1a0f2cb6615ab46eaa5b7e7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:21:14 +0100 Subject: [PATCH 078/107] fix: update connection check method in sendKobukiData function --- src/C++/Driver/src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 13b00f1..99dffa0 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -325,9 +325,9 @@ std::string serializeKobukiData(const TKobukiData &data) { // needed it so it can be threaded void sendKobukiData(TKobukiData &data) { while (true) { - if(!robot.connected()){ + if(!robot.isConnected()){ std::cout << "Kobuki is not connected anymore" << std::endl; - while(!robot.connected()){ + while(!robot.isConnected()){ robot.startCommunication("/dev/ttyUSB0", true, nullptr); std::this_thread::sleep_for(std::chrono::seconds(1)); } From 2396d61eae325d98f22d5ec803bb16463d7a43e6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:24:57 +0100 Subject: [PATCH 079/107] fix: update on_message function signature to include userdata parameter --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 3393168..a666754 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -9,7 +9,7 @@ kobuki_message = "" latest_image = None # Globale MQTT setup -def on_message(client, message): +def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) From 1c081451aa244012b142389505ee0ad9133e75dc Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:29:25 +0100 Subject: [PATCH 080/107] made thread to wait 1 sec before reconnect --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 3 ++- src/C++/Driver/src/main.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 7b5e0b4..98fb138 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -68,7 +68,8 @@ int CKobuki::connect(char *comportT) { HCom = open(comportT, O_RDWR | O_NOCTTY | O_NONBLOCK); if (HCom == -1) { - printf("unable to connect\n"); + std::cerr <<"unable to connect. retry in 1 second" << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(1)); return HCom; } else { set_interface_attribs2(HCom, B115200,0); // set speed to 115,200 bps, 8n1 (no parity) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 99dffa0..5871450 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -327,6 +327,7 @@ void sendKobukiData(TKobukiData &data) { while (true) { if(!robot.isConnected()){ std::cout << "Kobuki is not connected anymore" << std::endl; + robot.connect("/dev/ttyUSB0"); while(!robot.isConnected()){ robot.startCommunication("/dev/ttyUSB0", true, nullptr); std::this_thread::sleep_for(std::chrono::seconds(1)); From 89421ccf341bfcdbfd3daade7ccade60e05277e6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:31:41 +0100 Subject: [PATCH 081/107] replaced connect --- src/C++/Driver/src/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 5871450..d3e7236 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -327,9 +327,8 @@ void sendKobukiData(TKobukiData &data) { while (true) { if(!robot.isConnected()){ std::cout << "Kobuki is not connected anymore" << std::endl; - robot.connect("/dev/ttyUSB0"); - while(!robot.isConnected()){ robot.startCommunication("/dev/ttyUSB0", true, nullptr); + while(!robot.isConnected()){ std::this_thread::sleep_for(std::chrono::seconds(1)); } } From ec2a08c656da8ca63f031ee17e2e7ca205a12855 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:38:18 +0100 Subject: [PATCH 082/107] refactor: comment out connection check in sendKobukiData function --- src/C++/Driver/src/main.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index d3e7236..7473fae 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -325,13 +325,13 @@ std::string serializeKobukiData(const TKobukiData &data) { // needed it so it can be threaded void sendKobukiData(TKobukiData &data) { while (true) { - if(!robot.isConnected()){ - std::cout << "Kobuki is not connected anymore" << std::endl; - robot.startCommunication("/dev/ttyUSB0", true, nullptr); - while(!robot.isConnected()){ - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - } + // if(!robot.isConnected()){ + // std::cout << "Kobuki is not connected anymore" << std::endl; + // robot.startCommunication("/dev/ttyUSB0", true, nullptr); + // while(!robot.isConnected()){ + // std::this_thread::sleep_for(std::chrono::seconds(1)); + // } + // } client.publishMessage("kobuki/data", serializeKobukiData(data)); std::cout << "Sent data" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); From 6ef739f79475a49e406839effd4ecca2be61a704 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:40:47 +0100 Subject: [PATCH 083/107] removed thread --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index 98fb138..fa2a9cb 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -69,7 +69,6 @@ int CKobuki::connect(char *comportT) { if (HCom == -1) { std::cerr <<"unable to connect. retry in 1 second" << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(1)); return HCom; } else { set_interface_attribs2(HCom, B115200,0); // set speed to 115,200 bps, 8n1 (no parity) From 015b2db819be71eed3f53da7c7f11e996d931455 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:48:01 +0100 Subject: [PATCH 084/107] refactor: change startCommunication return type from bool to void --- src/C++/Driver/src/KobukiDriver/CKobuki.cpp | 2 +- src/C++/Driver/src/KobukiDriver/CKobuki.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp index fa2a9cb..4ccd6f6 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.cpp +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.cpp @@ -238,7 +238,7 @@ void CKobuki::setSound(int noteinHz, int duration) { pocet = write(HCom, &message, 9); } -bool CKobuki::startCommunication(char *portname, bool CommandsEnabled, +void CKobuki::startCommunication(char *portname, bool CommandsEnabled, void *userDataL) { if(connect(portname) != -1){ enableCommands(CommandsEnabled); diff --git a/src/C++/Driver/src/KobukiDriver/CKobuki.h b/src/C++/Driver/src/KobukiDriver/CKobuki.h index ab66055..31db87e 100755 --- a/src/C++/Driver/src/KobukiDriver/CKobuki.h +++ b/src/C++/Driver/src/KobukiDriver/CKobuki.h @@ -60,7 +60,7 @@ public: long loop(void *user_data, TKobukiData &Kobuki_data); - bool startCommunication(char *portname,bool CommandsEnabled,void *userDataL); + void startCommunication(char *portname,bool CommandsEnabled,void *userDataL); int measure(); //thread function, contains an infinite loop and reads data void setLed(int led1 = 0, int led2 = 0); //led1 green/red 2/1, //led2 green/red 2/1 void setTranslationSpeed(int mmpersec); From 9a3829cdb2f53038dba1a471de6b1a7d490d4c40 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:54:15 +0100 Subject: [PATCH 085/107] feat: add save_sensor_data function to insert sensor data into the database --- src/Python/flask/web/app.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index a666754..59fe855 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -90,5 +90,14 @@ def database(): cursor.close() return str(rows) +def save_sensor_data(data): + db = get_db() + cursor = db.cursor() + sql = "INSERT INTO kobuki_data (data) VALUES (%s)" + value = data + cursor.execute(sql, (value,)) + db.commit() + cursor.close() + if __name__ == '__main__': app.run(debug=True, port=5000) From 2e4f048ed98026f910fa412876a3f5509700c0c7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:56:45 +0100 Subject: [PATCH 086/107] feat: call save_sensor_data function upon receiving kobuki/data message --- src/Python/flask/web/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 59fe855..6fa1f1d 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -13,6 +13,7 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) + save_sensor_data(kobuki_message) elif message.topic == "kobuki/cam": latest_image = message.payload From e59f235b91c0b77bf28b4bc412c220c5fcbf89f6 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 13:58:34 +0100 Subject: [PATCH 087/107] changed db send data to other function --- src/Python/flask/web/app.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 6fa1f1d..ef2a503 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -60,8 +60,16 @@ def control(): @app.route('/data', methods=['GET']) def data(): + db = get_db() + cursor = db.cursor() + sql = "INSERT INTO kobuki_data (data) VALUES (%s)" + value = data + cursor.execute(sql, (value,)) + db.commit() + cursor.close() return kobuki_message + @app.route('/move', methods=['POST']) def move(): data = request.get_json() @@ -91,14 +99,6 @@ def database(): cursor.close() return str(rows) -def save_sensor_data(data): - db = get_db() - cursor = db.cursor() - sql = "INSERT INTO kobuki_data (data) VALUES (%s)" - value = data - cursor.execute(sql, (value,)) - db.commit() - cursor.close() if __name__ == '__main__': app.run(debug=True, port=5000) From 3aa77f53149e62d17c66486392bc487f0e931e99 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:03:24 +0100 Subject: [PATCH 088/107] feat: insert sensor data into the database upon command execution --- src/Python/flask/web/app.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index ef2a503..fbe0040 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -1,6 +1,7 @@ from flask import Flask, request, render_template, jsonify, g import paho.mqtt.client as mqtt import mysql.connector +import json app = Flask(__name__) @@ -60,13 +61,6 @@ def control(): @app.route('/data', methods=['GET']) def data(): - db = get_db() - cursor = db.cursor() - sql = "INSERT INTO kobuki_data (data) VALUES (%s)" - value = data - cursor.execute(sql, (value,)) - db.commit() - cursor.close() return kobuki_message @@ -81,9 +75,14 @@ def move(): db_connection = get_db() cursor = db_connection.cursor() - sql = "INSERT INTO command (command) VALUES (%s)" - value = direction - cursor.execute(sql, (value,)) + sql_command = "INSERT INTO command (command) VALUES (%s)" + cursor.execute(sql_command, (direction,)) + + if data: + sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" + sensor_data_tuples = [(name, value) for name, value in sensor_data.items()] + cursor.executemany(sql_sensor, sensor_data_tuples) + db_connection.commit() cursor.close() db_connection.close() From ec0b32a2216d68097b5b94e53ef2d3f6f98a97ee Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:04:40 +0100 Subject: [PATCH 089/107] changed name --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index fbe0040..152f2b5 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -80,7 +80,7 @@ def move(): if data: sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" - sensor_data_tuples = [(name, value) for name, value in sensor_data.items()] + sensor_data_tuples = [(name, value) for name, value in data.items()] cursor.executemany(sql_sensor, sensor_data_tuples) db_connection.commit() From 331de940cc4a58c744a26ed6d14065760bd4e0d4 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:06:13 +0100 Subject: [PATCH 090/107] fixed variable name --- src/Python/flask/web/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 152f2b5..fb24856 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -14,7 +14,6 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) - save_sensor_data(kobuki_message) elif message.topic == "kobuki/cam": latest_image = message.payload @@ -78,9 +77,9 @@ def move(): sql_command = "INSERT INTO command (command) VALUES (%s)" cursor.execute(sql_command, (direction,)) - if data: + if kobuki_message: sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" - sensor_data_tuples = [(name, value) for name, value in data.items()] + sensor_data_tuples = [(name, value) for name, value in kobuki_message.items()] cursor.executemany(sql_sensor, sensor_data_tuples) db_connection.commit() From 7560b0f67ad416741b33b316068739d91cb8ba14 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:09:33 +0100 Subject: [PATCH 091/107] changed to 2 functions --- src/Python/flask/web/app.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index fb24856..3ebc7bd 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -76,12 +76,6 @@ def move(): cursor = db_connection.cursor() sql_command = "INSERT INTO command (command) VALUES (%s)" cursor.execute(sql_command, (direction,)) - - if kobuki_message: - sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" - sensor_data_tuples = [(name, value) for name, value in kobuki_message.items()] - cursor.executemany(sql_sensor, sensor_data_tuples) - db_connection.commit() cursor.close() db_connection.close() @@ -97,6 +91,14 @@ def database(): cursor.close() return str(rows) +def sensor_data(): + db = get_db() + cursor = db.cursor() + cursor.execute("SELECT * FROM kobuki_data") + rows = cursor.fetchall() + cursor.close() + return str(rows) + if __name__ == '__main__': app.run(debug=True, port=5000) From ec71028270ab220ffb3288f225fbd236e44eb71c Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:16:16 +0100 Subject: [PATCH 092/107] wrote own functions for sending data --- src/Python/flask/web/app.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 3ebc7bd..a94d875 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -91,14 +91,13 @@ def database(): cursor.close() return str(rows) -def sensor_data(): +def sensor_data(kobuki_message): db = get_db() cursor = db.cursor() - cursor.execute("SELECT * FROM kobuki_data") - rows = cursor.fetchall() + sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" + cursor.execute(sql_sensor, (kobuki_message,)) + db.commit() cursor.close() - return str(rows) - if __name__ == '__main__': app.run(debug=True, port=5000) From 369120b16bdfd664341504adcc0ab475f4bab1fc Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:19:01 +0100 Subject: [PATCH 093/107] feat: save sensor data to the database upon receiving message --- src/Python/flask/web/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index a94d875..e308fd3 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -14,6 +14,7 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) + sensor_data(kobuki_message) # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload From a9c37ec470cf700fdc17730de4d955499fdc92ed Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:21:06 +0100 Subject: [PATCH 094/107] fix: ensure app context is set when saving sensor data --- src/Python/flask/web/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index e308fd3..3dd5d30 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -14,7 +14,8 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) - sensor_data(kobuki_message) # Sla de data op in de database + with app.app_context(): + sensor_data(kobuki_message) # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload From ed475cd19fa5fc2c777424a662c3b8e68f547ff9 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:28:04 +0100 Subject: [PATCH 095/107] split the data --- src/Python/flask/web/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 3dd5d30..54db722 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -96,8 +96,11 @@ def database(): def sensor_data(kobuki_message): db = get_db() cursor = db.cursor() - sql_sensor = "INSERT INTO kobuki_data (data) VALUES (%s)" - cursor.execute(sql_sensor, (kobuki_message,)) + sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" + data_dict = json.loads(kobuki_message) + sensor_data_tuples = [(name, float(value))for name, value in data_dict.items()] + cursor.executemany(sql_sensor, sensor_data_tuples) + db.commit() cursor.close() From a4ae0170a0ce246c5eae0f0730c50dc3b709b699 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:29:54 +0100 Subject: [PATCH 096/107] refactor: update sensor_data function to accept data directly instead of JSON string --- src/Python/flask/web/app.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 54db722..e41b580 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -93,12 +93,11 @@ def database(): cursor.close() return str(rows) -def sensor_data(kobuki_message): +def sensor_data(data): db = get_db() cursor = db.cursor() sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" - data_dict = json.loads(kobuki_message) - sensor_data_tuples = [(name, float(value))for name, value in data_dict.items()] + sensor_data_tuples = [(name, float(value))for name, value in data.items()] cursor.executemany(sql_sensor, sensor_data_tuples) db.commit() From 39659fffababc3457afe54f5f021d42a5cee60d7 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:30:33 +0100 Subject: [PATCH 097/107] fix: correct variable name in on_message function to use 'data' instead of 'kobuki_message' --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index e41b580..ff51185 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -15,7 +15,7 @@ def on_message(client,userdata, message): if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) with app.app_context(): - sensor_data(kobuki_message) # Sla de data op in de database + sensor_data(data) # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload From c87ebc565c4d3eb610feee9586a0300cd835e468 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:34:26 +0100 Subject: [PATCH 098/107] fix: parse JSON message before saving sensor data --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index ff51185..fc67fba 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -15,7 +15,7 @@ def on_message(client,userdata, message): if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) with app.app_context(): - sensor_data(data) # Sla de data op in de database + sensor_data(json.loads(kobuki_message)) # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload From 64452b78b489a7a0ed68647687ad8789a61a1eb8 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:36:57 +0100 Subject: [PATCH 099/107] fix: retrieve JSON data directly in sensor_data function --- src/Python/flask/web/app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index fc67fba..bf55940 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -93,7 +93,8 @@ def database(): cursor.close() return str(rows) -def sensor_data(data): +def sensor_data(): + data = request.get_json() db = get_db() cursor = db.cursor() sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" From ffbf3347f4764309bc48bdabe9378176afcdc8ae Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:37:26 +0100 Subject: [PATCH 100/107] removed parameter --- src/Python/flask/web/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index bf55940..41c2cba 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -15,7 +15,7 @@ def on_message(client,userdata, message): if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) with app.app_context(): - sensor_data(json.loads(kobuki_message)) # Sla de data op in de database + sensor_data() # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload From 79c2073d291756fd439059105d35f7f382e946b5 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:50:41 +0100 Subject: [PATCH 101/107] want to check data --- src/Python/flask/web/app.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 41c2cba..9cd919e 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -77,11 +77,12 @@ def move(): db_connection = get_db() cursor = db_connection.cursor() sql_command = "INSERT INTO command (command) VALUES (%s)" - cursor.execute(sql_command, (direction,)) + cursor.execute(sql_command, (direction,)) + db_connection.commit() cursor.close() db_connection.close() - + print(f"check: {data}") return jsonify({"status": "success", "direction": direction}) @app.route("/database") From 8d339851dd1a8db8aa8275de42ee933c7136353a Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:51:17 +0100 Subject: [PATCH 102/107] commented sensor data --- src/Python/flask/web/app.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index 9cd919e..f4599d6 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -14,8 +14,8 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) - with app.app_context(): - sensor_data() # Sla de data op in de database + # with app.app_context(): + # sensor_data() # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload @@ -94,16 +94,16 @@ def database(): cursor.close() return str(rows) -def sensor_data(): - data = request.get_json() - db = get_db() - cursor = db.cursor() - sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" - sensor_data_tuples = [(name, float(value))for name, value in data.items()] - cursor.executemany(sql_sensor, sensor_data_tuples) +# def sensor_data(): +# data = request.get_json() +# db = get_db() +# cursor = db.cursor() +# sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" +# sensor_data_tuples = [(name, float(value))for name, value in data.items()] +# cursor.executemany(sql_sensor, sensor_data_tuples) - db.commit() - cursor.close() +# db.commit() +# cursor.close() if __name__ == '__main__': app.run(debug=True, port=5000) From 1964589abc2668f90bba6fbb9da2bd3181764a91 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 14:57:56 +0100 Subject: [PATCH 103/107] fix: update sensor_data function to process and store JSON message from kobuki --- src/Python/flask/web/app.py | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/Python/flask/web/app.py b/src/Python/flask/web/app.py index f4599d6..32403a9 100644 --- a/src/Python/flask/web/app.py +++ b/src/Python/flask/web/app.py @@ -14,8 +14,8 @@ def on_message(client,userdata, message): global kobuki_message, latest_image if message.topic == "kobuki/data": kobuki_message = str(message.payload.decode("utf-8")) - # with app.app_context(): - # sensor_data() # Sla de data op in de database + with app.app_context(): + sensor_data(kobuki_message) # Sla de data op in de database elif message.topic == "kobuki/cam": latest_image = message.payload @@ -82,7 +82,6 @@ def move(): db_connection.commit() cursor.close() db_connection.close() - print(f"check: {data}") return jsonify({"status": "success", "direction": direction}) @app.route("/database") @@ -94,16 +93,39 @@ def database(): cursor.close() return str(rows) -# def sensor_data(): -# data = request.get_json() -# db = get_db() -# cursor = db.cursor() -# sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s)" -# sensor_data_tuples = [(name, float(value))for name, value in data.items()] -# cursor.executemany(sql_sensor, sensor_data_tuples) +def sensor_data(kobuki_message): + try: + # Parse de JSON-string naar een Python-dictionary + data = json.loads(kobuki_message) -# db.commit() -# cursor.close() + # Maak een lijst van tuples met de naam en waarde van elk veld + sensor_data_tuples = [(name, float(value)) for name, value in data.items() if isinstance(value, (int, float))] + + # Extra informatie of nested data (zoals "extraInfo" of "gyroData") kun je apart verwerken + if "extraInfo" in data: + for key, value in data["extraInfo"].items(): + sensor_data_tuples.append((f"extraInfo_{key}", float(value))) + + if "gyroData" in data: + for i, gyro in enumerate(data["gyroData"]): + for axis, value in gyro.items(): + sensor_data_tuples.append((f"gyroData_{i}_{axis}", float(value))) + + # Database-insert + db = get_db() + cursor = db.cursor() + + # Zorg dat je tabel `kobuki_data` kolommen heeft: `name` en `value` + sql_sensor = "INSERT INTO kobuki_data (name, value) VALUES (%s, %s)" + cursor.executemany(sql_sensor, sensor_data_tuples) + + # Commit en sluit de cursor + db.commit() + cursor.close() + except json.JSONDecodeError as e: + print(f"JSON decode error: {e}") + except mysql.connector.Error as err: + print(f"Database error: {err}") if __name__ == '__main__': app.run(debug=True, port=5000) From 82363d393c8493a99b347a83c9239214f4913963 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 15:32:30 +0100 Subject: [PATCH 104/107] fix: add reconnection logic for Kobuki in main loop --- src/C++/Driver/src/main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 7473fae..9101493 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -39,6 +39,15 @@ int main() { std::thread sendMqtt([&]() { sendKobukiData(robot.parser.data); }); while (true) { + if (!robot.isConnected()) { + std::cout << "Kobuki is not connected anymore. Reconnecting..." << std::endl; + robot.startCommunication("/dev/ttyUSB0", true, nullptr); + while (!robot.isConnected()) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + std::cout << "Reconnected to Kobuki." << std::endl; + } + std::string message = readMQTT(); if (!message.empty()) { parseMQTT(message); From 3bca04053a19536c3b0e98cfc1f1fbab0409e8ad Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 15:36:28 +0100 Subject: [PATCH 105/107] fix: add reconnect attempt logging for Kobuki communication --- src/C++/Driver/src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 9101493..9337292 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -43,6 +43,7 @@ int main() { std::cout << "Kobuki is not connected anymore. Reconnecting..." << std::endl; robot.startCommunication("/dev/ttyUSB0", true, nullptr); while (!robot.isConnected()) { + std::cout << "Attempting to reconnect..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } std::cout << "Reconnected to Kobuki." << std::endl; From 7d3a5fa9a32c62be87b3c71d853c0f1487781195 Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 15:43:45 +0100 Subject: [PATCH 106/107] fix: improve Kobuki communication startup and reconnection logging --- src/C++/Driver/src/main.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 9337292..6d2c67e 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -1,13 +1,10 @@ #include -#include #include #include "MQTT/MqttClient.h" #include "KobukiDriver/CKobuki.h" #include #include -#include - using namespace std; using namespace cv; CKobuki robot; @@ -15,7 +12,6 @@ CKobuki robot; std::string readMQTT(); void parseMQTT(std::string message); void CapnSend(); -// ip, clientID, username, password MqttClient client("ws://145.92.224.21/ws/", "KobukiRPI", "rpi","rpiwachtwoordofzo"); // create a client object std::string message = "stop"; std::string serializeKobukiData(const TKobukiData &data); @@ -23,12 +19,16 @@ void sendKobukiData(TKobukiData &data); void setup() { unsigned char *null_ptr(0); - robot.startCommunication("/dev/ttyUSB0", true, null_ptr); + std::cout << "Attempting to start communication with Kobuki..." << std::endl; + if (!robot.startCommunication("/dev/ttyUSB0", true, null_ptr)) { + std::cerr << "Failed to start communication with Kobuki." << std::endl; + } else { + std::cout << "Successfully started communication with Kobuki." << std::endl; + } // connect mqtt server and sub to commands client.connect(); client.subscribe("home/commands"); - } int main() { @@ -41,7 +41,9 @@ int main() { while (true) { if (!robot.isConnected()) { std::cout << "Kobuki is not connected anymore. Reconnecting..." << std::endl; - robot.startCommunication("/dev/ttyUSB0", true, nullptr); + if (!robot.startCommunication("/dev/ttyUSB0", true, nullptr)) { + std::cerr << "Failed to reconnect to Kobuki." << std::endl; + } while (!robot.isConnected()) { std::cout << "Attempting to reconnect..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); @@ -71,7 +73,7 @@ std::string readMQTT() { // Add a small delay to avoid busy-waiting std::this_thread::sleep_for(std::chrono::milliseconds(100)); - return message; + return lastMessage; } void parseMQTT(std::string message) { From 06dd2d9f48deda1d282f50ea2e1f92b49e36505b Mon Sep 17 00:00:00 2001 From: "ishak jmilou.ishak" Date: Thu, 9 Jan 2025 15:52:22 +0100 Subject: [PATCH 107/107] fix: streamline Kobuki communication startup and reconnection logic --- src/C++/Driver/src/main.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/C++/Driver/src/main.cpp b/src/C++/Driver/src/main.cpp index 6d2c67e..712deb2 100644 --- a/src/C++/Driver/src/main.cpp +++ b/src/C++/Driver/src/main.cpp @@ -20,7 +20,8 @@ void sendKobukiData(TKobukiData &data); void setup() { unsigned char *null_ptr(0); std::cout << "Attempting to start communication with Kobuki..." << std::endl; - if (!robot.startCommunication("/dev/ttyUSB0", true, null_ptr)) { + robot.startCommunication("/dev/ttyUSB0", true, null_ptr); + if (!robot.isConnected()) { std::cerr << "Failed to start communication with Kobuki." << std::endl; } else { std::cout << "Successfully started communication with Kobuki." << std::endl; @@ -41,9 +42,7 @@ int main() { while (true) { if (!robot.isConnected()) { std::cout << "Kobuki is not connected anymore. Reconnecting..." << std::endl; - if (!robot.startCommunication("/dev/ttyUSB0", true, nullptr)) { - std::cerr << "Failed to reconnect to Kobuki." << std::endl; - } + robot.startCommunication("/dev/ttyUSB0", true, nullptr); while (!robot.isConnected()) { std::cout << "Attempting to reconnect..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1));