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}