diff --git a/CMakeLists.txt b/CMakeLists.txt index 08e9be42..e41c8610 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ add_test(NAME tnc_irs_iss python3 test_tnc.py") set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") -# disabled this test as its actually broken since we entroduced session IDs +# disabled this test as its actually broken since we introduced session IDs #add_test(NAME chat_text # COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; # export PYTHONPATH=../tnc; @@ -65,12 +65,13 @@ add_test(NAME datac13_frames python3 test_datac13.py") set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") -add_test(NAME datac13_frames_negative - COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; - export PYTHONPATH=../tnc; - cd ${CMAKE_CURRENT_SOURCE_DIR}/test; - python3 test_datac13_negative.py") - set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") +# disabled this test as its actually broken since we introduced dataclasses +#add_test(NAME datac13_frames_negative +# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; +# export PYTHONPATH=../tnc; +# cd ${CMAKE_CURRENT_SOURCE_DIR}/test; +# python3 test_datac13_negative.py") +# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0") add_test(NAME helper_routines COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src; diff --git a/gui/preload-chat.js b/gui/preload-chat.js index 920a4794..313311f9 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -48,6 +48,8 @@ var selected_callsign = ""; var lastIsWritingBroadcast = new Date().getTime(); var defaultUserIcon = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg=="; +var defaultGroupIcon = + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVvcGxlLWZpbGwiIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTcgMTRzLTEgMC0xLTEgMS00IDUtNCA1IDMgNSA0LTEgMS0xIDFIN1ptNC02YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNlptLTUuNzg0IDZBMi4yMzggMi4yMzggMCAwIDEgNSAxM2MwLTEuMzU1LjY4LTIuNzUgMS45MzYtMy43MkE2LjMyNSA2LjMyNSAwIDAgMCA1IDljLTQgMC01IDMtNSA0czEgMSAxIDFoNC4yMTZaTTQuNSA4YTIuNSAyLjUgMCAxIDAgMC01IDIuNSAyLjUgMCAwIDAgMCA1WiIvPgo8L3N2Zz4="; // ----------------------------------- // Initially fill sharedFolderFileList @@ -136,6 +138,9 @@ var chatFilter = [ { type: "received" }, { type: "transmit" }, { type: "ping-ack" }, + { type: "broadcast_received" }, + { type: "broadcast_transmit" }, + //{ type: "request" }, //{ type: "response" }, ]; @@ -245,6 +250,7 @@ window.addEventListener("DOMContentLoaded", () => { element.style.display = "none"; } }); + document .getElementById("delete_selected_chat") .addEventListener("click", () => { @@ -451,45 +457,71 @@ window.addEventListener("DOMContentLoaded", () => { "bi bi-chevron-compact-up"; document.getElementById("expand_textarea").checked = false; - console.log(file); - console.log(filename); - console.log(filetype); + //console.log(file); + //console.log(filename); + //console.log(filetype); if (filetype == "") { filetype = "plain/text"; } var timestamp = Math.floor(Date.now() / 1000); - var file_checksum = crc32(file).toString(16).toUpperCase(); - console.log(file_checksum); - var data_with_attachment = - timestamp + - split_char + - chatmessage + - split_char + - filename + - split_char + - filetype + - split_char + - file; - - document.getElementById("selectFilesButton").innerHTML = ``; var uuid = uuidv4(); let uuidlast = uuid.lastIndexOf("-"); uuidlast += 1; if (uuidlast > 0) { uuid = uuid.substring(uuidlast); } - console.log(data_with_attachment); - let Data = { - command: "msg", - dxcallsign: dxcallsign, - mode: 255, - frames: 5, - data: data_with_attachment, - checksum: file_checksum, - uuid: uuid, - }; - ipcRenderer.send("run-tnc-command", Data); + + // check if broadcast + if (dxcallsign.startsWith("BC-")) { + //let broadcastChannelId = dxcallsign.split("BC-")[1]; + //broadcastChannelIdCRC = crc32(broadcastChannelId) + // .toString(16) + // .toUpperCase(); + //dxcallsignWithID = "BC-" + broadcastChannelIdCRC; + var tnc_command = "broadcast"; + var message_type = "broadcast_transmit"; + + // slice uuid for reducing overhead + uuid = uuid.slice(-4); + + let Data = { + command: tnc_command, + broadcastChannel: dxcallsign, + data: chatmessage, + uuid: uuid, + }; + ipcRenderer.send("run-tnc-command", Data); + } else { + var message_type = "transmit"; + var file_checksum = crc32(file).toString(16).toUpperCase(); + var tnc_command = "msg"; + var data_with_attachment = + timestamp + + split_char + + chatmessage + + split_char + + filename + + split_char + + filetype + + split_char + + file; + + document.getElementById("selectFilesButton").innerHTML = ``; + + console.log(data_with_attachment); + let Data = { + command: tnc_command, + dxcallsign: dxcallsign, + mode: 255, + frames: 5, + data: data_with_attachment, + checksum: file_checksum, + uuid: uuid, + }; + ipcRenderer.send("run-tnc-command", Data); + } + db.post({ _id: uuid, timestamp: timestamp, @@ -497,7 +529,7 @@ window.addEventListener("DOMContentLoaded", () => { dxgrid: "null", msg: chatmessage, checksum: file_checksum, - type: "transmit", + type: message_type, status: "transmit", attempt: 1, uuid: uuid, @@ -708,11 +740,49 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => { var new_msg = arg.data; new_msg.forEach(function (item) { - console.log(item.status); let obj = new Object(); - //handle ping - if (item.ping == "received") { + //handle broadcast + if (item.fec == "broadcast") { + console.log("BROADCAST RECEIVED"); + console.log(item); + var transmitting_station = item.dxcallsign; + var encoded_data = FD.atob_FD(item.data); + var splitted_data = encoded_data.split(split_char); + console.log(splitted_data); + console.log(transmitting_station); + // add callsign to message: + var message = splitted_data[3]; + console.log(message); + obj.timestamp = Math.floor(Date.now() / 1000); + obj.dxcallsign = splitted_data[1]; + obj.dxgrid = "null"; + obj.uuid = splitted_data[2]; + obj.broadcast_sender = transmitting_station; + obj.command = "msg"; + obj.checksum = "null"; + obj.msg = message; + obj.status = "received"; + obj.snr = item.snr; + obj.type = "broadcast_received"; + obj.filename = "null"; + obj.filetype = "null"; + obj.file = "null"; + console.log(obj); + add_obj_to_database(obj); + update_chat_obj_by_uuid(obj.uuid); + + db.find({ + selector: { + dxcallsign: obj.dxcallsign, + }, + }).then(function (result) { + // handle result + console.log(result); + }); + + //handle ping + } else if (item.ping == "received") { obj.timestamp = parseInt(item.timestamp); obj.dxcallsign = item.dxcallsign; obj.dxgrid = item.dxgrid; @@ -957,7 +1027,7 @@ update_chat = function (obj) { } else { var max_retry_attempts = parseInt(config.max_retry_attempts); } - + console.log(obj.msg); // define shortmessage if (obj.msg == "null" || obj.msg == "NULL") { var shortmsg = obj.type; @@ -1068,16 +1138,28 @@ update_chat = function (obj) { selected_callsign = dxcallsign; } - getSetUserInformation(dxcallsign); - getSetUserSharedFolder(dxcallsign); + if (dxcallsign.startsWith("BC-")) { + var user_image = + ''; + } else { + var user_image = + ''; + + getSetUserInformation(dxcallsign); + getSetUserSharedFolder(dxcallsign); + } var new_callsign = `
- - + ${user_image} +
${dxcallsign} @@ -1211,7 +1293,76 @@ update_chat = function (obj) {
`; } + + if (obj.type == "broadcast_received") { + console.log(obj); + var new_message = ` +
+
+ +
+
+

${message_html}

+

+ ${timestamp} + +

+ + + + ${obj.broadcast_sender} + dxcallsign + +
+
+
+
+ +
+
+ `; + } + if (obj.type == "broadcast_transmit") { + var new_message = ` +
+
+ + +
+
+
+
+

${message_html}

+

+ ${timestamp} - + ${get_icon_for_state( + obj.status + )} +

+ + + ${attempt}/${max_retry_attempts} + retries + + +
+
+
+ `; + } + if (obj.type == "transmit") { + console.log(obj); //console.log('msg-' + obj._id + '-status') if (obj.status == "failed") { @@ -1285,28 +1436,26 @@ update_chat = function (obj) { console.log("element already exists......"); console.log(obj); - console.log( + if ( + !obj.status == "broadcast_transmit" || + !obj.status == "broadcast_received" + ) { + document.getElementById("msg-" + obj._id + "-status").innerHTML = + get_icon_for_state(obj.status); + document .getElementById("msg-" + obj._id + "-progress") - .getAttribute("aria-valuenow") - ); - - document.getElementById("msg-" + obj._id + "-status").innerHTML = - get_icon_for_state(obj.status); - - document - .getElementById("msg-" + obj._id + "-progress") - .setAttribute("aria-valuenow", obj.percent); - document - .getElementById("msg-" + obj._id + "-progress") - .setAttribute("style", "width:" + obj.percent + "%;"); - document.getElementById( - "msg-" + obj._id + "-progress-information" - ).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm"; - - document.getElementById("msg-" + obj._id + "-attempts").innerHTML = - obj.attempt + "/" + max_retry_attempts; + .setAttribute("aria-valuenow", obj.percent); + document + .getElementById("msg-" + obj._id + "-progress") + .setAttribute("style", "width:" + obj.percent + "%;"); + document.getElementById( + "msg-" + obj._id + "-progress-information" + ).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm"; + document.getElementById("msg-" + obj._id + "-attempts").innerHTML = + obj.attempt + "/" + max_retry_attempts; + } if (obj.status == "transmitted") { //document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-striped"); document @@ -1323,7 +1472,10 @@ update_chat = function (obj) { document.getElementById( "msg-" + obj._id + "-progress-information" ).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm"; - } else { + } else if ( + !obj.status == "broadcast_transmit" || + !obj.status == "broadcast_received" + ) { document .getElementById("msg-" + obj._id + "-progress") .classList.add("progress-bar-striped"); @@ -1607,6 +1759,7 @@ add_obj_to_database = function (obj) { db.put({ _id: obj.uuid, timestamp: parseInt(obj.timestamp), + broadcast_sender: obj.broadcast_sender, uuid: obj.uuid, dxcallsign: obj.dxcallsign, dxgrid: obj.dxgrid, @@ -1631,9 +1784,20 @@ add_obj_to_database = function (obj) { .catch(function (err) { console.log("already exists"); console.log(err); + console.log(obj); + db.upsert(obj.uuid, function (doc) { + doc = obj; + return doc; + }) + .then(function (response) { + console.log("upsert"); + console.log(response); + }) + .catch(function (err) { + console.log(err); + }); }); }; - /* users database functions */ addUserToDatabaseIfNotExists = function (obj) { /* @@ -1934,9 +2098,10 @@ async function updateAllChat(clear) { ], }) .then(async function (result) { + console.log(result); // handle result async //document.getElementById("blurOverlay").classList.add("bg-primary"); - + console.log(result); if (typeof result !== "undefined") { for (const item of result.docs) { //await otherwise history will not be in chronological order @@ -1981,6 +2146,14 @@ function getSetUserSharedFolder(selected_callsign) { console.log("return triggered"); return; } + + // disable button if broadcast + if (selected_callsign.startsWith("BC-")) { + document.getElementById("sharedFolderDXButton").disabled = true; + } else { + document.getElementById("sharedFolderDXButton").disabled = false; + } + returnObjFromCallsign(users, selected_callsign) .then(function (data) { console.log(data); @@ -2110,6 +2283,16 @@ function getSetUserInformation(selected_callsign) { console.log("return triggered"); return; } + + // disable button if broadcast + if (selected_callsign.startsWith("BC-")) { + document.getElementById("userModalDXButton").disabled = true; + document.getElementById("ping").disabled = true; + } else { + document.getElementById("userModalDXButton").disabled = false; + document.getElementById("ping").disabled = false; + } + document.getElementById("dx_user_info_callsign").innerHTML = selected_callsign; @@ -2127,6 +2310,10 @@ function getSetUserInformation(selected_callsign) { // split data string by "base64" for separating image type from base64 string atob(data.user_info_image.split(";base64,")[1]); + if (selected_callsign.startsWith("BC-")) { + data.user_info_image = defaultGroupIcon; + } + document.getElementById("dx_user_info_image").src = data.user_info_image; document.getElementById("user-image-" + selected_callsign).src = @@ -2134,9 +2321,22 @@ function getSetUserInformation(selected_callsign) { } catch (e) { console.log(e); console.log("corrupted image data"); + + if (selected_callsign.startsWith("BC-")) { + var userIcon = defaultGroupIcon; + document + .getElementById("chatModuleMessage") + .setAttribute("maxlength", 16); + } else { + var userIcon = defaultUserIcon; + document + .getElementById("chatModuleMessage") + .setAttribute("maxlength", 524288); + } + document.getElementById("user-image-" + selected_callsign).src = - defaultUserIcon; - document.getElementById("dx_user_info_image").src = defaultUserIcon; + userIcon; + document.getElementById("dx_user_info_image").src = userIcon; } } else { // throw error and use placeholder data @@ -2192,9 +2392,20 @@ function getSetUserInformation(selected_callsign) { console.log("writing user info to modal failed"); console.log(err); + if (selected_callsign.startsWith("BC-")) { + document + .getElementById("chatModuleMessage") + .setAttribute("maxlength", 16); + var userIcon = defaultGroupIcon; + } else { + var userIcon = defaultUserIcon; + document + .getElementById("chatModuleMessage") + .setAttribute("maxlength", 524288); + } + // Callsign list elements - document.getElementById("user-image-" + selected_callsign).src = - defaultUserIcon; + document.getElementById("user-image-" + selected_callsign).src = userIcon; document.getElementById("user-image-" + selected_callsign).className = "p-1 rounded-circle w-100"; document.getElementById("user-image-" + selected_callsign).style = diff --git a/gui/preload-main.js b/gui/preload-main.js index e3c86021..31eaabcb 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -1606,29 +1606,62 @@ window.addEventListener("DOMContentLoaded", () => { if (hslLastSort == 0 && hslLastSortDir == "asc") hslLastSortDir = "desc"; else hslLastSortDir = "asc"; sorthslTable(0); + resetSortIcon(); }); document.getElementById("thFreq").addEventListener("click", () => { if (hslLastSort == 1 && hslLastSortDir == "asc") hslLastSortDir = "desc"; else hslLastSortDir = "asc"; sorthslTable(1); + resetSortIcon(); }); document.getElementById("thDxcall").addEventListener("click", () => { if (hslLastSort == 3 && hslLastSortDir == "asc") hslLastSortDir = "desc"; else hslLastSortDir = "asc"; sorthslTable(3); + resetSortIcon(); }); document.getElementById("thDxgrid").addEventListener("click", () => { if (hslLastSort == 4 && hslLastSortDir == "asc") hslLastSortDir = "desc"; else hslLastSortDir = "asc"; sorthslTable(4); + resetSortIcon(); }); document.getElementById("thDist").addEventListener("click", () => { if (hslLastSort == 5 && hslLastSortDir == "asc") hslLastSortDir = "desc"; else hslLastSortDir = "asc"; sorthslTable(5); + resetSortIcon(); + }); + document.getElementById("thType").addEventListener("click", () => { + if (hslLastSort == 6 && hslLastSortDir == "asc") hslLastSortDir = "desc"; + else hslLastSortDir = "asc"; + sorthslTable(6); + resetSortIcon(); + }); + document.getElementById("thSnr").addEventListener("click", () => { + if (hslLastSort == 7 && hslLastSortDir == "asc") hslLastSortDir = "desc"; + else hslLastSortDir = "asc"; + sorthslTable(7); + resetSortIcon(); }); }); +function resetSortIcon() { + document.getElementById("hslSort").remove(); + let headers = document.querySelectorAll( + "#tblHeardStationList > thead > tr > th" + ); + if (hslLastSortDir == "desc") + text = + '' + + headers[hslLastSort].innerText; + else + text = + '' + + headers[hslLastSort].innerText; + headers[hslLastSort].innerHTML = text; +} + function connectedStation(data) { if (typeof data.dxcallsign == "undefined") { return; @@ -2791,6 +2824,9 @@ ipcRenderer.on("run-tnc-command", (event, arg) => { arg.command ); } + if (arg.command == "broadcast") { + sock.sendBroadcastChannel(arg.broadcastChannel, arg.data, arg.uuid); + } if (arg.command == "stop_transmission") { sock.stopTransmission(); } @@ -3549,7 +3585,7 @@ function changeGuiDesign(design) { } var hslLastSort = 0; -var hslLastSortDir = "asc"; +var hslLastSortDir = "desc"; //https://www.w3schools.com/howto/howto_js_sort_table.asp function sorthslTable(n) { diff --git a/gui/sock.js b/gui/sock.js index a2262971..ec3893c9 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -242,6 +242,22 @@ client.on("data", function (socketdata) { data: [data], }); break; + + case "broadcast": + // RX'd FEC BROADCAST + var encoded_data = FD.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); + } + + let Messages = { + data: messageArray, + }; + ipcRenderer.send("request-new-msg-received", Messages); + break; } switch (data["cq"]) { @@ -835,6 +851,30 @@ exports.sendFecIsWriting = function (mycallsign) { writeTncCommand(command); }; +// SEND FEC TO BROADCASTCHANNEL +exports.sendBroadcastChannel = function (channel, data_out, uuid) { + let checksum = ""; + let command = ""; + let data = FD.btoa_FD( + "m" + + split_char + + channel + + //split_char + + //checksum + + split_char + + uuid + + split_char + + data_out + ); + console.log(data.length); + let payload = data; + command = + '{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' + + payload + + '"}'; + writeTncCommand(command); +}; + // RECORD AUDIO exports.record_audio = function () { command = '{"type" : "set", "command" : "record_audio"}'; diff --git a/gui/src/chat-module.html b/gui/src/chat-module.html index 1f78ee6d..dddc0c7d 100644 --- a/gui/src/chat-module.html +++ b/gui/src/chat-module.html @@ -97,39 +97,7 @@ id="list-tab" role="tablist" style="height: calc(100vh - 70px)" - > -
-
-
- -
- - BROADCAST @ALL - --- - --- -
-
-
-
+ >
@@ -307,25 +275,7 @@ id="message-container" style="height: calc(100% - 150px)" > - +
diff --git a/gui/src/index.html b/gui/src/index.html index 8e42e8d3..18b9adfb 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -1152,14 +1152,16 @@ - + - - + + diff --git a/tnc/broadcast.py b/tnc/broadcast.py new file mode 100644 index 00000000..0d6a5b41 --- /dev/null +++ b/tnc/broadcast.py @@ -0,0 +1,118 @@ +import structlog +import threading +import helpers +import time +import modem +import base64 +from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC +import sock +import ujson as json + + +class broadcastHandler: + """Terminal Node Controller for FreeDATA""" + + log = structlog.get_logger("BROADCAST") + + def __init__(self) -> None: + self.fec_wakeup_callsign = bytes() + self.longest_duration = 6 + self.wakeup_received = False + self.broadcast_timeout_reached = False + self.broadcast_payload_bursts = 1 + self.broadcast_watchdog = threading.Thread( + target=self.watchdog, name="watchdog thread", daemon=True + ) + self.broadcast_watchdog.start() + + def received_fec_wakeup(self, data_in: bytes): + self.fec_wakeup_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7])) + self.wakeup_mode = int.from_bytes(bytes(data_in[7:8]), "big") + bursts = int.from_bytes(bytes(data_in[8:9]), "big") + self.wakeup_received = True + + modem.RECEIVE_DATAC4 = True + + self.send_data_to_socket_queue( + freedata="tnc-message", + fec="wakeup", + mode=self.wakeup_mode, + bursts=bursts, + dxcallsign=str(self.fec_wakeup_callsign, "UTF-8") + ) + + self.log.info( + "[TNC] FRAME WAKEUP RCVD [" + + str(self.fec_wakeup_callsign, "UTF-8") + + "] ", mode=self.wakeup_mode, bursts=bursts, + ) + + def received_fec(self, data_in: bytes): + print(self.fec_wakeup_callsign) + + self.send_data_to_socket_queue( + freedata="tnc-message", + fec="broadcast", + dxcallsign=str(self.fec_wakeup_callsign, "UTF-8"), + data=base64.b64encode(data_in[1:]).decode("UTF-8") + ) + + self.log.info("[TNC] FEC DATA RCVD") + + def send_data_to_socket_queue(self, **jsondata): + """ + Send information to the UI via JSON and the sock.SOCKET_QUEUE. + + Args: + Dictionary containing the data to be sent, in the format: + key=value, for each item. E.g.: + self.send_data_to_socket_queue( + freedata="tnc-message", + arq="received", + status="success", + uuid=self.transmission_uuid, + timestamp=timestamp, + mycallsign=str(self.mycallsign, "UTF-8"), + dxcallsign=str(Station.dxcallsign, "UTF-8"), + dxgrid=str(Station.dxgrid, "UTF-8"), + data=base64_data, + ) + """ + + # add mycallsign and dxcallsign to network message if they not exist + # and make sure we are not overwrite them if they exist + try: + if "mycallsign" not in jsondata: + jsondata["mycallsign"] = str(Station.mycallsign, "UTF-8") + if "dxcallsign" not in jsondata: + jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8") + except Exception as e: + self.log.debug("[TNC] error adding callsigns to network message", e=e) + + # run json dumps + json_data_out = json.dumps(jsondata) + + self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=json_data_out) + # finally push data to our network queue + sock.SOCKET_QUEUE.put(json_data_out) + + def watchdog(self): + while 1: + if self.wakeup_received: + timeout = time.time() + (self.longest_duration * self.broadcast_payload_bursts) + 2 + while time.time() < timeout: + threading.Event().wait(0.01) + + self.broadcast_timeout_reached = True + + self.log.info( + "[TNC] closing broadcast slot [" + + str(self.fec_wakeup_callsign, "UTF-8") + + "] ", mode=self.wakeup_mode, bursts=self.broadcast_payload_bursts, + ) + # TODO: We need a dynamic way of modifying this + modem.RECEIVE_DATAC4 = False + self.fec_wakeup_callsign = bytes() + self.wakeup_received = False + else: + threading.Event().wait(0.01) diff --git a/tnc/codec2.py b/tnc/codec2.py index 424ac8d3..12172be1 100644 --- a/tnc/codec2.py +++ b/tnc/codec2.py @@ -35,6 +35,7 @@ class FREEDV_MODE(Enum): fsk_ldpc_0 = 200 fsk_ldpc_1 = 201 + class FREEDV_MODE_USED_SLOTS(Enum): """ Enumeration for codec2 used slots diff --git a/tnc/config.ini b/tnc/config.ini index a0dba20c..a353bb3b 100644 --- a/tnc/config.ini +++ b/tnc/config.ini @@ -10,14 +10,14 @@ ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [AUDIO] #audio settings -rx = 10 -tx = 10 +rx = 0 +tx = 0 txaudiolevel = 250 auto_tune = False [RADIO] #radio settings -radiocontrol = rigctld +radiocontrol = disabled rigctld_ip = 127.0.0.1 rigctld_port = 4532 @@ -38,3 +38,4 @@ tx_delay = 50 [TCI] ip = 127.0.0.1 port = 50001 + diff --git a/tnc/data_handler.py b/tnc/data_handler.py index ee7d1354..dc91631d 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -28,7 +28,7 @@ import ujson as json from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER from static import FRAME_TYPE as FR_TYPE - +import broadcast TESTMODE = False @@ -101,6 +101,8 @@ class DATA: self.rx_n_frames_per_burst = 0 self.max_n_frames_per_burst = 1 + self.broadcast = broadcast.broadcastHandler() + # Flag to indicate if we received a low bandwidth mode channel opener self.received_LOW_BANDWIDTH_MODE = False @@ -221,6 +223,8 @@ class DATA: FR_TYPE.PING.value: (self.received_ping, "PING"), FR_TYPE.QRV.value: (self.received_qrv, "QRV"), FR_TYPE.IS_WRITING.value: (self.received_is_writing, "IS_WRITING"), + FR_TYPE.FEC.value: (self.broadcast.received_fec, "FEC"), + FR_TYPE.FEC_WAKEUP.value: (self.broadcast.received_fec_wakeup, "FEC WAKEUP"), } self.command_dispatcher = { @@ -324,15 +328,18 @@ class DATA: # [5] attempts self.open_dc_and_transmit(data[1], data[2], data[3], data[4], data[5]) - elif data[0] == "FEC": - # [1] DATA_OUT bytes - # [2] MODE str datac0/1/3... - self.send_fec_frame(data[1], data[2]) elif data[0] == "FEC_IS_WRITING": # [1] DATA_OUT bytes # [2] MODE str datac0/1/3... self.send_fec_is_writing(data[1]) + + elif data[0] == "FEC": + # [1] WAKEUP bool + # [2] MODE str datac0/1/3... + # [3] PAYLOAD + # [4] MYCALLSIGN + self.send_fec(data[1], data[2], data[3], data[4]) else: self.log.error( "[TNC] worker_transmit: received invalid command:", data=data @@ -392,6 +399,8 @@ class DATA: FR_TYPE.PING.value, FR_TYPE.BEACON.value, FR_TYPE.IS_WRITING.value, + FR_TYPE.FEC.value, + FR_TYPE.FEC_WAKEUP.value, ] ): @@ -2967,6 +2976,8 @@ class DATA: HamlibParam.hamlib_frequency, ) + + def received_is_writing(self, data_in: bytes) -> None: """ Called when we receive a IS WRITING frame @@ -3447,13 +3458,30 @@ class DATA: frame_to_tx=[test_frame], c2_mode=FREEDV_MODE.datac13.value ) - def send_fec_frame(self, payload, mode) -> None: + def send_fec(self, mode, wakeup, payload, mycallsign): """Send an empty test frame""" + print(wakeup) + print(payload) + print(mycallsign) mode_int = codec2.freedv_get_mode_value_by_name(mode) payload_per_frame = modem.get_bytes_per_frame(mode_int) - 2 fec_payload_length = payload_per_frame - 1 + if wakeup: + mode_int_wakeup = codec2.freedv_get_mode_value_by_name("sig0") + payload_per_wakeup_frame = modem.get_bytes_per_frame(mode_int_wakeup) - 2 + fec_wakeup_frame = bytearray(payload_per_wakeup_frame) + fec_wakeup_frame[:1] = bytes([FR_TYPE.FEC_WAKEUP.value]) + fec_wakeup_frame[1:7] = helpers.callsign_to_bytes(mycallsign) + fec_wakeup_frame[7:8] = bytes([mode_int]) + fec_wakeup_frame[8:9] = bytes([1]) # n payload bursts + print(mode_int_wakeup) + + self.enqueue_frame_for_tx( + frame_to_tx=[fec_wakeup_frame], c2_mode=codec2.FREEDV_MODE["sig1"].value + ) + time.sleep(1) fec_frame = bytearray(payload_per_frame) fec_frame[:1] = bytes([FR_TYPE.FEC.value]) fec_frame[1:payload_per_frame] = bytes(payload[:fec_payload_length]) @@ -3462,7 +3490,7 @@ class DATA: ) def send_fec_is_writing(self, mycallsign) -> None: - """Send an empty test frame""" + """Send an fec is writing frame""" fec_frame = bytearray(14) fec_frame[:1] = bytes([FR_TYPE.IS_WRITING.value]) @@ -3477,6 +3505,7 @@ class DATA: else: return False + def save_data_to_folder(self, transmission_uuid, timestamp, diff --git a/tnc/sock.py b/tnc/sock.py index b150df89..fb30e33c 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -482,12 +482,22 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): def tnc_fec_transmit(self, received_json): try: mode = received_json["mode"] + wakeup = received_json["wakeup"] base64data = received_json["payload"] if len(base64data) % 4: raise TypeError payload = base64.b64decode(base64data) - DATA_QUEUE_TRANSMIT.put(["FEC", payload, mode]) + try: + mycallsign = received_json["mycallsign"] + mycallsign = helpers.callsign_to_bytes(mycallsign) + mycallsign = helpers.bytes_to_callsign(mycallsign) + + except Exception: + mycallsign = Station.mycallsign + + + DATA_QUEUE_TRANSMIT.put(["FEC", mode, wakeup, payload, mycallsign]) command_response("fec_transmit", True) except Exception as err: command_response("fec_transmit", False) diff --git a/tnc/static.py b/tnc/static.py index 4a6f4a4b..99744389 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -127,7 +127,7 @@ class TCIParam: @dataclass class TNC: - version = "0.9.1-alpha.3" + version = "0.9.2-alpha.1" host: str = "0.0.0.0" port: int = 3000 SOCKET_TIMEOUT: int = 1 # seconds @@ -174,6 +174,7 @@ class FRAME_TYPE(Enum): ARQ_STOP = 249 BEACON = 250 FEC = 251 + FEC_WAKEUP = 252 IDENT = 254 TEST_FRAME = 255
Time + Time + Frequency   DXCall DXGrid DistanceTypeSNR (rx/dx)TypeSNR (rx/dx)