diff --git a/gui/src/components/main_active_broadcasts.vue b/gui/src/components/main_active_broadcasts.vue index d07db1b7..f2285d47 100644 --- a/gui/src/components/main_active_broadcasts.vue +++ b/gui/src/components/main_active_broadcasts.vue @@ -11,7 +11,6 @@ const settings = useSettingsStore(pinia); import { useStateStore } from "../store/stateStore.js"; const state = useStateStore(pinia); -import { sendPing, startBeacon, stopBeacon } from "../js/sock.js"; import { postToServer } from "../js/rest.js"; @@ -20,17 +19,20 @@ function transmitCQ() { } function transmitPing() { - sendPing((document.getElementById("dxCall")).value); + let command = {"dxcall": (document.getElementById("dxCall")).value} + postToServer("localhost", 5000, "modem/ping_ping", command); } function startStopBeacon() { switch (state.beacon_state) { case "False": - startBeacon(settings.beacon_interval); + postToServer("localhost", 5000, "modem/beacon", {"enabled": "True"}); + break; case "True": - stopBeacon(); + postToServer("localhost", 5000, "modem/beacon", {"enabled": "False"}); + break; default: diff --git a/gui/src/js/eventHandler.js b/gui/src/js/eventHandler.js new file mode 100644 index 00000000..3e0c4f60 --- /dev/null +++ b/gui/src/js/eventHandler.js @@ -0,0 +1,378 @@ +import { addDataToWaterfall } from "../js/waterfallHandler.js"; + +import { + newMessageReceived, + newBeaconReceived, + updateTransmissionStatus, + setStateSuccess, + setStateFailed, +} from "./chatHandler"; +import { displayToast } from "./popupHandler"; + +// ----------------- init pinia stores ------------- +import { setActivePinia } from "pinia"; +import pinia from "../store/index"; +setActivePinia(pinia); +import { useStateStore } from "../store/stateStore.js"; +const stateStore = useStateStore(pinia); + +import { useSettingsStore } from "../store/settingsStore.js"; +const settings = useSettingsStore(pinia); + + +export function eventDispatcher(data){ +console.log(data) +if (data["command"] == "modem_state") { + //console.log(data) + + stateStore.rx_buffer_length = data["rx_buffer_length"]; + stateStore.frequency = data["frequency"]; + stateStore.busy_state = data["modem_state"]; + stateStore.arq_state = data["arq_state"]; + stateStore.mode = data["mode"]; + stateStore.bandwidth = data["bandwidth"]; + stateStore.tx_audio_level = data["tx_audio_level"]; + stateStore.rx_audio_level = data["rx_audio_level"]; + // if audio level is different from config one, send new audio level to modem + //console.log(parseInt(stateStore.tx_audio_level)) + //console.log(parseInt(settings.tx_audio_level)) + if ( + parseInt(stateStore.tx_audio_level) !== + parseInt(settings.tx_audio_level) && + setTxAudioLevelOnce === true + ) { + setTxAudioLevelOnce = false; + console.log(setTxAudioLevelOnce); + setTxAudioLevel(settings.tx_audio_level); + } + + if ( + parseInt(stateStore.rx_audio_level) !== + parseInt(settings.rx_audio_level) && + setRxAudioLevelOnce === true + ) { + setRxAudioLevelOnce = false; + console.log(setRxAudioLevelOnce); + setRxAudioLevel(settings.rx_audio_level); + } + + stateStore.dbfs_level = data["audio_dbfs"]; + stateStore.ptt_state = data["ptt_state"]; + stateStore.speed_level = data["speed_level"]; + stateStore.fft = JSON.parse(data["fft"]); + stateStore.channel_busy = data["channel_busy"]; + stateStore.channel_busy_slot = data["channel_busy_slot"]; + + addDataToWaterfall(JSON.parse(data["fft"])); + + if (data["scatter"].length > 0) { + stateStore.scatter = data["scatter"]; + } + // s meter strength + stateStore.s_meter_strength_raw = data["strength"]; + if (stateStore.s_meter_strength_raw == "") { + stateStore.s_meter_strength_raw = "Unsupported"; + stateStore.s_meter_strength_percent = 0; + } else { + // https://www.moellerstudios.org/converting-amplitude-representations/ + stateStore.s_meter_strength_percent = Math.round( + Math.pow(10, stateStore.s_meter_strength_raw / 20) * 100, + ); + } + + stateStore.dbfs_level_percent = Math.round( + Math.pow(10, stateStore.dbfs_level / 20) * 100, + ); + stateStore.dbfs_level = Math.round(stateStore.dbfs_level); + + stateStore.arq_total_bytes = data["total_bytes"]; + stateStore.heard_stations = data["stations"].sort( + sortByPropertyDesc("timestamp"), + ); + stateStore.dxcallsign = data["dxcallsign"]; + + stateStore.beacon_state = data["beacon_state"]; + stateStore.audio_recording = data["audio_recording"]; + + stateStore.hamlib_status = data["hamlib_status"]; + stateStore.alc = data["alc"]; + stateStore.rf_level = data["rf_level"]; + + stateStore.is_codec2_traffic = data["is_codec2_traffic"]; + + stateStore.arq_session_state = data["arq_session"]; + stateStore.arq_state = data["arq_state"]; + stateStore.arq_transmission_percent = data["arq_transmission_percent"]; + stateStore.arq_seconds_until_finish = data["arq_seconds_until_finish"]; + stateStore.arq_seconds_until_timeout = + data["arq_seconds_until_timeout"]; + stateStore.arq_seconds_until_timeout_percent = + (stateStore.arq_seconds_until_timeout / 180) * 100; + + if (data["speed_list"].length > 0) { + prepareStatsDataForStore(data["speed_list"]); + } + + // TODO: Remove ported objects + /* + let Data = { + mycallsign: data["mycallsign"], + mygrid: data["mygrid"], + //channel_state: data['CHANNEL_STATE'], + + info: data["info"], + rx_msg_buffer_length: data["rx_msg_buffer_length"], + tx_n_max_retries: data["tx_n_max_retries"], + arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"], + arq_tx_n_bursts: data["arq_tx_n_bursts"], + arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"], + arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"], + arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"], + arq_rx_n_current_arq_frame: data["arq_rx_n_current_arq_frame"], + arq_n_arq_frames_per_data_frame: + data["arq_n_arq_frames_per_data_frame"], + arq_bytes_per_minute: data["arq_bytes_per_minute"], + arq_compression_factor: data["arq_compression_factor"], + routing_table: data["routing_table"], + mesh_signalling_table: data["mesh_signalling_table"], + listen: data["listen"], + //speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}], + }; + */ + + } + + + var message = ""; + if (data["freedata"] == "modem-message") { + // break early if we received a dummy callsign + // thats a kind of hotfix, as long as the modem isnt handling this better + if ( + data["dxcallsign"] == "AA0AA-0" || + data["dxcallsign"] == "ZZ9YY-0" + ) { + return; + } + + console.log(data); + + switch (data["fec"]) { + case "is_writing": + // RX'd FECiswriting + break; + + case "broadcast": + // RX'd FEC BROADCAST + var encoded_data = atob_FD(data["data"]); + var splitted_data = encoded_data.split(split_char); + var messageArray = []; + if (splitted_data[0] == "m") { + messageArray.push(data); + console.log(data); + } + break; + } + + switch (data["cq"]) { + case "transmitting": + // CQ TRANSMITTING + displayToast( + "success", + "bi-arrow-left-right", + "Transmitting CQ", + 5000, + ); + break; + + case "received": + // CQ RECEIVED + message = "CQ from " + data["dxcallsign"]; + displayToast("success", "bi-person-arms-up", message, 5000); + break; + } + + switch (data["qrv"]) { + case "transmitting": + // QRV TRANSMITTING + displayToast( + "info", + "bi-person-raised-hand", + "Transmitting QRV ", + 5000, + ); + break; + + case "received": + // QRV RECEIVED + message = "QRV from " + data["dxcallsign"] + " | " + data["dxgrid"]; + displayToast("success", "bi-person-raised-hand", message, 5000); + break; + } + + switch (data["beacon"]) { + case "transmitting": + // BEACON TRANSMITTING + displayToast( + "success", + "bi-broadcast-pin", + "Transmitting beacon", + 5000, + ); + break; + + case "received": + // BEACON RECEIVED + newBeaconReceived(data); + + message = + "Beacon from " + data["dxcallsign"] + " | " + data["dxgrid"]; + displayToast("info", "bi-broadcast", message, 5000); + break; + } + + switch (data["ping"]) { + case "transmitting": + // PING TRANSMITTING + message = "Sending ping to " + data["dxcallsign"]; + displayToast("success", "bi-arrow-right", message, 5000); + break; + + case "received": + // PING RECEIVED + message = + "Ping request from " + + data["dxcallsign"] + + " | " + + data["dxgrid"]; + displayToast("success", "bi-arrow-right-short", message, 5000); + break; + + case "acknowledge": + // PING ACKNOWLEDGE + message = + "Received ping-ack from " + + data["dxcallsign"] + + " | " + + data["dxgrid"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + } + + // ARQ SESSION && freedata == modem-message + if (data["arq"] == "session") { + switch (data["status"]) { + case "connecting": + // ARQ Open + break; + + case "connected": + // ARQ Opening + break; + + case "waiting": + // ARQ Opening + break; + + case "close": + // ARQ Closing + break; + + case "failed": + // ARQ Failed + break; + } + } + // ARQ TRANSMISSION && freedata == modem-message + if (data["arq"] == "transmission") { + switch (data["status"]) { + case "opened": + // ARQ Open + message = "ARQ session opened: " + data["dxcallsign"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + + case "opening": + // ARQ Opening IRS/ISS + if (data["irs"] == "False") { + message = "ARQ session opening: " + data["dxcallsign"]; + displayToast("info", "bi-arrow-left-right", message, 5000); + break; + } else { + message = "ARQ sesson request from: " + data["dxcallsign"]; + displayToast("success", "bi-arrow-left-right", message, 5000); + break; + } + + case "waiting": + // ARQ waiting + message = "Channel busy | ARQ protocol is waiting"; + displayToast("warning", "bi-hourglass-split", message, 5000); + break; + + case "receiving": + // ARQ RX + break; + + case "failed": + // ARQ TX Failed + if (data["reason"] == "protocol version missmatch") { + message = "Protocol version mismatch!"; + displayToast("danger", "bi-chevron-bar-expand", message, 5000); + setStateFailed(); + + break; + } else { + message = "Transmission failed"; + displayToast("danger", "bi-x-octagon", message, 5000); + updateTransmissionStatus(data); + setStateFailed(); + break; + } + switch (data["irs"]) { + case "True": + updateTransmissionStatus(data); + + break; + default: + updateTransmissionStatus(data); + break; + } + break; + + case "received": + // ARQ data received + + console.log(data); + // we need to encode here to do a deep check for checking if file or message + //var encoded_data = atob(data['data']) + var encoded_data = atob_FD(data["data"]); + var splitted_data = encoded_data.split(split_char); + + // new message received + if (splitted_data[0] == "m") { + console.log(splitted_data); + newMessageReceived(splitted_data, data); + } + + break; + + case "transmitting": + // ARQ transmitting + updateTransmissionStatus(data); + break; + + case "transmitted": + // ARQ transmitted + message = "Data transmitted"; + displayToast("success", "bi-check-sqaure", message, 5000); + updateTransmissionStatus(data); + setStateSuccess(); + + break; + } + } + } + +} + + diff --git a/gui/src/js/event_sock.js b/gui/src/js/event_sock.js index b959c0ad..02e827fa 100644 --- a/gui/src/js/event_sock.js +++ b/gui/src/js/event_sock.js @@ -1,3 +1,8 @@ +import { + eventDispatcher +} from "../js/eventHandler.js"; + + let socket; let retries = 0; let maxRetries = 15; @@ -14,7 +19,7 @@ function connect() { // handle data socket.addEventListener("message", function (event) { console.log("Message from server:", event.data); - console.log("Message from server:", event); + eventDispatcher(event.data) }); // handle errors @@ -27,12 +32,11 @@ function connect() { console.log("WebSocket connection closed:", event.code); // Reconnect handler - if (!event.wasClean && retries < maxRetries) { + if (!event.wasClean) { setTimeout(() => { - console.log("Reconnecting to websocket. Attempt: " + retries); + console.log("Reconnecting to websocket"); connect(); }, 1000); - retries++; } }); } diff --git a/gui/src/js/rest.js b/gui/src/js/rest.js index 35ae0061..d085b761 100644 --- a/gui/src/js/rest.js +++ b/gui/src/js/rest.js @@ -9,6 +9,8 @@ import { getModemConfigAsJSON, } from "../js/settingsHandler.ts"; + + export async function getFromServer(host, port, endpoint) { // our central function for fetching the modems REST API by a specific endpoint // TODO make this function using the host and port, specified in settings diff --git a/gui/src/js/sock.js b/gui/src/js/sock.js index 722f755b..7f4cb908 100644 --- a/gui/src/js/sock.js +++ b/gui/src/js/sock.js @@ -733,20 +733,7 @@ export function getRxBuffer() { writeTncCommand(command); } -// START BEACON -export function startBeacon(interval) { - var command = - '{"type" : "broadcast", "command" : "start_beacon", "parameter": "' + - interval + - '"}'; - writeTncCommand(command); -} -// STOP BEACON -export function stopBeacon() { - var command = '{"type" : "broadcast", "command" : "stop_beacon"}'; - writeTncCommand(command); -} // OPEN ARQ SESSION export function connectARQ(dxcallsign) { diff --git a/modem/data_handler.py b/modem/data_handler.py index 7e98a4ef..cefc31dc 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -2543,10 +2543,23 @@ class DATA: dxcallsign:bytes: """ + # check if specific callsign is set with different SSID than the Modem is initialized + try: + mycallsign = helpers.callsign_to_bytes(mycallsign) + mycallsign = helpers.bytes_to_callsign(mycallsign) + except Exception: + mycallsign = self.mycallsign + if not str(dxcallsign).strip(): - # TODO We should display a message to this effect on the UI. self.log.warning("[Modem] Missing required callsign", dxcallsign=dxcallsign) return + + # additional step for being sure our callsign is correctly + # in case we are not getting a station ssid + # then we are forcing a station ssid = 0 + dxcallsign = helpers.callsign_to_bytes(dxcallsign) + dxcallsign = helpers.bytes_to_callsign(dxcallsign) + Station.dxcallsign = dxcallsign Station.dxcallsign_crc = helpers.get_crc_24(Station.dxcallsign) self.send_data_to_socket_queue( diff --git a/modem/server.py b/modem/server.py index eaea6c01..56a1adef 100644 --- a/modem/server.py +++ b/modem/server.py @@ -14,7 +14,6 @@ app = Flask(__name__) CORS(app) CORS(app, resources={r"/*": {"origins": "*"}}) sock = Sock(app) -print(sock) # set config file to use def set_config(): @@ -76,29 +75,42 @@ def get_serial_devices(): devices = serial_ports.get_ports() return api_response(devices) -# @app.route('/modem/listen', methods=['POST']) -# @app.route('/modem/record_audio', methods=['POST']) -# @app.route('/modem/responde_to_call', methods=['POST']) -# @app.route('/modem/responde_to_cq', methods=['POST']) -# @app.route('/modem/audio_levels', methods=['POST']) # tx and rx -# @app.route('/modem/send_test_frame', methods=['POST']) -# @app.route('/modem/fec_transmit', methods=['POST']) -# @app.route('/modem/fec_is_writing', methods=['POST']) @app.route('/modem/cqcqcq', methods=['POST', 'GET']) def post_cqcqcq(): if request.method in ['POST']: server_commands.cqcqcq() return api_response({"cmd": "cqcqcq"}) - else: return api_response({"info": "endpoint for triggering a CQ via POST"}) +@app.route('/modem/beacon', methods=['POST']) +def post_beacon(): + if request.method in ['POST']: + server_commands.beacon(request.json) + return api_response(request.json) + else: + return api_response({"info": "endpoint for controlling BEACON STATE via POST"}) + +@app.route('/modem/ping_ping', methods=['POST']) +def post_ping(): + if request.method in ['POST']: + server_commands.ping_ping(request.json) + return api_response(request.json) + else: + return api_response({"info": "endpoint for controlling PING via POST"}) + +# @app.route('/modem/listen', methods=['POST']) # not needed if we are restarting modem on changing settings +# @app.route('/modem/record_audio', methods=['POST']) +# @app.route('/modem/responde_to_call', methods=['POST']) # not needed if we are restarting modem on changing settings +# @app.route('/modem/responde_to_cq', methods=['POST']) # not needed if we are restarting modem on changing settings +# @app.route('/modem/audio_levels', methods=['POST']) # tx and rx # not needed if we are restarting modem on changing settings +# @app.route('/modem/send_test_frame', methods=['POST']) +# @app.route('/modem/fec_transmit', methods=['POST']) +# @app.route('/modem/fec_is_writing', methods=['POST']) -# @app.route('/modem/beacon', methods=['POST']) # on/off # @app.route('/modem/mesh_ping', methods=['POST']) -# @app.route('/modem/ping_ping', methods=['POST']) -# @app.route('/modem/arc_connect', methods=['POST']) -# @app.route('/modem/arc_disconnect', methods=['POST']) +# @app.route('/modem/arq_connect', methods=['POST']) +# @app.route('/modem/arq_disconnect', methods=['POST']) # @app.route('/modem/send_raw', methods=['POST']) # @app.route('/modem/stop_transmission', methods=['POST']) @@ -116,5 +128,7 @@ def post_cqcqcq(): # Event websocket @sock.route('/events') def echo(sock): - ev = app.modem_events.get() - sock.send(ev) + # it seems we have to keep the logics inside a loop, otherwise connection will be terminated + while True: + ev = app.modem_events.get() + sock.send(ev) diff --git a/modem/server_commands.py b/modem/server_commands.py index 8040f21b..e9a09dd3 100644 --- a/modem/server_commands.py +++ b/modem/server_commands.py @@ -1,4 +1,28 @@ from queues import DATA_QUEUE_TRANSMIT +import structlog +log = structlog.get_logger("COMMANDS") -def cqcqcq(): +def cqcqcq(self): DATA_QUEUE_TRANSMIT.put(["CQ"]) + +def ping_ping(data): + try: + dxcallsign = data["dxcallsign"] + if not str(dxcallsign).strip(): + return + DATA_QUEUE_TRANSMIT.put(["PING", None, dxcallsign]) + + except Exception as err: + log.warning( + "[CMD] PING command execution error", e=err, command=data + ) + +def beacon(data): + beacon_state = False + if data['enabled'] in ['True']: + beacon_state = True + #Beacon.beacon_state = beacon_state + log.info( + "[CMD] Changing beacon state", state=beacon_state + ) + DATA_QUEUE_TRANSMIT.put(["BEACON", 300, beacon_state]) \ No newline at end of file