diff --git a/gui/main.js b/gui/main.js index 86a0b454..24a2d198 100644 --- a/gui/main.js +++ b/gui/main.js @@ -454,6 +454,9 @@ ipcMain.on('request-update-rx-msg-buffer', (event, arg) => { ipcMain.on('request-new-msg-received', (event, arg) => { chat.webContents.send('action-new-msg-received', arg); }); +ipcMain.on('request-update-transmission-status', (event, arg) => { + chat.webContents.send('action-update-transmission-status', arg); +}); ipcMain.on('request-open-tnc-log', (event) => { logViewer.show(); diff --git a/gui/package.json b/gui/package.json index df00b32e..e984e507 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,6 +1,6 @@ { "name": "FreeDATA", - "version": "0.1.2-alpha", + "version": "0.2.0-alpha", "description": "FreeDATA ", "main": "main.js", "scripts": { @@ -35,8 +35,12 @@ "chartjs-plugin-annotation": "^1.0.2", "electron-log": "^4.4.6", "electron-updater": "^5.0.0", + "emoji-picker-element": "^1.11.0", + "emoji-picker-element-data": "^1.3.0", "pouchdb": "^7.2.2", + "pouchdb-find": "^7.2.2", "qth-locator": "^2.1.0", + "utf8": "^3.0.0", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/gui/preload-chat.js b/gui/preload-chat.js index 851c43ec..676f8fc6 100644 --- a/gui/preload-chat.js +++ b/gui/preload-chat.js @@ -4,6 +4,7 @@ const { } = require('electron') const { v4: uuidv4 } = require('uuid'); +const utf8 = require('utf8'); // https://stackoverflow.com/a/26227660 var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config") @@ -26,6 +27,7 @@ var chatDB = path.join(configFolder, 'chatDB') // ---- MessageDB var PouchDB = require('pouchdb'); +PouchDB.plugin(require('pouchdb-find')); var db = new PouchDB(chatDB); @@ -37,58 +39,121 @@ var db = new PouchDB(chatDB); + + var dxcallsigns = new Set(); -db.allDocs({ - include_docs: true, - attachments: true + + + + + +db.createIndex({ + index: { + fields: ['timestamp', 'uuid', 'dxcallsign', 'dxgrid', 'msg', 'checksum', 'type', 'command', 'status'] + } }).then(function (result) { // handle result - // get all dxcallsigns and append to list - result.rows.forEach(function(item) { - update_chat(item.doc) - }); - + console.log(result) }).catch(function (err) { console.log(err); }); + +db.find({ + selector: { + timestamp: {$exists: true}}, + sort: [{'timestamp': 'asc'}] + }).then(function (result) { + // handle result + console.log(result); + console.log(typeof(result)); + if(typeof(result) !== 'undefined'){ + result.docs.forEach(function(item) { + + update_chat(item); + + }); + + } +}).catch(function (err) { + console.log(err); +}); + + // WINDOW LISTENER window.addEventListener('DOMContentLoaded', () => { + + document.querySelector('emoji-picker').addEventListener("emoji-click", (event) => { + document.getElementById('chatModuleMessage').setRangeText(event.detail.emoji.unicode) + + + console.log(event.detail); +}) + + + +document.getElementById("emojipickerbutton").addEventListener("click", () => { +var element = document.getElementById("emojipickercontainer") + + console.log(element.style.display); + + + if (element.style.display === "none") { + element.style.display = "block"; + } else { + element.style.display = "none"; + } + + + + +}) + + // SEND MSG document.getElementById("sendMessage").addEventListener("click", () => { - - var dxcallsign = document.getElementById('chatModuleDxCall').value - dxcallsign = dxcallsign.toUpperCase() - message = document.getElementById('chatModuleMessage').value - console.log(dxcallsign) + document.getElementById('emojipickercontainer').style.display = "none"; + var dxcallsign = document.getElementById('chatModuleDxCall').value; + dxcallsign = dxcallsign.toUpperCase(); + + var chatmessage = document.getElementById('chatModuleMessage').value; + //chatmessage = Buffer.from(chatmessage, 'utf-8').toString(); + + + + var uuid = uuidv4(); + console.log(chatmessage) let Data = { command: "send_message", dxcallsign : dxcallsign, mode : 255, frames : 1, - data : message, - checksum : '123' + data : chatmessage, + checksum : '123', + uuid : uuid }; ipcRenderer.send('run-tnc-command', Data); - var uuid = uuidv4(); + db.post({ _id: uuid, timestamp: Math.floor(Date.now() / 1000), dxcallsign: dxcallsign, dxgrid: 'NULL', - msg: message, + msg: chatmessage, checksum: 'NULL', - type: "transmit" + type: "transmit", + status: 'transmit', + uuid: uuid }).then(function (response) { // handle response - console.log("new database entry") - console.log(response) + console.log("new database entry"); + console.log(response); }).catch(function (err) { console.log(err); @@ -110,42 +175,79 @@ window.addEventListener('DOMContentLoaded', () => { // clear input document.getElementById('chatModuleMessage').value = '' - - + }) +}); +ipcRenderer.on('action-update-transmission-status', (event, arg) => { + + console.log(arg.status); + console.log(arg.uuid); + + +db.get(arg.uuid).then(function(doc) { + + return db.put({ + _id: arg.uuid, + _rev: doc._rev, + timestamp: doc.timestamp, + dxcallsign: doc.dxcallsign, + dxgrid: doc.dxgrid, + msg: doc.msg, + checksum: doc.checksum, + type: "transmit", + status: arg.status, + uuid: doc.uuid + }); +}).then(function(response) { + // handle response + db.get(arg.uuid).then(function (doc) { + // handle doc + update_chat(doc); + }).catch(function (err) { + console.log(err); + }); +}).catch(function (err) { + console.log(err); +}); + + + + + }); ipcRenderer.on('action-new-msg-received', (event, arg) => { - console.log(arg.data) + console.log(arg); + console.log(arg.data); + + var new_msg = arg.data; - var new_msg = arg.data new_msg.forEach(function(item) { - console.log(item) + console.log(item); //for (i = 0; i < arg.data.length; i++) { let obj = new Object(); var encoded_data = atob(item.data); - var splitted_data = encoded_data.split(split_char) - + var splitted_data = encoded_data.split(split_char); + console.log(utf8.decode(splitted_data[3])); //obj.uuid = item.uuid; - item.checksum = splitted_data[2] - item.msg = splitted_data[3] + item.command = splitted_data[1]; + item.checksum = splitted_data[2]; + // convert message to unicode from utf8 because of emojis + item.uuid = utf8.decode(splitted_data[3]); + item.msg = utf8.decode(splitted_data[4]); //obj.dxcallsign = item.dxcallsign; //obj.dxgrid = item.dxgrid; //obj.timestamp = item.timestamp; - - - - - + item.status = 'null'; // check if message not exists in database. @@ -161,15 +263,18 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => { db.put({ _id: item.uuid, timestamp: item.timestamp, + uuid: item.uuid, dxcallsign: item.dxcallsign, dxgrid: item.dxgrid, msg: item.msg, checksum: item.checksum, - type: "received" + type : "received", + command : item.command, + status : item.status }).then(function (response) { // handle response - console.log("new database entry") - console.log(response) + console.log("new database entry"); + console.log(response); }).catch(function (err) { console.log(err); @@ -182,7 +287,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => { // handle doc // timestamp - update_chat(doc) + update_chat(doc); }).catch(function (err) { console.log(err); }); @@ -190,43 +295,26 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => { console.log("...................................") return - - - - - - - - }); - - - - - - }); - - - - - - // Update chat list update_chat = function(obj) { - + +console.log(obj); var dxcallsign = obj.dxcallsign; + var timestamp = dateFormat.format(obj.timestamp * 1000); + var dxgrid = obj.dxgrid; // CALLSIGN LIST if(!(document.getElementById('chat-' + dxcallsign + '-list'))){ var new_callsign = ` - +
${dxcallsign}
- + ${dxgrid}
@@ -251,10 +339,22 @@ update_chat = function(obj) { } + // APPEND MESSAGES TO CALLSIGN - var timestamp = dateFormat.format(obj.timestamp * 1000); + if (obj.status == 'transmit'){ + var message_class = 'card text-right border-primary bg-primary'; + }else if (obj.status == 'transmitting'){ + var message_class = 'card text-right border-warning bg-warning'; + }else if (obj.status == 'failed'){ + var message_class = 'card text-right border-danger bg-danger'; + }else if (obj.status == 'success'){ + var message_class = 'card text-right border-success bg-success'; + } else { + var message_class = 'card text-right border-secondary bg-secondary'; + } + if(!(document.getElementById('msg-' + obj._id))){ if (obj.type == 'received'){ @@ -271,25 +371,30 @@ update_chat = function(obj) { } if (obj.type == 'transmit'){ - + var new_message = `

${timestamp}

-
+

${obj.msg}

+
`; - } + } var id = "chat-" + obj.dxcallsign document.getElementById(id).insertAdjacentHTML("beforeend", new_message); var element = document.getElementById("message-container"); element.scrollTo(0,element.scrollHeight); - } - + } else if(document.getElementById('msg-' + obj._id)) { + id = "msg-" + obj._id; + document.getElementById(id).className = message_class; + } } + + diff --git a/gui/preload-main.js b/gui/preload-main.js index f45a5e33..730488e6 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -368,6 +368,7 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () // on change ping callsign document.getElementById("dataModalDxCall").addEventListener("change", () => { document.getElementById("dxCall").value = document.getElementById("dataModalDxCall").value; + }); @@ -762,6 +763,8 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () var bstransmitFileSidebar = new bootstrap.Offcanvas(transmitFileSidebar) bstransmitFileSidebar.show() */ + + var fileList = document.getElementById("dataModalFile").files; @@ -786,7 +789,10 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () data: data, checksum: '123123123', }; - ipcRenderer.send('run-tnc-command', Data); + // only send command if dxcallsign entered and we have a file selected + if(document.getElementById("dataModalDxCall").value.length > 0){ + ipcRenderer.send('run-tnc-command', Data); + } }; reader.onerror = function(e) { // error occurred @@ -794,7 +800,7 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () }; }) - // START TRANSMISSION + // STOP TRANSMISSION document.getElementById("stopTransmission").addEventListener("click", () => { let Data = { command: "stop_transmission" @@ -802,6 +808,16 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () ipcRenderer.send('run-tnc-command', Data); }) + // STOP TRANSMISSION AND CONNECRTION + document.getElementById("stop_transmission_connection").addEventListener("click", () => { + let Data = { + command: "stop_transmission" + }; + ipcRenderer.send('run-tnc-command', Data); + sock.disconnectARQ(); + }) + + // OPEN CHAT MODULE document.getElementById("openRFChat").addEventListener("click", () => { let Data = { @@ -810,6 +826,9 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', () ipcRenderer.send('request-show-chat-window', Data); }) + + + }) @@ -1675,15 +1694,13 @@ ipcRenderer.on('action-update-rx-buffer', (event, arg) => { tbl.appendChild(row); // https://stackoverflow.com/a/26227660 - var appDataFolder = process.env.HOME; - console.log("appDataFolder:" + appDataFolder); - var applicationFolder = path.join(appDataFolder, "FreeDATA"); - console.log(applicationFolder); + //var appDataFolder = process.env.HOME; + //console.log("appDataFolder:" + appDataFolder); + //var applicationFolder = path.join(appDataFolder, "FreeDATA"); + //console.log(applicationFolder); //var receivedFilesFolder = path.join(applicationFolder, "receivedFiles"); var receivedFilesFolder = path.join(config.received_files_folder); - - - + console.log("receivedFilesFolder: " + receivedFilesFolder); // Creates receivedFiles folder if not exists // https://stackoverflow.com/a/13544465 @@ -1728,7 +1745,7 @@ ipcRenderer.on('run-tnc-command', (event, arg) => { sock.sendFile(arg.dxcallsign, arg.mode, arg.frames, arg.filename, arg.filetype, arg.data, arg.checksum); } if (arg.command == 'send_message') { - sock.sendMessage(arg.dxcallsign, arg.mode, arg.frames, arg.data, arg.checksum); + sock.sendMessage(arg.dxcallsign, arg.mode, arg.frames, arg.data, arg.checksum, arg.uuid, arg.command); } if (arg.command == 'stop_transmission') { sock.stopTransmission(); diff --git a/gui/sock.js b/gui/sock.js index 534f324d..10a185ff 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -6,7 +6,9 @@ const { const log = require('electron-log'); const socketLog = log.scope('tnc'); - +const utf8 = require('utf8'); + + // https://stackoverflow.com/a/26227660 var appDataFolder = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Application Support' : process.env.HOME + "/.config") var configFolder = path.join(appDataFolder, "FreeDATA"); @@ -132,7 +134,6 @@ client.on('data', function(socketdata) { stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in */ - socketdata = socketdata.toString('utf8'); // convert data to string socketchunk += socketdata// append data to buffer so we can stick long data together @@ -218,6 +219,20 @@ client.on('data', function(socketdata) { ipcRenderer.send('request-update-tnc-state', Data); } + // update transmission status + + if (data['arq'] == 'transmission'){ + socketLog.info(data) + + let state = { + status: data['status'], + uuid: data['uuid'], + }; + ipcRenderer.send('request-update-transmission-status', state); + + + } + /* A TEST WITH STREAMING DATA .... */ // if we received data through network stream, we get a single data item if (data['arq'] == 'received') { @@ -359,17 +374,23 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data, } // Send Message -exports.sendMessage = function(dxcallsign, mode, frames, data, checksum) { +exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) { socketLog.info(data) - + // convert message to plain utf8 because of unicode emojis + data = utf8.encode(data) + socketLog.info(data) + var datatype = "m" - data = datatype + split_char + split_char + checksum + split_char + data + data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data socketLog.info(data) + + + socketLog.info(btoa(data)) data = btoa(data) //command = '{"type" : "arq", "command" : "send_message", "parameter" : [{ "dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '" , "checksum" : "' + checksum + '"}]}' - command = '{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '"}]}' + command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '"}]}' socketLog.info(command) socketLog.info("-------------------------------------") writeTncCommand(command) diff --git a/gui/src/chat-module.html b/gui/src/chat-module.html index 9572bbcb..41be7287 100644 --- a/gui/src/chat-module.html +++ b/gui/src/chat-module.html @@ -20,18 +20,24 @@ + + + + +
-
+
-
+
-
@@ -61,7 +67,16 @@
- + + + + + + + + + +
diff --git a/gui/src/index.html b/gui/src/index.html index 9e8fdfa8..45ed35df 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -60,7 +60,15 @@
+ + +
PTT Port diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 512ef434..c6772257 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -43,6 +43,7 @@ class DATA(): self.arq_session_timeout = 30 self.session_connect_max_retries = 3 + self.transmission_uuid = '' self.data_channel_last_received = 0.0 # time of last "live sign" of a frame @@ -151,7 +152,8 @@ class DATA(): # [1] DATA_OUT bytes # [2] MODE int # [3] N_FRAMES_PER_BURST int - self.open_dc_and_transmit(data[1], data[2], data[3]) + # [4] self.transmission_uuid str + self.open_dc_and_transmit(data[1], data[2], data[3], data[4]) ''' print(static.ARQ_SESSION) if not static.ARQ_SESSION: @@ -443,7 +445,6 @@ class DATA(): # updated modes we are listening to self.set_listening_modes(self.mode_list[self.speed_level]) - # create an ack frame ack_frame = bytearray(14) ack_frame[:1] = bytes([60]) @@ -553,9 +554,8 @@ class DATA(): base64_data = base64_data.decode("utf-8") static.RX_BUFFER.append([uniqueid, timestamp, static.DXCALLSIGN, static.DXGRID, base64_data]) jsondata = {"arq":"received", "uuid" : uniqueid, "timestamp": timestamp, "dxcallsign": str(static.DXCALLSIGN, 'utf-8'), "dxgrid": str(static.DXGRID, 'utf-8'), "data": base64_data} - data_out = json.dumps(jsondata) - print(data_out) - sock.SOCKET_QUEUE.put(data_out) + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) static.INFO.append("ARQ;RECEIVING;SUCCESS") # BUILDING ACK FRAME FOR DATA FRAME @@ -645,6 +645,12 @@ class DATA(): static.TOTAL_BYTES = len(data_out) frame_total_size = len(data_out).to_bytes(4, byteorder='big') static.INFO.append("ARQ;TRANSMITTING") + + jsondata = {"arq":"transmission", "status" :"transmitting", "uuid" : self.transmission_uuid} + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) + + structlog.get_logger("structlog").info("[TNC] | TX | DATACHANNEL", mode=mode, Bytes=static.TOTAL_BYTES) @@ -680,6 +686,7 @@ class DATA(): # force usage of selected mode if mode != 255: data_mode = mode + structlog.get_logger("structlog").debug("FIXED MODE", mode=data_mode) else: @@ -810,13 +817,20 @@ class DATA(): if self.data_frame_ack_received: static.INFO.append("ARQ;TRANSMITTING;SUCCESS") - + jsondata = {"arq":"transmission", "status" :"success", "uuid" : self.transmission_uuid} + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) + structlog.get_logger("structlog").info("ARQ | TX | DATA TRANSMITTED!", BytesPerMinute=static.ARQ_BYTES_PER_MINUTE, BitsPerSecond=static.ARQ_BITS_PER_SECOND, overflows=static.BUFFER_OVERFLOW_COUNTER) else: static.INFO.append("ARQ;TRANSMITTING;FAILED") + jsondata = {"arq":"transmission", "status" :"failed", "uuid" : self.transmission_uuid} + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) + structlog.get_logger("structlog").info("ARQ | TX | TRANSMISSION FAILED OR TIME OUT!", overflows=static.BUFFER_OVERFLOW_COUNTER) self.stop_transmission() @@ -902,7 +916,9 @@ class DATA(): """ static.INFO.append("ARQ;TRANSMITTING;FAILED") - + jsondata = {"arq":"transmission", "status" : "failed", "uuid" : self.transmission_uuid} + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) self.arq_session_last_received = int(time.time()) # we need to update our timeout timestamp if not TESTMODE: @@ -1133,7 +1149,7 @@ class DATA(): # ARQ DATA CHANNEL HANDLER # ############################################################################################################ - def open_dc_and_transmit(self, data_out:bytes, mode:int, n_frames_per_burst:int): + def open_dc_and_transmit(self, data_out:bytes, mode:int, n_frames_per_burst:int, transmission_uuid:str): """ Args: @@ -1147,6 +1163,8 @@ class DATA(): static.TNC_STATE = 'BUSY' self.arq_file_transfer = True + self.transmission_uuid = transmission_uuid + # wait a moment for the case, an heartbeat is already on the way back to us if static.ARQ_SESSION: time.sleep(0.5) @@ -1226,6 +1244,10 @@ class DATA(): if not static.ARQ_STATE and attempt == self.data_channel_max_retries: static.INFO.append("DATACHANNEL;FAILED") + print(self.transmission_uuid) + jsondata = {"arq":"transmission", "status" :"failed", "uuid" : self.transmission_uuid} + json_data_out = json.dumps(jsondata) + sock.SOCKET_QUEUE.put(json_data_out) structlog.get_logger("structlog").warning("[TNC] ARQ | TX | DATA [" + str(static.MYCALLSIGN, 'utf-8') + "]>>X<<[" + str(static.DXCALLSIGN, 'utf-8') + "]") self.datachannel_timeout = True @@ -1582,6 +1604,9 @@ class DATA(): """ try: + print(static.TOTAL_BYTES) + if static.TOTAL_BYTES == 0: + static.TOTAL_BYTES = 1 static.ARQ_TRANSMISSION_PERCENT = int((receivedbytes*static.ARQ_COMPRESSION_FACTOR / (static.TOTAL_BYTES)) * 100) transmissiontime = time.time() - self.rx_start_of_transmission diff --git a/tnc/modem.py b/tnc/modem.py index b9f0610a..2911dbc7 100644 --- a/tnc/modem.py +++ b/tnc/modem.py @@ -524,7 +524,8 @@ class RF(): modem_stats_snr = modem_stats_snr.value try: - static.SNR = round(modem_stats_snr, 1) + snr = round(modem_stats_snr, 1) + static.SNR = np.clip(snr, 0, 255) #limit to max value of 255 return static.SNR except: static.SNR = 0 diff --git a/tnc/rig.py b/tnc/rig.py index 25c7c3be..1a0b42bb 100644 --- a/tnc/rig.py +++ b/tnc/rig.py @@ -38,7 +38,7 @@ try: # this is not needed as python will be shipped with app bundle sys.path.append('/usr/local/lib/python3.6/site-packages') sys.path.append('/usr/local/lib/python3.7/site-packages') - #sys.path.append('/usr/local/lib/python3.8/site-packages') + sys.path.append('/usr/local/lib/python3.8/site-packages') sys.path.append('/usr/local/lib/python3.9/site-packages') sys.path.append('/usr/local/lib/python3.10/site-packages') diff --git a/tnc/sock.py b/tnc/sock.py index efe645d7..f77a2c44 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -302,10 +302,16 @@ def process_tnc_commands(data): n_frames = int(received_json["parameter"][0]["n_frames"]) base64data = received_json["parameter"][0]["data"] + # check if transmission uuid provided else set no-uuid + try: + arq_uuid = received_json["uuid"] + except: + arq_uuid = 'no-uuid' + if not len(base64data) % 4: binarydata = base64.b64decode(base64data) - data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', binarydata, mode, n_frames]) + data_handler.DATA_QUEUE_TRANSMIT.put(['ARQ_RAW', binarydata, mode, n_frames, arq_uuid]) else: raise TypeError diff --git a/tnc/static.py b/tnc/static.py index 81467069..b87d37b2 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -8,7 +8,7 @@ Here we are saving application wide variables and stats, which have to be access Not nice, suggestions are appreciated :-) """ -VERSION = '0.1.0-alpha' +VERSION = '0.2.0-alpha' # DAEMON DAEMONPORT = 3001