mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge pull request #311 from DJ2LS/ls-arq
arq improvements from OTA tests
This commit is contained in:
commit
0d13b638ad
2
.github/workflows/build_multiplatform.yml
vendored
2
.github/workflows/build_multiplatform.yml
vendored
|
@ -268,7 +268,7 @@ jobs:
|
|||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
run: |
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||
|
||||
- name: Install MacOS pyAudio
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
|
|
|
@ -38,10 +38,13 @@
|
|||
"electron-updater": "^5.2.1",
|
||||
"emoji-picker-element": "^1.12.1",
|
||||
"emoji-picker-element-data": "^1.3.0",
|
||||
"express-pouchdb": "^4.2.0",
|
||||
"mime": "^3.0.0",
|
||||
"pouchdb": "^7.3.0",
|
||||
"pouchdb-browser": "^7.3.0",
|
||||
"pouchdb-express-router": "^0.0.11",
|
||||
"pouchdb-find": "^7.3.0",
|
||||
"pouchdb-replication": "^8.0.0",
|
||||
"qth-locator": "^2.1.0",
|
||||
"utf8": "^3.0.0",
|
||||
"uuid": "^9.0.0"
|
||||
|
|
|
@ -34,7 +34,7 @@ const dateFormatHours = new Intl.DateTimeFormat('en-GB', {
|
|||
hour12: false,
|
||||
});
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
const split_char = '\0;\1;'
|
||||
// global for our selected file we want to transmit
|
||||
// ----------------- some chat globals
|
||||
var filetype = '';
|
||||
|
@ -61,25 +61,51 @@ try{
|
|||
}
|
||||
|
||||
PouchDB.plugin(require('pouchdb-find'));
|
||||
var db = new PouchDB(chatDB);
|
||||
var remoteDB = new PouchDB('http://192.168.178.79:5984/chatDB')
|
||||
//PouchDB.plugin(require('pouchdb-replication'));
|
||||
|
||||
db.sync(remoteDB, {
|
||||
var db = new PouchDB(chatDB);
|
||||
|
||||
/*
|
||||
// REMOTE SYNC ATTEMPTS
|
||||
|
||||
|
||||
var remoteDB = new PouchDB('http://172.20.10.4:5984/chatDB')
|
||||
|
||||
// we need express packages for running pouchdb sync "express-pouchdb"
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
//app.use('/chatDB', require('express-pouchdb')(PouchDB));
|
||||
//app.listen(5984);
|
||||
|
||||
app.use('/chatDB', require('pouchdb-express-router')(PouchDB));
|
||||
app.listen(5984);
|
||||
|
||||
|
||||
|
||||
db.sync('http://172.20.10.4:5984/jojo', {
|
||||
//var sync = PouchDB.sync('chatDB', 'http://172.20.10.4:5984/chatDB', {
|
||||
live: true,
|
||||
retry: true
|
||||
retry: false
|
||||
}).on('change', function (change) {
|
||||
// yo, something changed!
|
||||
console.log(change)
|
||||
}).on('paused', function (info) {
|
||||
}).on('paused', function (err) {
|
||||
// replication was paused, usually because of a lost connection
|
||||
console.log(info)
|
||||
console.log(err)
|
||||
}).on('active', function (info) {
|
||||
// replication was resumed
|
||||
console.log(info)
|
||||
}).on('error', function (err) {
|
||||
// totally unhandled error (shouldn't happen)
|
||||
console.log(error)
|
||||
console.log(err)
|
||||
}).on('denied', function (err) {
|
||||
// a document failed to replicate (e.g. due to permissions)
|
||||
console.log(err)
|
||||
}).on('complete', function (info) {
|
||||
// handle complete;
|
||||
console.log(info)
|
||||
});
|
||||
*/
|
||||
|
||||
var dxcallsigns = new Set();
|
||||
db.createIndex({
|
||||
|
@ -297,7 +323,9 @@ db.post({
|
|||
}
|
||||
var timestamp = Math.floor(Date.now() / 1000);
|
||||
|
||||
var data_with_attachment = chatmessage + split_char + filename + split_char + filetype + split_char + file + split_char + timestamp;
|
||||
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();
|
||||
|
@ -308,7 +336,7 @@ db.post({
|
|||
mode: 255,
|
||||
frames: 1,
|
||||
data: data_with_attachment,
|
||||
checksum: '123',
|
||||
checksum: file_checksum,
|
||||
uuid: uuid
|
||||
};
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
|
@ -318,7 +346,7 @@ db.post({
|
|||
dxcallsign: dxcallsign,
|
||||
dxgrid: 'null',
|
||||
msg: chatmessage,
|
||||
checksum: 'null',
|
||||
checksum: file_checksum,
|
||||
type: "transmit",
|
||||
status: 'transmit',
|
||||
uuid: uuid,
|
||||
|
@ -404,7 +432,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
|
||||
//handle ping
|
||||
if (item.ping == 'received') {
|
||||
obj.timestamp = item.timestamp;
|
||||
obj.timestamp = parseInt(item.timestamp);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.uuid = item.uuid;
|
||||
|
@ -421,12 +449,9 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
add_obj_to_database(obj)
|
||||
update_chat_obj_by_uuid(obj.uuid);
|
||||
|
||||
|
||||
|
||||
|
||||
// handle beacon
|
||||
} else if (item.beacon == 'received') {
|
||||
obj.timestamp = item.timestamp;
|
||||
obj.timestamp = parseInt(item.timestamp);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.uuid = item.uuid;
|
||||
|
@ -451,20 +476,20 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
|||
|
||||
console.log(splitted_data)
|
||||
|
||||
obj.timestamp = splitted_data[8];
|
||||
obj.timestamp = parseInt(splitted_data[4]);
|
||||
obj.dxcallsign = item.dxcallsign;
|
||||
obj.dxgrid = item.dxgrid;
|
||||
obj.command = splitted_data[1];
|
||||
obj.checksum = splitted_data[2];
|
||||
// convert message to unicode from utf8 because of emojis
|
||||
obj.uuid = utf8.decode(splitted_data[3]);
|
||||
obj.msg = utf8.decode(splitted_data[4]);
|
||||
obj.msg = utf8.decode(splitted_data[5]);
|
||||
obj.status = 'null';
|
||||
obj.snr = 'null';
|
||||
obj.type = 'received';
|
||||
obj.filename = utf8.decode(splitted_data[5]);
|
||||
obj.filetype = utf8.decode(splitted_data[6]);
|
||||
obj.file = btoa(utf8.decode(splitted_data[7]));
|
||||
obj.filename = utf8.decode(splitted_data[6]);
|
||||
obj.filetype = utf8.decode(splitted_data[7]);
|
||||
obj.file = btoa(splitted_data[8]);
|
||||
|
||||
add_obj_to_database(obj);
|
||||
update_chat_obj_by_uuid(obj.uuid);
|
||||
|
@ -839,7 +864,9 @@ update_chat = function(obj) {
|
|||
}).then(function(){
|
||||
|
||||
console.log(binaryString)
|
||||
var data_with_attachment = doc.msg + split_char + filename + split_char + filetype + split_char + binaryString + split_char + doc.timestamp;
|
||||
console.log(binaryString.length)
|
||||
|
||||
var data_with_attachment = doc.timestamp + split_char + utf8.encode(doc.msg) + split_char + filename + split_char + filetype + split_char + binaryString;
|
||||
let Data = {
|
||||
command: "send_message",
|
||||
dxcallsign: doc.dxcallsign,
|
||||
|
@ -939,7 +966,7 @@ update_chat_obj_by_uuid = function(uuid) {
|
|||
add_obj_to_database = function(obj){
|
||||
db.put({
|
||||
_id: obj.uuid,
|
||||
timestamp: obj.timestamp,
|
||||
timestamp: parseInt(obj.timestamp),
|
||||
uuid: obj.uuid,
|
||||
dxcallsign: obj.dxcallsign,
|
||||
dxgrid: obj.dxgrid,
|
||||
|
@ -969,3 +996,36 @@ function scrollMessagesToBottom() {
|
|||
var messageBody = document.getElementById('message-container');
|
||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CRC CHECKSUMS
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
|
||||
var makeCRCTable = function(){
|
||||
var c;
|
||||
var crcTable = [];
|
||||
for(var n =0; n < 256; n++){
|
||||
c = n;
|
||||
for(var k =0; k < 8; k++){
|
||||
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
}
|
||||
crcTable[n] = c;
|
||||
}
|
||||
return crcTable;
|
||||
}
|
||||
|
||||
var crc32 = function(str) {
|
||||
var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
|
||||
var crc = 0 ^ (-1);
|
||||
|
||||
for (var i = 0; i < str.length; i++ ) {
|
||||
crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
|
||||
}
|
||||
|
||||
return (crc ^ (-1)) >>> 0;
|
||||
};
|
|
@ -38,6 +38,19 @@ var dbfs_level_raw = 0
|
|||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
|
||||
|
||||
|
||||
// start stop audio recording event listener
|
||||
document.getElementById("startStopRecording").addEventListener("click", () => {
|
||||
let Data = {
|
||||
type: "set",
|
||||
command: "record_audio",
|
||||
};
|
||||
ipcRenderer.send('run-tnc-command', Data);
|
||||
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('received_files_folder').addEventListener('click', () => {
|
||||
|
||||
ipcRenderer.send('get-folder-path',{
|
||||
|
@ -96,8 +109,8 @@ document.getElementById('openReceivedFilesFolder').addEventListener('click', ()
|
|||
// hamlib settings
|
||||
document.getElementById('hamlib_deviceid').value = config.hamlib_deviceid;
|
||||
|
||||
set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport)
|
||||
set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
|
||||
set_setting_switch("enable_hamlib_deviceport", "hamlib_deviceport", config.enable_hamlib_deviceport)
|
||||
set_setting_switch("enable_hamlib_ptt_port", "hamlib_ptt_port", config.enable_hamlib_ptt_port)
|
||||
|
||||
document.getElementById('hamlib_serialspeed').value = config.hamlib_serialspeed;
|
||||
set_setting_switch("enable_hamlib_serialspeed", "hamlib_serialspeed", config.enable_hamlib_serialspeed)
|
||||
|
@ -1441,6 +1454,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
|||
document.getElementById("ptt_state").className = "btn btn-sm btn-secondary";
|
||||
}
|
||||
|
||||
// AUDIO RECORDING
|
||||
if (arg.audio_recording == 'True') {
|
||||
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||
document.getElementById("startStopRecording").innerHTML = "Stop Rec"
|
||||
} else if (arg.ptt_state == 'False') {
|
||||
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||
document.getElementById("startStopRecording").innerHTML = "Start Rec"
|
||||
} else {
|
||||
document.getElementById("startStopRecording").className = "btn btn-sm btn-danger";
|
||||
document.getElementById("startStopRecording").innerHTML = "Start Rec"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// CHANNEL BUSY STATE
|
||||
if (arg.channel_busy == 'True') {
|
||||
document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
|
||||
|
@ -2075,6 +2104,9 @@ ipcRenderer.on('run-tnc-command', (event, arg) => {
|
|||
if (arg.command == 'set_tx_audio_level') {
|
||||
sock.setTxAudioLevel(arg.tx_audio_level);
|
||||
}
|
||||
if (arg.command == 'record_audio') {
|
||||
sock.record_audio();
|
||||
}
|
||||
if (arg.command == 'send_test_frame') {
|
||||
sock.sendTestFrame();
|
||||
}
|
||||
|
|
41
gui/sock.js
41
gui/sock.js
|
@ -19,7 +19,7 @@ var client = new net.Socket();
|
|||
var socketchunk = ''; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
const split_char = '\0;'
|
||||
const split_char = '\0;\1;'
|
||||
|
||||
// globals for getting new data only if available so we are saving bandwidth
|
||||
var rxBufferLengthTnc = 0
|
||||
|
@ -227,6 +227,8 @@ client.on('data', function(socketdata) {
|
|||
stations: data['stations'],
|
||||
beacon_state: data['beacon_state'],
|
||||
hamlib_status: data['hamlib_status'],
|
||||
listen: data['listen'],
|
||||
audio_recording: data['audio_recording'],
|
||||
};
|
||||
|
||||
ipcRenderer.send('request-update-tnc-state', Data);
|
||||
|
@ -515,20 +517,25 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data,
|
|||
|
||||
// Send Message
|
||||
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) {
|
||||
socketLog.info(data)
|
||||
//socketLog.info(data)
|
||||
|
||||
// Disabled this here
|
||||
// convert message to plain utf8 because of unicode emojis
|
||||
data = utf8.encode(data)
|
||||
socketLog.info(data)
|
||||
//data = utf8.encode(data)
|
||||
|
||||
//socketLog.info(data)
|
||||
|
||||
|
||||
var datatype = "m"
|
||||
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
||||
socketLog.info(data)
|
||||
|
||||
|
||||
|
||||
socketLog.info(btoa(data))
|
||||
//socketLog.info(data)
|
||||
console.log(data)
|
||||
|
||||
console.log("CHECKSUM" + checksum)
|
||||
//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", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}'
|
||||
socketLog.info(command)
|
||||
|
@ -584,7 +591,11 @@ exports.sendTestFrame = function() {
|
|||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
|
||||
// RECORD AUDIO
|
||||
exports.record_audio = function() {
|
||||
command = '{"type" : "set", "command" : "record_audio"}'
|
||||
writeTncCommand(command)
|
||||
}
|
||||
|
||||
ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
||||
client.destroy();
|
||||
|
@ -602,4 +613,12 @@ ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
|||
tnc_host = arg.adress;
|
||||
connectTNC();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
var crc32=function(r){for(var a,o=[],c=0;c<256;c++){a=c;for(var f=0;f<8;f++)a=1&a?3988292384^a>>>1:a>>>1;o[c]=a}for(var n=-1,t=0;t<r.length;t++)n=n>>>8^o[255&(n^r.charCodeAt(t))];return(-1^n)>>>0};
|
||||
|
|
|
@ -768,6 +768,8 @@
|
|||
<div class="card text-dark mb-1">
|
||||
<div class="card-header p-1"><i class="bi bi-volume-up" style="font-size: 1rem; color: black;"></i> <strong>AUDIO LEVEL</strong>
|
||||
<button type="button" id="audioModalButton" data-bs-toggle="modal" data-bs-target="#audioModal" class="btn btn-sm btn-secondary">Tune</button>
|
||||
<button type="button" id="startStopRecording" class="btn btn-sm btn-danger">Rec</button>
|
||||
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="progress mb-0" style="height: 15px;">
|
||||
|
|
|
@ -296,6 +296,6 @@ def t_datac0_2(
|
|||
assert item in str(
|
||||
sock.SOCKET_QUEUE.queue
|
||||
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||
|
||||
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||
# TODO: Not sure why we need this for every test run
|
||||
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||
log.warning("station2: Exiting!")
|
||||
|
|
|
@ -305,6 +305,6 @@ def t_datac0_2(
|
|||
assert item not in str(
|
||||
sock.SOCKET_QUEUE.queue
|
||||
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
|
||||
|
||||
assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||
# TODO: Not sure why we need this for every test run
|
||||
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||
log.warning("station2: Exiting!")
|
||||
|
|
|
@ -7,6 +7,7 @@ Created on Sun Dec 27 20:43:40 2020
|
|||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||
|
||||
import os
|
||||
import base64
|
||||
import sys
|
||||
import threading
|
||||
|
@ -80,8 +81,8 @@ class DATA:
|
|||
# 3 bytes for the EOF End of File indicator in a data frame
|
||||
self.data_frame_eof = b"EOF"
|
||||
|
||||
self.tx_n_max_retries_per_burst = 50
|
||||
self.rx_n_max_retries_per_burst = 50
|
||||
self.tx_n_max_retries_per_burst = 40
|
||||
self.rx_n_max_retries_per_burst = 40
|
||||
self.n_retries_per_burst = 0
|
||||
|
||||
# Flag to indicate if we recevied a low bandwidth mode channel opener
|
||||
|
@ -590,7 +591,8 @@ class DATA:
|
|||
mycallsign = self.mycallsign
|
||||
|
||||
# only process data if we are in ARQ and BUSY state else return to quit
|
||||
if not static.ARQ_STATE and static.TNC_STATE != "BUSY":
|
||||
if not static.ARQ_STATE and static.TNC_STATE not in ["BUSY"]:
|
||||
self.log.warning("[TNC] wrong tnc state - dropping data", arq_state=static.ARQ_STATE, tnc_state=static.TNC_STATE)
|
||||
return
|
||||
|
||||
self.arq_file_transfer = True
|
||||
|
@ -810,7 +812,7 @@ class DATA:
|
|||
|
||||
# transmittion duration
|
||||
duration = time.time() - self.rx_start_of_transmission
|
||||
self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter,bytesperminute=static.ARQ_BYTES_PER_MINUTE, duration=duration
|
||||
self.log.info("[TNC] ARQ | RX | DATA FRAME SUCCESSFULLY RECEIVED", nacks=self.frame_nack_counter,bytesperminute=static.ARQ_BYTES_PER_MINUTE, total_bytes=static.TOTAL_BYTES, duration=duration
|
||||
)
|
||||
|
||||
# Decompress the data frame
|
||||
|
@ -864,13 +866,33 @@ class DATA:
|
|||
self.log.error(
|
||||
"[TNC] ARQ | RX | error occurred when saving data!",
|
||||
e=e,
|
||||
uuid = self.transmission_uuid,
|
||||
timestamp = timestamp,
|
||||
dxcall = static.DXCALLSIGN,
|
||||
dxgrid = static.DXGRID,
|
||||
data = base64_data
|
||||
uuid=self.transmission_uuid,
|
||||
timestamp=timestamp,
|
||||
dxcall=static.DXCALLSIGN,
|
||||
dxgrid=static.DXGRID,
|
||||
data=base64_data
|
||||
)
|
||||
|
||||
if static.ARQ_SAVE_TO_FOLDER:
|
||||
try:
|
||||
self.save_data_to_folder(
|
||||
self.transmission_uuid,
|
||||
timestamp,
|
||||
mycallsign,
|
||||
static.DXCALLSIGN,
|
||||
static.DXGRID,
|
||||
data_frame
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.error(
|
||||
"[TNC] ARQ | RX | can't save file to folder",
|
||||
e=e,
|
||||
uuid=self.transmission_uuid,
|
||||
timestamp=timestamp,
|
||||
dxcall=static.DXCALLSIGN,
|
||||
dxgrid=static.DXGRID,
|
||||
data=base64_data
|
||||
)
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
|
@ -1194,6 +1216,7 @@ class DATA:
|
|||
self.log.info(
|
||||
"[TNC] ARQ | TX | DATA TRANSMITTED!",
|
||||
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
|
||||
total_bytes=static.TOTAL_BYTES,
|
||||
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
|
||||
overflows=static.BUFFER_OVERFLOW_COUNTER,
|
||||
|
||||
|
@ -1512,7 +1535,7 @@ class DATA:
|
|||
+ "]>>?<<["
|
||||
+ str(self.dxcallsign, "UTF-8")
|
||||
+ "]",
|
||||
a=str(attempt + 1) + "/" + str(self.session_connect_max_retries), # Adjust for 0-based for user display
|
||||
a=f"{str(attempt + 1)}/{str(self.session_connect_max_retries)}",
|
||||
state=static.ARQ_SESSION_STATE,
|
||||
)
|
||||
|
||||
|
@ -1818,37 +1841,6 @@ class DATA:
|
|||
# for calculating transmission statistics
|
||||
# static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(lzma.compress(data_out))
|
||||
|
||||
# Let's check if we have a busy channel and if we are not in a running arq session.
|
||||
if static.CHANNEL_BUSY and not static.ARQ_SESSION:
|
||||
self.log.warning("[TNC] Channel busy, waiting until free...")
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="waiting",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
)
|
||||
|
||||
# wait while timeout not reached and our busy state is busy
|
||||
channel_busy_timeout = time.time() + 30
|
||||
while static.CHANNEL_BUSY and time.time() < channel_busy_timeout:
|
||||
threading.Event().wait(0.01)
|
||||
|
||||
# if channel busy timeout reached, stop connecting
|
||||
if time.time() > channel_busy_timeout:
|
||||
self.log.warning("[TNC] Channel busy, try again later...")
|
||||
static.ARQ_SESSION_STATE = "failed"
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="failed",
|
||||
reason="busy",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
)
|
||||
static.ARQ_SESSION_STATE = "disconnected"
|
||||
return False
|
||||
|
||||
self.arq_open_data_channel(mode, n_frames_per_burst, mycallsign)
|
||||
|
||||
# wait until data channel is open
|
||||
|
@ -1923,6 +1915,37 @@ class DATA:
|
|||
attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}",
|
||||
)
|
||||
|
||||
# Let's check if we have a busy channel and if we are not in a running arq session.
|
||||
if static.CHANNEL_BUSY and not static.ARQ_STATE:
|
||||
self.log.warning("[TNC] Channel busy, waiting until free...")
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="waiting",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
)
|
||||
|
||||
# wait while timeout not reached and our busy state is busy
|
||||
channel_busy_timeout = time.time() + 30
|
||||
while static.CHANNEL_BUSY and time.time() < channel_busy_timeout:
|
||||
threading.Event().wait(0.01)
|
||||
|
||||
# if channel busy timeout reached, stop connecting
|
||||
if time.time() > channel_busy_timeout:
|
||||
self.log.warning("[TNC] Channel busy, try again later...")
|
||||
static.ARQ_SESSION_STATE = "failed"
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="failed",
|
||||
reason="busy",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
)
|
||||
static.ARQ_SESSION_STATE = "disconnected"
|
||||
return False
|
||||
|
||||
self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
|
||||
|
||||
timeout = time.time() + 3
|
||||
|
@ -1931,6 +1954,8 @@ class DATA:
|
|||
# Stop waiting if data channel is opened
|
||||
if static.ARQ_STATE:
|
||||
return True
|
||||
if static.TNC_STATE in ["IDLE"]:
|
||||
return False
|
||||
|
||||
# `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up
|
||||
|
||||
|
@ -2214,6 +2239,7 @@ class DATA:
|
|||
received=protocol_version,
|
||||
own=static.ARQ_PROTOCOL_VERSION,
|
||||
)
|
||||
self.stop_transmission()
|
||||
self.arq_cleanup()
|
||||
|
||||
# ---------- PING
|
||||
|
@ -2382,6 +2408,18 @@ class DATA:
|
|||
Force a stop of the running transmission
|
||||
"""
|
||||
self.log.warning("[TNC] Stopping transmission!")
|
||||
|
||||
|
||||
static.TNC_STATE = "IDLE"
|
||||
static.ARQ_STATE = False
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="stopped",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
)
|
||||
|
||||
stop_frame = bytearray(self.length_sig0_frame)
|
||||
stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value])
|
||||
stop_frame[1:4] = static.DXCALLSIGN_CRC
|
||||
|
@ -2389,17 +2427,9 @@ class DATA:
|
|||
# TODO: Not sure if we really need the session id when disconnecting
|
||||
# stop_frame[1:2] = self.session_id
|
||||
stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
|
||||
self.enqueue_frame_for_tx([stop_frame], c2_mode=FREEDV_MODE.sig1.value, copies=6, repeat_delay=0)
|
||||
|
||||
static.TNC_STATE = "IDLE"
|
||||
static.ARQ_STATE = False
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
status="stopped",
|
||||
)
|
||||
self.arq_cleanup()
|
||||
|
||||
def received_stop_transmission(
|
||||
|
@ -2414,9 +2444,9 @@ class DATA:
|
|||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
arq="transmission",
|
||||
status="stopped",
|
||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||
status="stopped",
|
||||
uuid=self.transmission_uuid,
|
||||
)
|
||||
self.arq_cleanup()
|
||||
|
@ -2440,6 +2470,9 @@ class DATA:
|
|||
not static.ARQ_SESSION
|
||||
and not self.arq_file_transfer
|
||||
and not static.BEACON_PAUSE
|
||||
and not static.CHANNEL_BUSY
|
||||
and static.TNC_STATE not in ["busy"]
|
||||
and not static.ARQ_STATE
|
||||
):
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
|
@ -2454,7 +2487,7 @@ class DATA:
|
|||
beacon_frame = bytearray(self.length_sig0_frame)
|
||||
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
|
||||
beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
|
||||
beacon_frame[9:13] = static.MYGRID[:4]
|
||||
beacon_frame[7:13] = static.MYGRID
|
||||
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
|
||||
|
||||
if static.ENABLE_FSK:
|
||||
|
@ -2486,7 +2519,7 @@ class DATA:
|
|||
"""
|
||||
# here we add the received station to the heard stations buffer
|
||||
beacon_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
||||
dxgrid = bytes(data_in[9:13]).rstrip(b"\x00")
|
||||
dxgrid = bytes(data_in[7:13]).rstrip(b"\x00")
|
||||
|
||||
self.send_data_to_socket_queue(
|
||||
freedata="tnc-message",
|
||||
|
@ -2812,8 +2845,8 @@ class DATA:
|
|||
self.n_retries_per_burst = 0
|
||||
|
||||
# reset max retries possibly overriden by api
|
||||
self.session_connect_max_retries = 15
|
||||
self.data_channel_max_retries = 15
|
||||
self.session_connect_max_retries = 10
|
||||
self.data_channel_max_retries = 10
|
||||
|
||||
if not static.ARQ_SESSION:
|
||||
static.TNC_STATE = "IDLE"
|
||||
|
@ -2938,7 +2971,8 @@ class DATA:
|
|||
self.send_burst_nack_frame_watchdog(0)
|
||||
|
||||
# Update data_channel timestamp
|
||||
self.data_channel_last_received = time.time()
|
||||
# TODO: Disabled this one for testing.
|
||||
# self.data_channel_last_received = time.time()
|
||||
self.n_retries_per_burst += 1
|
||||
else:
|
||||
# print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time())
|
||||
|
@ -3040,3 +3074,81 @@ class DATA:
|
|||
self.enqueue_frame_for_tx(
|
||||
frame_to_tx=[bytearray(126)], c2_mode=FREEDV_MODE.datac3.value
|
||||
)
|
||||
|
||||
def save_data_to_folder(self,
|
||||
transmission_uuid,
|
||||
timestamp,
|
||||
mycallsign,
|
||||
dxcallsign,
|
||||
dxgrid,
|
||||
data_frame
|
||||
):
|
||||
|
||||
"""
|
||||
Save received data to folder
|
||||
Also supports chat messages
|
||||
"""
|
||||
|
||||
try:
|
||||
|
||||
self.log.info("[TNC] ARQ | RX | saving data to folder")
|
||||
|
||||
mycallsign = str(mycallsign, "UTF-8")
|
||||
dxcallsign = str(dxcallsign, "UTF-8")
|
||||
|
||||
folder_path = "received"
|
||||
if not os.path.exists(folder_path):
|
||||
os.makedirs(folder_path)
|
||||
|
||||
callsign_path = f"{mycallsign}_{dxcallsign}"
|
||||
if not os.path.exists(f"{folder_path}/{callsign_path}"):
|
||||
os.makedirs(f"{folder_path}/{callsign_path}")
|
||||
|
||||
split_char = b"\0;\1;"
|
||||
n_objects = 9
|
||||
decoded_data = data_frame.split(split_char)
|
||||
# if we have a false positive in case our split_char is available in data
|
||||
# lets stick the data together, so we are not loosing it
|
||||
if len(decoded_data) > n_objects:
|
||||
file_data = b''.join(decoded_data[n_objects:])
|
||||
|
||||
# slice is crashing nuitka
|
||||
# decoded_data = [*decoded_data[:n_objects], file_data]
|
||||
decoded_data = decoded_data[:n_objects] + [file_data]
|
||||
|
||||
if decoded_data[0] in [b'm']:
|
||||
checksum_delivered = str(decoded_data[2], "utf-8").lower()
|
||||
# transmission_uuid = decoded_data[3]
|
||||
message = decoded_data[5]
|
||||
filename = decoded_data[6]
|
||||
# filetype = decoded_data[7]
|
||||
# timestamp = decoded_data[4]
|
||||
data = decoded_data[8]
|
||||
else:
|
||||
message = b''
|
||||
filename = b''
|
||||
|
||||
# save file to folder
|
||||
if filename not in [b'', b'undefined']:
|
||||
# doing crc check
|
||||
crc = helpers.get_crc_32(data).hex().lower()
|
||||
validity = checksum_delivered == crc
|
||||
self.log.info(
|
||||
"[TNC] ARQ | RX | checking data crc",
|
||||
crc_delivered=checksum_delivered,
|
||||
crc_calculated=crc,
|
||||
valid=validity,
|
||||
)
|
||||
filename = str(filename, "UTF-8")
|
||||
filename_complex = f"{timestamp}_{transmission_uuid}_{filename}"
|
||||
with open(f"{folder_path}/{callsign_path}/{filename_complex}", "wb") as file:
|
||||
file.write(data)
|
||||
|
||||
if message not in [b'', b'undefined']:
|
||||
# save message to folder
|
||||
message_name = f"{timestamp}_{transmission_uuid}_msg.txt"
|
||||
with open(f"{folder_path}/{callsign_path}/{message_name}", "wb") as file:
|
||||
file.write(message)
|
||||
|
||||
except Exception as e:
|
||||
self.log.error("[TNC] error saving data to folder", e=e)
|
|
@ -32,11 +32,7 @@ class explorer():
|
|||
|
||||
def push(self):
|
||||
|
||||
|
||||
if static.HAMLIB_FREQUENCY is not None:
|
||||
frequency = static.HAMLIB_FREQUENCY
|
||||
else:
|
||||
frequency = 0
|
||||
frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY
|
||||
band = "USB"
|
||||
callsign = str(static.MYCALLSIGN, "utf-8")
|
||||
gridsquare = str(static.MYGRID, "utf-8")
|
||||
|
@ -47,7 +43,20 @@ class explorer():
|
|||
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon}
|
||||
station_data = {'callsign': callsign, 'gridsquare': gridsquare, 'frequency': frequency, 'band': band, 'version': version, 'bandwidth': bandwidth, 'beacon': beacon, "lastheard": []}
|
||||
|
||||
for i in static.HEARD_STATIONS:
|
||||
try:
|
||||
callsign = str(i[0], "UTF-8")
|
||||
grid = str(i[1], "UTF-8")
|
||||
try:
|
||||
snr = i[4].split("/")[1]
|
||||
except AttributeError:
|
||||
snr = str(i[4])
|
||||
station_data["lastheard"].append({"callsign": callsign, "grid": grid, "snr": snr})
|
||||
except Exception as e:
|
||||
log.debug("[EXPLORER] not publishing station", e=e)
|
||||
|
||||
station_data = json.dumps(station_data)
|
||||
try:
|
||||
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
||||
|
|
|
@ -331,6 +331,8 @@ def check_session_id(id: bytes, id_to_check: bytes):
|
|||
True
|
||||
False
|
||||
"""
|
||||
if id_to_check == b'\x00':
|
||||
return False
|
||||
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
||||
return id == id_to_check
|
||||
|
||||
|
@ -392,11 +394,7 @@ def decode_grid(b_code_word: bytes):
|
|||
|
||||
int_val = int(code_word & 0b111111111)
|
||||
int_first, int_sec = divmod(int_val, 18)
|
||||
# int_first = int_val // 18
|
||||
# int_sec = int_val % 18
|
||||
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||
|
||||
return grid
|
||||
return chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||
|
||||
|
||||
def encode_call(call):
|
||||
|
|
14
tnc/main.py
14
tnc/main.py
|
@ -70,6 +70,15 @@ if __name__ == "__main__":
|
|||
type=str,
|
||||
help="Use the default config file config.ini",
|
||||
)
|
||||
|
||||
PARSER.add_argument(
|
||||
"--save-to-folder",
|
||||
dest="savetofolder",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Save received data to local folder",
|
||||
)
|
||||
|
||||
PARSER.add_argument(
|
||||
"--mycall",
|
||||
dest="mycall",
|
||||
|
@ -269,8 +278,9 @@ if __name__ == "__main__":
|
|||
)
|
||||
ARGS = PARSER.parse_args()
|
||||
|
||||
|
||||
|
||||
# set save to folder state for allowing downloading files to local file system
|
||||
static.ARQ_SAVE_TO_FOLDER = ARGS.savetofolder
|
||||
|
||||
if not ARGS.configfile:
|
||||
|
||||
|
||||
|
|
118
tnc/modem.py
118
tnc/modem.py
|
@ -5,6 +5,7 @@ Created on Wed Dec 23 07:04:24 2020
|
|||
|
||||
@author: DJ2LS
|
||||
"""
|
||||
|
||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
|
@ -15,8 +16,9 @@ import sys
|
|||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
import wave
|
||||
import codec2
|
||||
import itertools
|
||||
import numpy as np
|
||||
import sock
|
||||
import sounddevice as sd
|
||||
|
@ -67,6 +69,7 @@ class RF:
|
|||
self.AUDIO_CHANNELS = 1
|
||||
self.MODE = 0
|
||||
|
||||
|
||||
# Locking state for mod out so buffer will be filled before we can use it
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/127
|
||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
||||
|
@ -355,30 +358,44 @@ class RF:
|
|||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||
x = self.resampler.resample48_to_8(x)
|
||||
|
||||
# audio recording for debugging purposes
|
||||
if static.AUDIO_RECORD:
|
||||
#static.AUDIO_RECORD_FILE.write(x)
|
||||
static.AUDIO_RECORD_FILE.writeframes(x)
|
||||
|
||||
# Avoid decoding when transmitting to reduce CPU
|
||||
if not static.TRANSMITTING:
|
||||
length_x = len(x)
|
||||
# TODO: Overriding this for testing purposes
|
||||
# if not static.TRANSMITTING:
|
||||
length_x = len(x)
|
||||
|
||||
# Avoid buffer overflow by filling only if buffer for
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0, 0),
|
||||
(self.sig1_datac0_buffer, RECEIVE_SIG1, 1),
|
||||
(self.dat0_datac1_buffer, RECEIVE_DATAC1, 2),
|
||||
(self.dat0_datac3_buffer, RECEIVE_DATAC3, 3),
|
||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
|
||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
|
||||
]:
|
||||
if audiobuffer.nbuffer + length_x > audiobuffer.size:
|
||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||
elif receive:
|
||||
audiobuffer.push(x)
|
||||
# Avoid buffer overflow by filling only if buffer for
|
||||
# selected datachannel mode is not full
|
||||
for audiobuffer, receive, index in [
|
||||
(self.sig0_datac0_buffer, RECEIVE_SIG0, 0),
|
||||
(self.sig1_datac0_buffer, RECEIVE_SIG1, 1),
|
||||
(self.dat0_datac1_buffer, RECEIVE_DATAC1, 2),
|
||||
(self.dat0_datac3_buffer, RECEIVE_DATAC3, 3),
|
||||
(self.fsk_ldpc_buffer_0, static.ENABLE_FSK, 4),
|
||||
(self.fsk_ldpc_buffer_1, static.ENABLE_FSK, 5),
|
||||
]:
|
||||
if audiobuffer.nbuffer + length_x > audiobuffer.size:
|
||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||
elif receive:
|
||||
audiobuffer.push(x)
|
||||
# end of "not static.TRANSMITTING" if block
|
||||
|
||||
if len(self.modoutqueue) <= 0 or self.mod_out_locked:
|
||||
# if not self.modoutqueue or self.mod_out_locked:
|
||||
if not self.modoutqueue or self.mod_out_locked:
|
||||
data_out48k = np.zeros(frames, dtype=np.int16)
|
||||
self.fft_data = x
|
||||
else:
|
||||
if not static.PTT_STATE:
|
||||
# TODO: Moved to this place for testing
|
||||
# Maybe we can avoid moments of silence before transmitting
|
||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
jsondata = {"ptt": "True"}
|
||||
data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(data_out)
|
||||
|
||||
data_out48k = self.modoutqueue.popleft()
|
||||
self.fft_data = data_out48k
|
||||
|
||||
|
@ -424,11 +441,12 @@ class RF:
|
|||
|
||||
static.TRANSMITTING = True
|
||||
start_of_transmission = time.time()
|
||||
# TODO: Moved ptt toggle some steps before audio is ready for testing
|
||||
# Toggle ptt early to save some time and send ptt state via socket
|
||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
jsondata = {"ptt": "True"}
|
||||
data_out = json.dumps(jsondata)
|
||||
sock.SOCKET_QUEUE.put(data_out)
|
||||
# static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||
# jsondata = {"ptt": "True"}
|
||||
# data_out = json.dumps(jsondata)
|
||||
# sock.SOCKET_QUEUE.put(data_out)
|
||||
|
||||
# Open codec2 instance
|
||||
self.MODE = mode
|
||||
|
@ -456,11 +474,12 @@ class RF:
|
|||
)
|
||||
|
||||
# Add empty data to handle ptt toggle time
|
||||
data_delay_mseconds = 0 # milliseconds
|
||||
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
txbuffer = bytes(mod_out_silence)
|
||||
|
||||
#data_delay_mseconds = 0 # milliseconds
|
||||
#data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||
#mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||
#txbuffer = bytes(mod_out_silence)
|
||||
# TODO: Disabled this one for testing
|
||||
txbuffer = bytes()
|
||||
self.log.debug(
|
||||
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
||||
)
|
||||
|
@ -832,13 +851,21 @@ class RF:
|
|||
)
|
||||
|
||||
scatterdata = []
|
||||
for i in range(codec2.MODEM_STATS_NC_MAX):
|
||||
for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
||||
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||
if xsymbols != 0.0 and ysymbols != 0.0:
|
||||
scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
|
||||
# original function before itertool
|
||||
#for i in range(codec2.MODEM_STATS_NC_MAX):
|
||||
# for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
||||
# # print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||
# xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||
# ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||
# if xsymbols != 0.0 and ysymbols != 0.0:
|
||||
# scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
|
||||
|
||||
for i, j in itertools.product(range(codec2.MODEM_STATS_NC_MAX), range(1, codec2.MODEM_STATS_NR_MAX, 2)):
|
||||
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||
if xsymbols != 0.0 and ysymbols != 0.0:
|
||||
scatterdata.append({"x": str(xsymbols), "y": str(ysymbols)})
|
||||
|
||||
# Send all the data if we have too-few samples, otherwise send a sampling
|
||||
if 150 > len(scatterdata) > 0:
|
||||
|
@ -890,12 +917,11 @@ class RF:
|
|||
- static.HAMLIB_BANDWIDTH
|
||||
"""
|
||||
while True:
|
||||
threading.Event().wait(0.5)
|
||||
threading.Event().wait(0.25)
|
||||
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
||||
static.HAMLIB_MODE = self.hamlib.get_mode()
|
||||
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
||||
static.HAMLIB_STATUS = self.hamlib.get_status()
|
||||
|
||||
def calculate_fft(self) -> None:
|
||||
"""
|
||||
Calculate an average signal strength of the channel to assess
|
||||
|
@ -945,8 +971,16 @@ class RF:
|
|||
# calculate RMS and then dBFS
|
||||
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
||||
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
|
||||
rms = int(np.sqrt(np.max(d ** 2)))
|
||||
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
||||
# try except for avoiding runtime errors by division/0
|
||||
try:
|
||||
rms = int(np.sqrt(np.max(d ** 2)))
|
||||
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
||||
except Exception as e:
|
||||
self.log.warning(
|
||||
"[MDM] fft calculation error - please check your audio setup",
|
||||
e=e,
|
||||
)
|
||||
static.AUDIO_DBFS = -100
|
||||
|
||||
rms_counter = 0
|
||||
|
||||
|
@ -968,11 +1002,7 @@ class RF:
|
|||
# 3200Hz = 315
|
||||
|
||||
# define the area, we are detecting busy state
|
||||
if static.LOW_BANDWIDTH_MODE:
|
||||
dfft = dfft[120:176]
|
||||
else:
|
||||
dfft = dfft[65:231]
|
||||
|
||||
dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231]
|
||||
|
||||
# Check for signals higher than average by checking for "100"
|
||||
# If we have a signal, increment our channel_busy delay counter
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#
|
||||
# modified and adjusted to FreeDATA needs by DJ2LS
|
||||
|
||||
import contextlib
|
||||
import socket
|
||||
import time
|
||||
import structlog
|
||||
|
@ -101,7 +102,7 @@ class radio:
|
|||
self.sock.close()
|
||||
self.connected = False
|
||||
|
||||
def send_command(self, command) -> bytes:
|
||||
def send_command(self, command, expect_answer) -> bytes:
|
||||
"""Send a command to the connected rotctld instance,
|
||||
and return the return value.
|
||||
|
||||
|
@ -122,7 +123,9 @@ class radio:
|
|||
self.connected = False
|
||||
|
||||
try:
|
||||
return self.connection.recv(1024)
|
||||
# recv seems to be blocking so in case of ptt we dont need the response
|
||||
# maybe this speeds things up and avoids blocking states
|
||||
return self.connection.recv(16) if expect_answer else True
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"[RIGCTLD] No command response!",
|
||||
|
@ -146,11 +149,14 @@ class radio:
|
|||
def get_mode(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = self.send_command(b"m", True)
|
||||
data = data.split(b"\n")
|
||||
data = data[0].decode("utf-8")
|
||||
if 'RPRT' not in data:
|
||||
self.mode = data
|
||||
try:
|
||||
data = int(data)
|
||||
except ValueError:
|
||||
self.mode = str(data)
|
||||
|
||||
return self.mode
|
||||
except Exception:
|
||||
|
@ -159,12 +165,13 @@ class radio:
|
|||
def get_bandwidth(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"m")
|
||||
data = self.send_command(b"m", True)
|
||||
data = data.split(b"\n")
|
||||
data = data[1].decode("utf-8")
|
||||
|
||||
if 'RPRT' not in data:
|
||||
self.bandwidth = int(data)
|
||||
if 'RPRT' not in data and data not in ['']:
|
||||
with contextlib.suppress(ValueError):
|
||||
self.bandwidth = int(data)
|
||||
return self.bandwidth
|
||||
except Exception:
|
||||
return self.bandwidth
|
||||
|
@ -172,11 +179,14 @@ class radio:
|
|||
def get_frequency(self):
|
||||
""" """
|
||||
try:
|
||||
data = self.send_command(b"f")
|
||||
data = self.send_command(b"f", True)
|
||||
data = data.decode("utf-8")
|
||||
if 'RPRT' not in data:
|
||||
self.frequency = data
|
||||
|
||||
if 'RPRT' not in data and data not in [0, '0', '']:
|
||||
with contextlib.suppress(ValueError):
|
||||
data = int(data)
|
||||
# make sure we have a frequency and not bandwidth
|
||||
if data >= 10000:
|
||||
self.frequency = data
|
||||
return self.frequency
|
||||
except Exception:
|
||||
return self.frequency
|
||||
|
@ -184,7 +194,7 @@ class radio:
|
|||
def get_ptt(self):
|
||||
""" """
|
||||
try:
|
||||
return self.send_command(b"t")
|
||||
return self.send_command(b"t", True)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
@ -199,9 +209,9 @@ class radio:
|
|||
"""
|
||||
try:
|
||||
if state:
|
||||
self.send_command(b"T 1")
|
||||
self.send_command(b"T 1", False)
|
||||
else:
|
||||
self.send_command(b"T 0")
|
||||
self.send_command(b"T 0", False)
|
||||
return state
|
||||
except Exception:
|
||||
return False
|
||||
|
|
25
tnc/sock.py
25
tnc/sock.py
|
@ -24,6 +24,7 @@ import socketserver
|
|||
import sys
|
||||
import threading
|
||||
import time
|
||||
import wave
|
||||
|
||||
import helpers
|
||||
import static
|
||||
|
@ -227,6 +228,28 @@ def process_tnc_commands(data):
|
|||
"[SCK] CQ command execution error", e=err, command=received_json
|
||||
)
|
||||
|
||||
# START STOP AUDIO RECORDING -----------------------------------------------------
|
||||
if received_json["type"] == "set" and received_json["command"] == "record_audio":
|
||||
try:
|
||||
if not static.AUDIO_RECORD:
|
||||
static.AUDIO_RECORD_FILE = wave.open(f"{int(time.time())}_audio_recording.wav", 'w')
|
||||
static.AUDIO_RECORD_FILE.setnchannels(1)
|
||||
static.AUDIO_RECORD_FILE.setsampwidth(2)
|
||||
static.AUDIO_RECORD_FILE.setframerate(8000)
|
||||
static.AUDIO_RECORD = True
|
||||
else:
|
||||
static.AUDIO_RECORD = False
|
||||
static.AUDIO_RECORD_FILE.close()
|
||||
|
||||
command_response("respond_to_call", True)
|
||||
|
||||
except Exception as err:
|
||||
command_response("respond_to_call", False)
|
||||
log.warning(
|
||||
"[SCK] CQ command execution error", e=err, command=received_json
|
||||
)
|
||||
|
||||
|
||||
# SET ENABLE/DISABLE RESPOND TO CALL -----------------------------------------------------
|
||||
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
|
||||
try:
|
||||
|
@ -612,6 +635,7 @@ def send_tnc_state():
|
|||
"dxgrid": str(static.DXGRID, encoding),
|
||||
"hamlib_status": static.HAMLIB_STATUS,
|
||||
"listen": str(static.LISTEN),
|
||||
"audio_recording": str(static.AUDIO_RECORD),
|
||||
}
|
||||
|
||||
# add heard stations to heard stations object
|
||||
|
@ -864,3 +888,4 @@ def command_response(command, status):
|
|||
jsondata = {"command_response": command, "status": s_status}
|
||||
data_out = json.dumps(jsondata)
|
||||
SOCKET_QUEUE.put(data_out)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Not nice, suggestions are appreciated :-)
|
|||
import subprocess
|
||||
from enum import Enum
|
||||
|
||||
VERSION = "0.6.8-alpha.1"
|
||||
VERSION = "0.6.9-alpha.1-exp"
|
||||
|
||||
ENABLE_EXPLORER = False
|
||||
|
||||
|
@ -82,6 +82,8 @@ AUDIO_INPUT_DEVICES: list = []
|
|||
AUDIO_OUTPUT_DEVICES: list = []
|
||||
AUDIO_INPUT_DEVICE: int = -2
|
||||
AUDIO_OUTPUT_DEVICE: int = -2
|
||||
AUDIO_RECORD: bool = False
|
||||
AUDIO_RECORD_FILE = ''
|
||||
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
||||
|
||||
AUDIO_DBFS: int = 0
|
||||
|
@ -90,7 +92,7 @@ ENABLE_FFT: bool = True
|
|||
CHANNEL_BUSY: bool = False
|
||||
|
||||
# ARQ PROTOCOL VERSION
|
||||
ARQ_PROTOCOL_VERSION: int = 4
|
||||
ARQ_PROTOCOL_VERSION: int = 5
|
||||
|
||||
# ARQ statistics
|
||||
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
||||
|
@ -101,6 +103,8 @@ ARQ_COMPRESSION_FACTOR: int = 0
|
|||
ARQ_TRANSMISSION_PERCENT: int = 0
|
||||
ARQ_SPEED_LEVEL: int = 0
|
||||
TOTAL_BYTES: int = 0
|
||||
# set save to folder state for allowing downloading files to local file system
|
||||
ARQ_SAVE_TO_FOLDER: bool = False
|
||||
|
||||
# CHANNEL_STATE = 'RECEIVING_SIGNALLING'
|
||||
TNC_STATE: str = "IDLE"
|
||||
|
|
|
@ -35,6 +35,10 @@ ip, port = args.socket_host, args.socket_port
|
|||
connected = True
|
||||
data = bytes()
|
||||
|
||||
"""
|
||||
Nachricht
|
||||
{'command': 'rx_buffer', 'data-array': [{'uuid': '8dde227d-3a09-4f39-b34c-5f8281d719d1', 'timestamp': 1672043316, 'dxcallsign': 'DJ2LS-1', 'dxgrid': 'JN48cs', 'data': 'bQA7c2VuZF9tZXNzYWdlADsxMjMAO2VkY2NjZDAyLTUzMTQtNDc3Ni1hMjlkLTFmY2M1ZDI4OTM4ZAA7VGVzdAoAOwA7cGxhaW4vdGV4dAA7ADsxNjcyMDQzMzA5'}]}
|
||||
"""
|
||||
|
||||
def decode_and_save_data(encoded_data):
|
||||
decoded_data = base64.b64decode(encoded_data)
|
||||
|
@ -69,36 +73,42 @@ def decode_and_save_data(encoded_data):
|
|||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.connect((ip, port))
|
||||
|
||||
print(sock)
|
||||
while connected:
|
||||
chunk = sock.recv(2)
|
||||
chunk = sock.recv(1024)
|
||||
data += chunk
|
||||
|
||||
if data.startswith(b'{') and data.endswith(b'}\n'):
|
||||
if data.startswith(b"{") and data.endswith(b"}\n"):
|
||||
# split data by \n if we have multiple commands in socket buffer
|
||||
data = data.split(b"\n")
|
||||
# remove empty data
|
||||
data.remove(b"")
|
||||
|
||||
jsondata = json.loads(data.split(b'\n')[0])
|
||||
data = bytes()
|
||||
# iterate through data list
|
||||
for command in data:
|
||||
|
||||
if jsondata.get('command') == "tnc_state":
|
||||
pass
|
||||
|
||||
if jsondata.get('freedata') == "tnc-message":
|
||||
log.info(jsondata)
|
||||
jsondata = json.loads(command)
|
||||
|
||||
if jsondata.get('ping') == "acknowledge":
|
||||
log.info(f"PING {jsondata.get('mycallsign')} >><< {jsondata.get('dxcallsign')}", snr=jsondata.get('snr'), dxsnr=jsondata.get('dxsnr'))
|
||||
if jsondata.get('command') == "tnc_state":
|
||||
pass
|
||||
|
||||
if jsondata.get('status') == 'receiving':
|
||||
log.info(jsondata)
|
||||
if jsondata.get('freedata') == "tnc-message":
|
||||
log.info(jsondata)
|
||||
|
||||
if jsondata.get('command') == 'rx_buffer':
|
||||
for rxdata in jsondata["data-array"]:
|
||||
log.info(f"rx buffer {rxdata.get('uuid')}")
|
||||
decode_and_save_data(rxdata.get('data'))
|
||||
if jsondata.get('ping') == "acknowledge":
|
||||
log.info(f"PING {jsondata.get('mycallsign')} >><< {jsondata.get('dxcallsign')}", snr=jsondata.get('snr'), dxsnr=jsondata.get('dxsnr'))
|
||||
|
||||
if jsondata.get('status') == 'received':
|
||||
decode_and_save_data(jsondata["data"])
|
||||
if jsondata.get('status') == 'receiving':
|
||||
log.info(jsondata)
|
||||
|
||||
if jsondata.get('command') == 'rx_buffer':
|
||||
for rxdata in jsondata["data-array"]:
|
||||
log.info(f"rx buffer {rxdata.get('uuid')}")
|
||||
decode_and_save_data(rxdata.get('data'))
|
||||
|
||||
if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission':
|
||||
decode_and_save_data(jsondata["data"])
|
||||
|
||||
# clear data buffer as soon as data has been read
|
||||
data = bytes()
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import base64
|
|||
import json
|
||||
import uuid
|
||||
import time
|
||||
import crcengine
|
||||
|
||||
# --------------------------------------------GET PARAMETER INPUTS
|
||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||
|
@ -47,26 +48,32 @@ else:
|
|||
|
||||
# convert binary data to base64
|
||||
#base64_data = base64.b64encode(file).decode("UTF-8")
|
||||
split_char = b'\0;'
|
||||
split_char = b'\0;\1;'
|
||||
|
||||
filetype = b"unknown"
|
||||
timestamp = str(int(time.time()))
|
||||
|
||||
# timestamp = timestamp.to_bytes(4, byteorder="big")
|
||||
timestamp = bytes(timestamp, "utf-8")
|
||||
msg_with_attachment = chatmessage + \
|
||||
msg_with_attachment = timestamp + \
|
||||
split_char + \
|
||||
chatmessage + \
|
||||
split_char + \
|
||||
filename + \
|
||||
split_char + \
|
||||
filetype + \
|
||||
split_char + \
|
||||
file + \
|
||||
split_char + \
|
||||
timestamp
|
||||
file
|
||||
|
||||
# calculate checksum
|
||||
crc_algorithm = crcengine.new("crc32") # load crc32 library
|
||||
crc_data = crc_algorithm(file)
|
||||
crc_data = crc_data.to_bytes(4, byteorder="big")
|
||||
|
||||
|
||||
datatype = b"m"
|
||||
command = b"send_message"
|
||||
checksum = b"123"
|
||||
checksum = bytes(crc_data.hex(), "utf-8")
|
||||
uuid_4 = bytes(str(uuid.uuid4()), "utf-8")
|
||||
|
||||
data = datatype + \
|
||||
|
@ -98,9 +105,13 @@ command = {"type": "arq",
|
|||
]
|
||||
}
|
||||
command = json.dumps(command)
|
||||
print(command)
|
||||
command = bytes(command + "\n", 'utf-8')
|
||||
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
# Connect to server and send data
|
||||
sock.connect((HOST, PORT))
|
||||
sock.sendall(command)
|
||||
timeout = time.time() + 5
|
||||
while time.time() < timeout:
|
||||
pass
|
Loading…
Reference in a new issue