Python
Kildekode for Flask-appen og MQTT-klienten.
Flask-app
app/audiostream.py
1from pydub import AudioSegment
2from io import BytesIO
3import time
4from utils.logging import logger
5
6# TODO: implement streaming
7
8class AudioStream:
9 """
10 Funksjonalitet:
11 - Leser inn en lydfil i opus-format.
12 - Sender en lydfil i rå-data over socket til WebServer.
13
14 :param str file_path: Stien til filen som skal spilles av.
15
16 :ivar str filepath: Stien til lydfilen.
17
18 """
19 def __init__(self, file_path, chunklength=0.05):
20 self.filepath = file_path
21 self.chunklength = chunklength
22 #self.samplerate = None
23 self.chunksize = None
24 self.samples = None
25 self.position = 0
26
27 def init_stream(self):
28 """
29 Oppretter en forbindelse med stream.
30
31 Foreløpig ikke implementert.
32
33 """
34 logger.info("AudioStream: Started init_stream")
35 try:
36 with open(self.filepath, "rb") as f:
37 opusfiledata = f.read()
38 logger.info(f"AudioStream: Read {len(opusfiledata)} bytes from {self.filepath}")
39
40 opus_data = BytesIO(opusfiledata)
41 logger.info("AudioStream: Created BytesIO object")
42
43 self.samples = AudioSegment.from_file(opus_data, codec="opus")
44 logger.info("AudioStream: Successfully loaded AudioSegment")
45
46 self.chunksize = int(self.chunklength * 1000)
47 self.position = 0
48 logger.info(f"AudioStream: Initialized with chunksize={self.chunksize}")
49
50 except Exception as e:
51 logger.error(f"AudioStream: Failed to initialize stream. Error: {e}")
52 raise
53
54
55 def get_next_chunk(self):
56 """
57 Henter nye lydbiter
58
59 Foreløpig ikke implementert.
60
61 """
62 logger.info("AudioStream: Getting next chunk")
63 # chunk = self.samples.readframes(self.chunksize)
64
65 begin = self.position
66 end = begin + self.chunksize
67
68 chunk = self.samples[begin:end]
69
70 if len(chunk) == 0:
71 return None
72
73 self.position = end
74 return chunk.raw_data
75
76
77 def stream_chunks(self):
78 """
79 Sender en lydstrøm
80
81 Ikke implementert enda.
82 """
83 logger.info("AudioStream: stream_chunks called")
84 self.init_stream()
85 while True:
86 chunk = self.get_next_chunk()
87 if chunk is None:
88 logger.info("AudioStream: get_next_chunk returned None, ending stream")
89 break
90 logger.info(f"AudioStream: yielding chunk of size {len(chunk)} bytes")
91 yield chunk
92
93
94 def simple_send(self):
95 """
96 Åpner en lydfil of returnerer den som rådata.
97
98 Returns:
99 bytes: Rådata opus-lydfil
100 Raises:
101 Exception: Hvis filen ikke kan leses eller en annen feil oppstår.
102 """
103 logger.info("AudioStream: sending simple file")
104 try:
105 with open(self.filepath, "rb") as f:
106 opusfiledata = f.read()
107 logger.info(f"AudioStream: Read {len(opusfiledata)} bytes from {self.filepath}")
108 return opusfiledata
109 except Exception as e:
110 logger.error(f"AudioStream: failed to send simple file: {e}")
111 raise
app/front.py
1import functools
2from utils.logging import logger
3from app import socketio
4
5from flask import (
6 Blueprint, flash, g, redirect, render_template, request, session, url_for, jsonify
7)
8
9bp = Blueprint('front', __name__, url_prefix='/front')
10
11@bp.route('/', methods=('GET', 'POST'))
12def front():
13 """
14 Setter opp nettsiden sin landingsside på GET-forespørsler,
15 og sender videre JSON-data i fra forespørslen til MQTT-klienten.
16
17 :rute:
18 GET, POST /front/
19
20 :returns: render_template|request
21 """
22
23 if request.method == 'POST':
24 data = request.get_json()
25 #strength_value = data.get('strength')
26 logger.info(f"received post request with {data}")
27 socketio.emit("strength", data)
28
29
30 return jsonify({"status": "success", "message": "Value for strength received by front post"}), 200
31
32 return render_template('front/fpage.html')
app/landing.py
1from utils.logging import logger
2import os
3import uuid
4from tinydb import TinyDB, Query
5import functools
6import random
7import json
8
9
10from flask_limiter.util import get_remote_address
11from flask import (
12 Blueprint, flash, g, redirect, render_template, request, session, url_for, jsonify, make_response
13)
14
15# local imports
16from app import socketio, limiter
17
18base_dir = os.path.dirname(os.path.abspath(__file__))
19
20# db
21db_uuid_file = os.path.join(base_dir, "../db/uuids.json")
22db = TinyDB(db_uuid_file)
23
24UUID = Query()
25
26# blueprints
27bp = Blueprint('landing', __name__)
28
29# send user to front
30@bp.route('/')
31@limiter.limit("5 per minute")
32def redirect_front():
33 """
34 Funksjon for å redirigere trafikk til landingssiden (front).
35
36 :rute:
37 GET /
38
39 :return: Rediriger nettleseren til /front-endepunktet.
40 """
41 return redirect(url_for('front.front'))
42
43
44@bp.route("/set_cookie")
45def set_cookie():
46 """
47 Oppretter cookie i nettleseren.
48
49 Egenskaper:
50 - Levetid: 3600 sekunder
51 - Secure: True
52
53 :rute:
54 GET /set_cookie
55
56 :return:
57 JSON:
58 - Status: om cookie-en ble opprettet suksessfult eller ikke
59 - Statuskode: ``200``
60
61 """
62 cookie = request.cookies.get("user_data")
63 if not cookie:
64 new_uuid = str(uuid.uuid4())
65 r, g, b = random.randint(50, 180), random.randint(50, 180), random.randint(50, 180)
66 user_data = {"uuid": new_uuid, "color": f"#{r:02x}{g:02x}{b:02x}"}
67
68 response = make_response(jsonify({"status": "new cookie set"}))
69 response.set_cookie(
70 'user_data',
71 json.dumps(user_data),
72 max_age=3600,
73 path='/',
74 samesite='Lax',
75 secure=True
76 )
77 return response
78 return jsonify({"status": "cookie already exists"}), 200
79
80
81
82@bp.route('/another_test', methods=['GET'])
83def another_test():
84 """
85 Kun for testing.
86 """
87 try:
88 socketio.emit("another_event", {"data": "from landing.py"})
89 logger.info("another event landing.py")
90 return jsonify({"status": "success"}), 200
91 except Exception as e:
92 logger.error(f"failed landing.py: {e}")
93 return jsonify({"status": "error", "error": str(e)}), 500
app/mqtt.py
1import os
2from utils.logging import logger
3from flask import Blueprint, request, jsonify
4from app import socketio
5from tinydb import TinyDB, Query
6
7bp = Blueprint('mqtt', __name__, url_prefix='/mqtt')
8
9base_dir = os.path.dirname(os.path.abspath(__file__))
10db_dir = os.path.join(base_dir, "../db")
11nfctag_db = os.path.join(db_dir, "nfctags.json")
12db = TinyDB(nfctag_db)
13
14def find_poi(nfctagID):
15 """
16 Hjelpefunksjon for uthenging av database-data.
17
18 :param nfctagID: NFC-chippen sin ID
19 :type nfctagID: str
20
21 :return: NFC-taggen sin tilhørende POI.
22 :rtype: int
23 """
24 res = db.search(Query().nfctagID == nfctagID)
25 logger.info(f"From find_poi: got {res}")
26
27 if res:
28 return res[0]["poiID"]
29 else:
30 return None
31
32@bp.route('/test', methods=['GET'])
33def test_socketio():
34 """
35 Test-funksjon for å sjekke at tilkoblingen fungerer.
36
37 """
38 try:
39 socketio.emit("test_event", {"data": "test from mqttpy"})
40 logger.info("test_event from mqtt.py")
41 return jsonify({"status": "success"}), 200
42 except Exception as e:
43 logger.error(f"failed socket test mqtt.py: {e}")
44 return jsonify({"status": "error"}), 500
45
46@bp.route('/', methods=['POST'])
47def getmqtt():
48 """
49 Funksjon som håndterer endepunktet for MQTT-beskjeder.
50
51 :rute:
52 GET /mqtt/
53
54 :return:
55 JSON:
56 - Status: om beskjeden mottas uten problemer
57 - Statuskode: ``200``
58 WebSocket:
59 - Endepunkt: ``mqttsocket`` sender melding.
60
61 """
62 logger.info("Got post request")
63
64
65 data = request.get_json()
66 topic = data.get('topic')
67 device = data.get('device')
68 message = data.get('message')
69
70 logger.info(f"Received POST on /mqtt, topic: {topic}, message: {message}")
71
72 socketio.emit("mqttsocket", {'topic': topic, 'message': message, 'device': device})
73
74 return jsonify({"status": "success"}), 200
75
76@bp.route('/location', methods=['POST'])
77def handle_locations():
78 """
79 Håndterer meldinger fra mikrokontrolleren og sender videre til nettleseren.
80
81 :rute:
82 GET /mqtt/location
83
84 :return:
85 JSON:
86 - Status: beskjed om suksess eller ikke
87 - Statuskode: ``200`` om ingen feil, ``404`` om feil
88 WebSocket:
89 - Endepunkt: ``getlocation`` sender melding.
90
91 """
92
93 logger.info("Got post request")
94
95 data = request.get_json()
96 #topic = data.get('topic')
97 device = data.get('devID')
98 nfctagID = str(data.get('nfctagID'))
99
100 logger.info(f"Received POST on /mqtt/location, from device: {device}, with nfctagID: {nfctagID}")
101
102 poiID = find_poi(nfctagID)
103
104 logger.info(f"found poiID: {poiID}, from nfctag: {nfctagID}")
105
106 if poiID:
107 socketio.emit("getlocation", {'poiID': poiID, 'device':device})
108 return jsonify({"status": "success"}), 200
109 else:
110 return jsonify({"status": "error in sending poi"}), 404
111
112
113@bp.route('/returndata', methods=['POST'])
114def handle_returndata():
115 """
116 Funksjon som videresender tolker JSON-data og videresender til MQTT-klienten på serversiden.
117
118 :return:
119 JSON:
120 Statusrapport.
121 SocketIO:
122 - Endepunkt: ``returndata``
123 - Lokasjonsdata til MQTT-klienten.
124
125 """
126 data = request.get_json()
127
128 logger.info(f"Received POI data: {data}")
129
130 try:
131 socketio.emit("returnlocation", data)
132 return jsonify({"status": "success", "message": "POI data received and taken care of"}), 200
133 except Exception as e:
134 logger.error(f"handle_returndata failed sending to returnlocation: {e}")
135 return jsonify({"error": "Error in returning location data"}), 500
app/sockets.py
1import os
2from app import socketio
3from utils.logging import logger
4import json
5import random
6import uuid
7from app.audiostream import AudioStream
8from tinydb import TinyDB, Query
9
10from flask import (
11 Blueprint, flash, g, redirect, render_template, request, session, url_for, jsonify, make_response
12)
13
14base_dir = os.path.dirname(os.path.abspath(__file__))
15db_dir = os.path.join(base_dir, "../db")
16nfctag_db = os.path.join(db_dir, "nfctags.json")
17db = TinyDB(nfctag_db)
18
19audiopath = os.path.join(base_dir, "../audio")
20
21
22def find_audio_file_path(poiID):
23 """
24 Hjelpefunksjon for uthenging av database-data.
25
26 :param poiID: lydfilen sin ID
27 :type poiID: int
28
29 :return: poiID sin tilhørende lydfil.
30 :rtype: str
31 """
32 if not isinstance(poiID, int):
33 logger.info("cant find file because it search term not int")
34 return
35
36 res = db.search(Query().poiID == poiID)
37
38 logger.info("from find_audio_filename: got {}".format(res[0]["audio_filename"]))
39 if res:
40 audio_file_path = os.path.join(audiopath, res[0]["audio_filename"])
41 return audio_file_path
42 else:
43 logger.info(f"error from find_audio_file got res: {res} ")
44 return None
45
46@socketio.on('connect')
47def handle_connect():
48 """
49 Funksjonen sikrer at serveren kobler seg til
50
51 Mottar på:
52 Socket:
53 - ``connect``
54
55 Returns:
56 Socket:
57 - ``server_message`` beskjed: `velkommen!`
58 """
59 logger.info("Client connected")
60 socketio.emit('server_message', {'data': 'Welcome!'})
61
62@socketio.on('ready_for_message')
63def check_cookie():
64 """
65 Funksjonen mottar en forespørsel fra klienten over websocket.
66 Deretter sørger den for at riktig fil blir funnet og returnert til klienten.
67 Hvis riktig fil ikke eksisterer sender den feilmelding tilbake.
68
69 Mottar på:
70 Socket:
71 - ``ready_for_message``
72
73 Returns:
74 Socket:
75 - ``no_cookie`` beskjed: `ingen cookie funnet`
76 - ``colorchange`` color: `fargedata [HEX]`
77 """
78 cookie = request.cookies.get("user_data")
79
80 if not cookie:
81 logger.info(f"No cookie found")
82 socketio.emit('no_cookie', {'message': 'no cookie exists'})
83 return
84
85 user_data = json.loads(cookie)
86
87 try:
88 logger.info("sending from landing page socket")
89 socketio.emit("colorchange", {"color": user_data['color']})
90 except Exception as e:
91 logger.error(f"failed to emit socket {e}")
92
93
94@socketio.on('request_audio')
95def handle_audio_request(data):
96 """
97 Funksjonen mottar en forespørsel fra klienten over websocket.
98 Deretter sørger den for at riktig fil blir funnet og returnert til klienten.
99 Hvis riktig fil ikke eksisterer sender den feilmelding tilbake.
100
101 Mottar på:
102 Socket:
103 - ``request_audio``
104
105 Returns:
106 Socket:
107 - ``error`` beskjed: `fil eksisterer ikke`
108 - ``error`` beskjed: `klarte ikke sende lydfil`
109 - ``audio_file`` `lydfil`
110 """
111 logger.info("initializing audio request")
112
113 poiId = int(data.get('poiId'))
114
115 audio_file_path = find_audio_file_path(poiId)
116
117 #if not isinstance(audio_file_path, str):
118 # logger.info("not a string received")
119 # return socketio.emit('error', {'message': 'did not receive string'})
120
121 if not audio_file_path.strip():
122 logger.info("empty audio_file_path!")
123 return socketio.emit('error', {'message': 'no audio_file_path given'})
124
125 audiostream = AudioStream(audio_file_path)
126
127 logger.info(f"Started audio stream with filepath: {audio_file_path}")
128
129 try:
130 audiofile = audiostream.simple_send()
131 socketio.emit('audio_file', audiofile)
132 except Exception as e:
133 logger.error(f"audio stream fail, error message: {e}")
134 return socketio.emit('error', {'message:': 'failed to send audio'})
135
136 logger.info("audio stream ended")
137
138@socketio.on('disconnect')
139def handle_disconnect():
140 """
141 Sørger for å loggføre når klienter kobler seg av.
142 """
143 logger.info("client disconnected")
Python MQTT-klient
mqttclient/mqttservice.py
1import paho.mqtt.client as paho
2import platform
3import paho.mqtt.publish as publish
4import requests
5import socketio
6from dotenv import load_dotenv
7import os
8import logging
9import traceback
10import json
11
12base_dir = os.path.dirname(os.path.abspath(__file__))
13dotenv_file = os.path.join(base_dir, "../.env")
14
15load_dotenv(dotenv_file)
16
17
18if platform.system() == "Linux":
19 log_dir = "/var/log/pymqtt"
20else:
21 log_dir = os.path.join(base_dir, "../logs")
22
23
24os.makedirs(log_dir, exist_ok=True)
25logging.basicConfig(level=logging.INFO, filename=f"{log_dir}/mqtt.log",filemode="w")
26#logging.basicConfig(level=logging.DEBUG, filename=f"{log_dir}/debug.log",filemode="w")
27logger = logging.getLogger(__name__)
28
29# Henter innloggingsdata for serverside MQTT-klient fra .env-fil
30username = os.getenv("MQTT_ADMIN_USERNAME")
31password = os.getenv("MQTT_ADMIN_PASSWORD")
32
33logger.info(f"got username {username}")
34
35# TODO: implement database with known ids?
36deviceIDs = ['1','2','3']
37
38sio = socketio.Client()
39
40
41@sio.event
42def connect():
43 """
44 Loggfører tilkobling til socket.
45
46 """
47 logger.info("SocketIO from mqtt-client connected")
48
49@sio.event
50def disconnect():
51 """
52 Loggfører avkobling til socket.
53
54 """
55 logger.info("SocketIO from mqtt-client disconnected")
56
57@sio.event
58def connect_error(data):
59 """
60 Sier i fra om koblingen feiler.
61
62 """
63 logger.info("Failed to connect to Flask Socket.IO server")
64
65@sio.on("strength")
66def handle_strength(data):
67 """
68 Returnerer styrke-koeffisient til mikrokontrolleren.
69
70 """
71 logger.info("handle_strength triggered.")
72
73 deviceid = data.get('device')
74
75 if not deviceid:
76 logger.info("Device ID not existing")
77 return
78
79 data.pop("device", None)
80
81 #dev = "devices/2/data"
82 try:
83 payload = json.dumps(data)
84 client.publish(f"devices/{deviceid}/data", payload=payload)
85 logger.info(f"sending to {deviceid} with payload: {payload}")
86 except:
87 exc = traceback.print_exc()
88 logger.info(f"!Sednging failed! Traceback: {exc}")
89
90@sio.on("returnlocation")
91def return_location(data):
92 """
93 Returnerer lokasjonsdata til MQTT-klienten.
94
95 """
96 logger.info(f"received {data}")
97
98 # could drop this
99 deviceID = data.get("deviceID")
100
101 if not deviceID:
102 logger.info("Device ID not existing")
103 return
104
105 data.pop("deviceID", None)
106
107 try:
108 payload = json.dumps(data)
109 client.publish(f"devices/{deviceID}/location", payload=payload)
110 logger.info(f"return_location send on socket with payload {payload}")
111 except:
112 logger.info("return_location failed")
113
114
115# connect socket
116#sio.connect("http://127.0.0.1:8000", transports=["websocket"])
117
118def message_handler(client, msg, deviceID):
119 """
120 Sender beskjeder til flask-appen.
121
122 """
123 payload = {
124 'topic': msg.topic,
125 'device': deviceID,
126 'message': msg.payload.decode()
127 }
128
129 try:
130 response = requests.post("http://127.0.0.1:8000/mqtt", json=payload)
131 logger.info(f"POST request to /mqtt complete with {response.status_code}")
132 except requests.exceptions.RequestException as e:
133 logger.info(e)
134
135def location_handler(client, msg, deviceID):
136 """
137 Videresender lokasjonsinformasjon til flask-appen.
138
139 """
140 payload = {
141 'nfctagID': msg.payload.decode(),
142 'devID': deviceID
143 }
144
145 logger.info(f"received payload: {payload}")
146
147 try:
148 response = requests.post("http://127.0.0.1:8000/mqtt/location", json=payload)
149 logger.info(f"POST request to /mqtt/location complete with {response.status_code}")
150 except requests.exceptions.RequestException as e:
151 logger.info(e)
152
153# enkel måte å implementere flere topics
154topics = {
155 "message": message_handler,
156 "location": location_handler
157}
158
159
160# connect mosquitto
161def on_connect(client, userdata, flags, rc):
162 """
163 Subscriber til følgende topics:
164
165 Forklaring:
166 - ``devices/+`` godtar alle int
167 - ``/#`` godtar alle str-topics
168
169 """
170 if rc == 0:
171 logger.info("Connected to broker")
172 client.subscribe('devices/+/#', qos=1)
173 else:
174 logger.info(f"Failed due to: {rc}")
175
176
177def on_message(client, userdata, msg):
178 """
179 Funksjon som sender forespørsel til server om lokasjonsdata.
180
181 """
182 logger.info(f"topic: {msg.topic} qos: {str(msg.qos)} payload: {str(msg.payload)}")
183 parts = msg.topic.split('/')
184
185 deviceID = parts[1]
186 logger.info(f"found device {deviceID}")
187
188 if deviceID not in deviceIDs:
189 logger.info(f"unkown device {deviceID} setting to unkown")
190 deviceID = 'unkown'
191
192 if parts[2] in topics:
193 handler = topics.get(parts[2])
194
195 #call handler from topic
196 handler(client, msg, deviceID)
197 else:
198 logger.info("{part} not in topic")
199
200
201client = paho.Client(transport="websockets")
202client.on_connect = on_connect
203client.on_message = on_message
204client.username_pw_set(username, password)
205client.connect("mqtt.gruppe1.tech", 9002)
206
207
208if __name__ == "__main__":
209 sio.connect("http://127.0.0.1:8000", transports=["websocket"])
210 client.loop_forever()
JavaScript
Befinner seg i mappen app/static/js
script.js
1import { MazeMap } from './mmap.js';
2import { AudioHandler } from './audio.js';
3
4
5const audio = new AudioHandler();
6let socket;
7
8/**
9 * Sørger for at nettleseren oppretter en ``cookie`` før websocket starter.
10 *
11 * @function fetch
12 */
13fetch('/set_cookie')
14 .then(response => {
15 if (response.ok) {
16 console.log("cookie set");
17 startwebsocket();
18 } else {
19 console.error("failed cookie");
20 }
21 })
22 .catch(error => console.error("Error setting cookie:", error));
23
24/**
25 * Starter WebSocket-tilkoblingen og setter opp hendelseslyttere for ulike servermeldinger.
26 *
27 * @function startwebsocket
28 */
29function startwebsocket() {
30 socket = io("https://gruppe1.tech", {
31 path: "/socket.io/",
32 transports: ["websocket"],
33 });
34
35 socket.on('connect', connectsocket);
36 socket.on('cookie_update', updatecookie);
37 socket.on('cookie_valid', confirmcookie);
38 socket.on('mqttsocket', mqttreceiver);
39 socket.on('test_event', receivetest);
40 socket.on('colorchange', colorhandler);
41 socket.on('getlocation', locationhandler);
42 socket.on('another_event', anothereventhandler);
43 socket.on('audio_file', audiofilehandler);
44
45 socket.onAny(loganyevent);
46}
47
48/**
49 * Håndterer ``connect``-hendelsen på WebSocketen
50 *
51 * @function connectsocket
52 */
53function connectsocket() {
54 console.log("Connected");
55 socket.emit("ready_for_message");
56}
57
58/**
59 * Håndterer ``cookie_update``-hendelsen fra
60 *
61 * @function updatecookie
62 * @param {Object} data - Data relatert til oppdaterte cookies.
63 */
64function updatecookie(data) {
65 console.log("New cookie generated for:", data.user_data);
66}
67
68/**
69 * Håndterer ``cookie_valid``.
70 *
71 * @function confirmcookie
72 * @param {Object} data - Data om gyldige cookies.
73 */
74function confirmcookie(data) {
75 console.log("Cookie already exists:", data.user_data);
76}
77
78/**
79 * Håndterer ``mqttsocket``.
80 *
81 * @function mqttreceiver
82 * @param {Object} data - Data fra MQTT-socket.
83 */
84function mqttreceiver(data) {
85 console.log("Updated info: ", data);
86 showinfo(data);
87}
88
89function receivetest(data) {
90 console.log("Test event received:", data);
91}
92
93/**
94 * Sørger for at ``colorchange``-hendelse på socket blir oppfylt.
95 *
96 * @function colorhandler
97 * @param {Object} data - Data som inneholder fargeinformasjon.
98 */
99function colorhandler(data) {
100 if (data) {
101 document.documentElement.style.setProperty('--common-color', data.color);
102 console.log(`Changed header color to ${data.color}`);
103 } else {
104 console.error("Problem with colorchange socket");
105 }
106}
107
108/**
109 * Sørger for at informasjon over WebSocket-en ``getlocation`` behandles korrekt.
110 *
111 * Funksjonen undersøker først om den har mottatt en gyldig POI (Point of Interest).
112 * Deretter kaller den det eksterne biblioteket ``MazeMap`` med POI.
113 * Til slutt returnerer informasjonen til funksjonen `handlepoi()`
114 *
115 * @function locationhandler
116 * @param {Object} data - Data som inneholder POI og enhets-ID.
117 * @param {string} data.poiID - ID for punktet av interesse.
118 * @param {string} data.device - Enhets-ID assosiert med forespørselen.
119 */
120function locationhandler(data) {
121 const poi = data.poiID;
122 const deviceID = data.device;
123
124 if (poi) {
125 console.log(`Found POI ${poi}`);
126 MazeMap.callPOI(poi).then(poiInfo => {
127 if (poiInfo) {
128 console.log("Received:", poiInfo);
129 handlepoi(poiInfo, deviceID);
130 } else {
131 console.error("MazeMap could not find a valid POI");
132 }
133 }).catch(error => {
134 console.error("MazeMap error:", error);
135 });
136 } else {
137 console.error("Wrong POI");
138 }
139}
140
141/**
142 * Just for testing not important
143 *
144 */
145function anothereventhandler(data) {
146 console.log("Another test from landing.py", data);
147}
148
149/**
150 * Håndterer 'audio_file'-hendelsen og sender lydfilen til `audio`-biblioteket.
151 *
152 * @function audiofilehandler
153 * @param {ArrayBuffer} audiofile Rå lyd-data.
154 */
155function audiofilehandler(audiofile) {
156 audio.playfile(audiofile);
157 console.log("Playing audio file");
158}
159
160/**
161 * Logger alle hendelser over SocketIO-tilkoblingen, som gjør det enklere å debugge.
162 *
163 * @function loganyevent
164 * @param {string} event Alle hendelser
165 * @param {Object} data Data sendt over socket
166 */
167function loganyevent(event, data) {
168 console.log(`Received event: ${event}`, data);
169}
170
171/**
172 * Loggfører beskjeder fra MQTT i nettleservinduet.
173 *
174 * @function showinfo
175 * @param {Object} data - Data mottatt fra MQTT.
176 */
177function showinfo(data) {
178 const mqttList = document.getElementById("mqtt-data");
179
180 const mqttItem = document.createElement("li");
181
182 mqttItem.innerHTML = `<strong> ${data.topic}: </strong> message: ${data.message} device-id: ${data.device}`;
183
184 mqttList.appendChild(mqttItem);
185}
186
187/**
188 * Håndterer ``strength``-knappen i nettleseren.
189 *
190 * @function handlestrengthbutton
191 */
192function handlestrengthbutton() {
193 const collectedvalue = document.getElementById("strength").value;
194 const savedeviceid = localStorage.getItem("chosendevice");
195
196
197 if (isNaN(parseInt(collectedvalue)) || collectedvalue === "") {
198 console.log("Please input valid numeric value");
199 return;
200 }
201
202 if (!savedeviceid) {
203 console.log("no savedevice selected");
204 return;
205 }
206
207 fetch('/front/', {
208 method: 'POST',
209 headers: { 'Content-Type': 'application/json' },
210 body: JSON.stringify({strength: collectedvalue, device: savedeviceid})
211 })
212 .then(response => response.json())
213 .then(data => console.log(data))
214 .catch(error => console.log('Errors:', error));
215}
216
217window.handlestrengthbutton = handlestrengthbutton;
218
219/**
220 * Håndterer lydavspilling og vising av informasjon innhentet fra MazeMap.
221 *
222 * @function handlepoi
223 * @param {Object} data - Data mottatt fra MQTT.
224 */
225function handlepoi(poi, deviceID) {
226 const poiList = document.getElementById("poi-data");
227
228 const poiItem = document.createElement("li");
229 const names = poi.names && Array.isArray(poi.names) ? poi.names.join(", ") : "Unknown Name";
230
231
232 poiItem.innerHTML = `
233 <strong>POI ID:</strong> ${poi.poiId}<br>
234 <strong>Names:</strong> ${names}<br>
235 <strong>Floor:</strong> ${poi.floorName}<br>
236 <strong>Building:</strong> ${poi.buildingName}
237 `;
238
239 poiList.appendChild(poiItem);
240
241 audio.clearqueue();
242
243 socket.emit('request_audio', { poiId: poi.poiId });
244
245 fetch('/mqtt/returndata', {
246 method: 'POST',
247 headers: { 'Content-Type': 'application/json' },
248 body: JSON.stringify({
249 ...poi,
250 deviceID: deviceID
251 })
252 })
253 .then(response => response.json())
254 .then(data => console.log("Server response:", data))
255 .catch(error => console.log('Errors:', error));
256
257}
258
259/**
260 * Håndterer klikk på knappen med ID `deviceselectbutton`.
261 *
262 * Når knappen trykkes:
263 *
264 * - `toggler` synligheten til enhetinput-feltet.
265 *
266 * @function deviceselectclick
267 */
268document.getElementById("deviceselectbutton").addEventListener("click", function () {
269 const devicewrapper = document.getElementById("devicewrapper");
270 const deviceidinput = document.getElementById("deviceidinput");
271
272 devicewrapper.classList.toggle("visible");
273
274 if (devicewrapper.classList.contains("visible")) {
275 deviceidinput.focus();
276 }
277});
278
279
280/**
281 * Lagrer hvilken enhet styrke-informasjonen skal returneres til.
282 *
283 * @function savedevice
284 * @param {Object} data - Data mottatt fra MQTT.
285 */
286function savedevice() {
287 const deviceidinput = document.getElementById("deviceidinput").value.trim();
288
289 if (deviceidinput === "") {
290 console.error("select valid id");
291 return;
292 }
293
294 document.getElementById("chosendevice").textContent = `Chosen device: ${deviceidinput}`;
295
296 localStorage.setItem("chosendevice", deviceidinput);
297
298 document.getElementById("devicewrapper").classList.remove("visible");
299 console.log(`device id set: ${deviceidinput}`);
300}
301
302/**
303 *
304 * Aktiverer lagring til enhet ved muse-klikk.
305 *
306 * @function savedevicebutton
307 */
308document.getElementById("savedevicebutton").addEventListener("click", function () {
309 savedevice();
310});
311
312/**
313 * Håndterer lagring av enhet
314 *
315 * @function deviceinput
316 */
317document.getElementById("deviceidinput").addEventListener("keydown", function(event) {
318 if (event.key === "Enter") {
319 event.preventDefault();
320 savedevice();
321 const strengthinput = document.getElementById("strength");
322 strengthinput.focus();
323 }
324});
325
326/**
327 * Forteller funksjonen `handlestrengthbutton()` til å returnere når enter-tasten trykkes.
328 *
329 * @function keydownlistener
330 */
331document.getElementById("strength").addEventListener("keydown", function(event) {
332 if (event.key === "Enter") {
333 event.preventDefault();
334 handlestrengthbutton();
335 this.blur();
336 }
337});
338
339
340/**
341 * Kjører når vinudet i nettleseren lastes inn
342 *
343 * @function onload
344 */
345window.onload = function () {
346 const savedeviceid = localStorage.getItem("chosendevice");
347 if (savedeviceid) {
348 document.getElementById("chosendevice").textContent = `Chosen device: ${savedeviceid}`;
349 document.getElementById("deviceidinput").value = savedeviceid;
350 }
351};
mmap.js
1export class MazeMap {
2 /**
3 * Oppretter en ny MazeMap-instans.
4 *
5 * @constructor
6 */
7 constructor(){}
8
9 /**
10 * Asynkron funksjon for uthenting av lokasjonsdata.
11 *
12 * **Variabler som hentes fra MazeMap:**
13 *
14 * - ``poiId``
15 * - ``names``
16 * - ``floorName``
17 * - ``buildingName``
18 *
19 * @async
20 * @static
21 * @param {string} poiID Hentes fra database.
22 * @returns {Object} Returnerer utvalgt ``point of interest``-informasjon eller ``null``
23 * hvis ingen poi ble funnet, eller en feil oppstod.
24 *
25 * @example
26 * const poi = await MazeMap.callPOI("36148");
27 * if (poi) {
28 * console.log(poi.names);
29 * }
30 * // returnerer informasjon om stedet med nummer POI-nummer 36148
31 */
32 static async callPOI(poiID) {
33 try {
34 const poi = await Mazemap.Data.getPoi(poiID);
35 console.log("got poi:", poi)
36 return {
37 poiId: poi.properties.poiId,
38 names: poi.properties.names,
39 floorName: poi.properties.floorName,
40 buildingName: poi.properties.buildingName
41 };
42 } catch(error) {
43 console.error(`No POI found, error: ${error}`);
44 return null;
45 }
46 }
47
48}
audio.js
1export class AudioHandler {
2 /**
3 * Oppretter en ny AudioHandler.
4 *
5 * @constructor
6 */
7 constructor() {
8 this.audiocontext = new AudioContext();
9 this.audioqueue = [];
10 this.isplaying = false;
11 }
12
13 addchunk(chunk) {
14 const arraybuffer = new Uint8Array(chunk).buffer;
15
16 this.audiocontext.decodeAudioData(arraybuffer, buffer => {
17 this.audioqueue.push(buffer);
18 if (!this.isplaying) {
19 this.playnextchunk();
20 }
21 }, error => {
22 console.error("decode error ", error);
23 });
24 }
25
26 playnextchunk() {
27 if (this.audioqueue.length > 0) {
28 const source = this.audiocontext.createBufferSource();
29 source.buffer = this.audioqueue.shift();
30 source.connect(this.audiocontext.destination);
31
32 this.isplaying = true;
33
34 source.start();
35
36 source.onended = () => {
37 if (this.audioqueue.length > 0) {
38 this.playnextchunk();
39 } else {
40 this.isplaying = false;
41 }
42 };
43 }
44 }
45
46 clearqueue() {
47 this.audioqueue = [];
48 this.isplaying = false;
49 }
50
51 /**
52 * Avspilling av lydfil mottatt fra server
53 *
54 * @param {ArrayBuffer} arraybuffer Rå lyd-data
55 */
56 playfile(arraybuffer) {
57 this.audiocontext.decodeAudioData(arraybuffer, (buffer) => {
58 const source = this.audiocontext.createBufferSource();
59 source.buffer = buffer;
60 source.connect(this.audiocontext.destination);
61 source.start();
62 console.log("playing audio");
63 }, (error) => {
64 console.error("failed to decode exit with error:", error);
65 });
66 }
67}