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
18 changed files with 532 additions and 197 deletions
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: matrix.os == 'ubuntu-20.04'
|
||||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||||
run: |
|
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
|
- name: Install MacOS pyAudio
|
||||||
if: ${{startsWith(matrix.os, 'macos')}}
|
if: ${{startsWith(matrix.os, 'macos')}}
|
||||||
|
|
|
@ -38,10 +38,13 @@
|
||||||
"electron-updater": "^5.2.1",
|
"electron-updater": "^5.2.1",
|
||||||
"emoji-picker-element": "^1.12.1",
|
"emoji-picker-element": "^1.12.1",
|
||||||
"emoji-picker-element-data": "^1.3.0",
|
"emoji-picker-element-data": "^1.3.0",
|
||||||
|
"express-pouchdb": "^4.2.0",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"pouchdb": "^7.3.0",
|
"pouchdb": "^7.3.0",
|
||||||
"pouchdb-browser": "^7.3.0",
|
"pouchdb-browser": "^7.3.0",
|
||||||
|
"pouchdb-express-router": "^0.0.11",
|
||||||
"pouchdb-find": "^7.3.0",
|
"pouchdb-find": "^7.3.0",
|
||||||
|
"pouchdb-replication": "^8.0.0",
|
||||||
"qth-locator": "^2.1.0",
|
"qth-locator": "^2.1.0",
|
||||||
"utf8": "^3.0.0",
|
"utf8": "^3.0.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
|
|
@ -34,7 +34,7 @@ const dateFormatHours = new Intl.DateTimeFormat('en-GB', {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
});
|
});
|
||||||
// split character
|
// split character
|
||||||
const split_char = '\0;'
|
const split_char = '\0;\1;'
|
||||||
// global for our selected file we want to transmit
|
// global for our selected file we want to transmit
|
||||||
// ----------------- some chat globals
|
// ----------------- some chat globals
|
||||||
var filetype = '';
|
var filetype = '';
|
||||||
|
@ -61,25 +61,51 @@ try{
|
||||||
}
|
}
|
||||||
|
|
||||||
PouchDB.plugin(require('pouchdb-find'));
|
PouchDB.plugin(require('pouchdb-find'));
|
||||||
var db = new PouchDB(chatDB);
|
//PouchDB.plugin(require('pouchdb-replication'));
|
||||||
var remoteDB = new PouchDB('http://192.168.178.79:5984/chatDB')
|
|
||||||
|
|
||||||
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,
|
live: true,
|
||||||
retry: true
|
retry: false
|
||||||
}).on('change', function (change) {
|
}).on('change', function (change) {
|
||||||
// yo, something changed!
|
// yo, something changed!
|
||||||
console.log(change)
|
console.log(change)
|
||||||
}).on('paused', function (info) {
|
}).on('paused', function (err) {
|
||||||
// replication was paused, usually because of a lost connection
|
// replication was paused, usually because of a lost connection
|
||||||
console.log(info)
|
console.log(err)
|
||||||
}).on('active', function (info) {
|
}).on('active', function (info) {
|
||||||
// replication was resumed
|
// replication was resumed
|
||||||
console.log(info)
|
console.log(info)
|
||||||
}).on('error', function (err) {
|
}).on('error', function (err) {
|
||||||
// totally unhandled error (shouldn't happen)
|
// 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();
|
var dxcallsigns = new Set();
|
||||||
db.createIndex({
|
db.createIndex({
|
||||||
|
@ -297,7 +323,9 @@ db.post({
|
||||||
}
|
}
|
||||||
var timestamp = Math.floor(Date.now() / 1000);
|
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 = ``;
|
document.getElementById('selectFilesButton').innerHTML = ``;
|
||||||
var uuid = uuidv4();
|
var uuid = uuidv4();
|
||||||
|
@ -308,7 +336,7 @@ db.post({
|
||||||
mode: 255,
|
mode: 255,
|
||||||
frames: 1,
|
frames: 1,
|
||||||
data: data_with_attachment,
|
data: data_with_attachment,
|
||||||
checksum: '123',
|
checksum: file_checksum,
|
||||||
uuid: uuid
|
uuid: uuid
|
||||||
};
|
};
|
||||||
ipcRenderer.send('run-tnc-command', Data);
|
ipcRenderer.send('run-tnc-command', Data);
|
||||||
|
@ -318,7 +346,7 @@ db.post({
|
||||||
dxcallsign: dxcallsign,
|
dxcallsign: dxcallsign,
|
||||||
dxgrid: 'null',
|
dxgrid: 'null',
|
||||||
msg: chatmessage,
|
msg: chatmessage,
|
||||||
checksum: 'null',
|
checksum: file_checksum,
|
||||||
type: "transmit",
|
type: "transmit",
|
||||||
status: 'transmit',
|
status: 'transmit',
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
|
@ -404,7 +432,7 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
|
|
||||||
//handle ping
|
//handle ping
|
||||||
if (item.ping == 'received') {
|
if (item.ping == 'received') {
|
||||||
obj.timestamp = item.timestamp;
|
obj.timestamp = parseInt(item.timestamp);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.uuid = item.uuid;
|
obj.uuid = item.uuid;
|
||||||
|
@ -421,12 +449,9 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
add_obj_to_database(obj)
|
add_obj_to_database(obj)
|
||||||
update_chat_obj_by_uuid(obj.uuid);
|
update_chat_obj_by_uuid(obj.uuid);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// handle beacon
|
// handle beacon
|
||||||
} else if (item.beacon == 'received') {
|
} else if (item.beacon == 'received') {
|
||||||
obj.timestamp = item.timestamp;
|
obj.timestamp = parseInt(item.timestamp);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.uuid = item.uuid;
|
obj.uuid = item.uuid;
|
||||||
|
@ -451,20 +476,20 @@ ipcRenderer.on('action-new-msg-received', (event, arg) => {
|
||||||
|
|
||||||
console.log(splitted_data)
|
console.log(splitted_data)
|
||||||
|
|
||||||
obj.timestamp = splitted_data[8];
|
obj.timestamp = parseInt(splitted_data[4]);
|
||||||
obj.dxcallsign = item.dxcallsign;
|
obj.dxcallsign = item.dxcallsign;
|
||||||
obj.dxgrid = item.dxgrid;
|
obj.dxgrid = item.dxgrid;
|
||||||
obj.command = splitted_data[1];
|
obj.command = splitted_data[1];
|
||||||
obj.checksum = splitted_data[2];
|
obj.checksum = splitted_data[2];
|
||||||
// convert message to unicode from utf8 because of emojis
|
// convert message to unicode from utf8 because of emojis
|
||||||
obj.uuid = utf8.decode(splitted_data[3]);
|
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.status = 'null';
|
||||||
obj.snr = 'null';
|
obj.snr = 'null';
|
||||||
obj.type = 'received';
|
obj.type = 'received';
|
||||||
obj.filename = utf8.decode(splitted_data[5]);
|
obj.filename = utf8.decode(splitted_data[6]);
|
||||||
obj.filetype = utf8.decode(splitted_data[6]);
|
obj.filetype = utf8.decode(splitted_data[7]);
|
||||||
obj.file = btoa(utf8.decode(splitted_data[7]));
|
obj.file = btoa(splitted_data[8]);
|
||||||
|
|
||||||
add_obj_to_database(obj);
|
add_obj_to_database(obj);
|
||||||
update_chat_obj_by_uuid(obj.uuid);
|
update_chat_obj_by_uuid(obj.uuid);
|
||||||
|
@ -839,7 +864,9 @@ update_chat = function(obj) {
|
||||||
}).then(function(){
|
}).then(function(){
|
||||||
|
|
||||||
console.log(binaryString)
|
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 = {
|
let Data = {
|
||||||
command: "send_message",
|
command: "send_message",
|
||||||
dxcallsign: doc.dxcallsign,
|
dxcallsign: doc.dxcallsign,
|
||||||
|
@ -939,7 +966,7 @@ update_chat_obj_by_uuid = function(uuid) {
|
||||||
add_obj_to_database = function(obj){
|
add_obj_to_database = function(obj){
|
||||||
db.put({
|
db.put({
|
||||||
_id: obj.uuid,
|
_id: obj.uuid,
|
||||||
timestamp: obj.timestamp,
|
timestamp: parseInt(obj.timestamp),
|
||||||
uuid: obj.uuid,
|
uuid: obj.uuid,
|
||||||
dxcallsign: obj.dxcallsign,
|
dxcallsign: obj.dxcallsign,
|
||||||
dxgrid: obj.dxgrid,
|
dxgrid: obj.dxgrid,
|
||||||
|
@ -969,3 +996,36 @@ function scrollMessagesToBottom() {
|
||||||
var messageBody = document.getElementById('message-container');
|
var messageBody = document.getElementById('message-container');
|
||||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
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', () => {
|
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', () => {
|
document.getElementById('received_files_folder').addEventListener('click', () => {
|
||||||
|
|
||||||
ipcRenderer.send('get-folder-path',{
|
ipcRenderer.send('get-folder-path',{
|
||||||
|
@ -1441,6 +1454,22 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => {
|
||||||
document.getElementById("ptt_state").className = "btn btn-sm btn-secondary";
|
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
|
// CHANNEL BUSY STATE
|
||||||
if (arg.channel_busy == 'True') {
|
if (arg.channel_busy == 'True') {
|
||||||
document.getElementById("channel_busy").className = "btn btn-sm btn-danger";
|
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') {
|
if (arg.command == 'set_tx_audio_level') {
|
||||||
sock.setTxAudioLevel(arg.tx_audio_level);
|
sock.setTxAudioLevel(arg.tx_audio_level);
|
||||||
}
|
}
|
||||||
|
if (arg.command == 'record_audio') {
|
||||||
|
sock.record_audio();
|
||||||
|
}
|
||||||
if (arg.command == 'send_test_frame') {
|
if (arg.command == 'send_test_frame') {
|
||||||
sock.sendTestFrame();
|
sock.sendTestFrame();
|
||||||
}
|
}
|
||||||
|
|
37
gui/sock.js
37
gui/sock.js
|
@ -19,7 +19,7 @@ var client = new net.Socket();
|
||||||
var socketchunk = ''; // Current message, per connection.
|
var socketchunk = ''; // Current message, per connection.
|
||||||
|
|
||||||
// split character
|
// split character
|
||||||
const split_char = '\0;'
|
const split_char = '\0;\1;'
|
||||||
|
|
||||||
// globals for getting new data only if available so we are saving bandwidth
|
// globals for getting new data only if available so we are saving bandwidth
|
||||||
var rxBufferLengthTnc = 0
|
var rxBufferLengthTnc = 0
|
||||||
|
@ -227,6 +227,8 @@ client.on('data', function(socketdata) {
|
||||||
stations: data['stations'],
|
stations: data['stations'],
|
||||||
beacon_state: data['beacon_state'],
|
beacon_state: data['beacon_state'],
|
||||||
hamlib_status: data['hamlib_status'],
|
hamlib_status: data['hamlib_status'],
|
||||||
|
listen: data['listen'],
|
||||||
|
audio_recording: data['audio_recording'],
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcRenderer.send('request-update-tnc-state', Data);
|
ipcRenderer.send('request-update-tnc-state', Data);
|
||||||
|
@ -515,20 +517,25 @@ exports.sendFile = function(dxcallsign, mode, frames, filename, filetype, data,
|
||||||
|
|
||||||
// Send Message
|
// Send Message
|
||||||
exports.sendMessage = function(dxcallsign, mode, frames, data, checksum, uuid, command) {
|
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
|
// convert message to plain utf8 because of unicode emojis
|
||||||
data = utf8.encode(data)
|
//data = utf8.encode(data)
|
||||||
socketLog.info(data)
|
|
||||||
|
//socketLog.info(data)
|
||||||
|
|
||||||
|
|
||||||
var datatype = "m"
|
var datatype = "m"
|
||||||
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
data = datatype + split_char + command + split_char + checksum + split_char + uuid + split_char + data
|
||||||
socketLog.info(data)
|
//socketLog.info(data)
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
console.log("CHECKSUM" + checksum)
|
||||||
|
//socketLog.info(btoa(data))
|
||||||
socketLog.info(btoa(data))
|
|
||||||
data = 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_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"}]}'
|
command = '{"type" : "arq", "command" : "send_raw", "uuid" : "'+ uuid +'", "parameter" : [{"dxcallsign" : "' + dxcallsign + '", "mode" : "' + mode + '", "n_frames" : "' + frames + '", "data" : "' + data + '", "attempts": "15"}]}'
|
||||||
socketLog.info(command)
|
socketLog.info(command)
|
||||||
|
@ -584,7 +591,11 @@ exports.sendTestFrame = function() {
|
||||||
writeTncCommand(command)
|
writeTncCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RECORD AUDIO
|
||||||
|
exports.record_audio = function() {
|
||||||
|
command = '{"type" : "set", "command" : "record_audio"}'
|
||||||
|
writeTncCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
|
@ -603,3 +614,11 @@ ipcRenderer.on('action-update-tnc-ip', (event, arg) => {
|
||||||
connectTNC();
|
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 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>
|
<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="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>
|
||||||
<div class="card-body p-2">
|
<div class="card-body p-2">
|
||||||
<div class="progress mb-0" style="height: 15px;">
|
<div class="progress mb-0" style="height: 15px;">
|
||||||
|
|
|
@ -296,6 +296,6 @@ def t_datac0_2(
|
||||||
assert item in str(
|
assert item in str(
|
||||||
sock.SOCKET_QUEUE.queue
|
sock.SOCKET_QUEUE.queue
|
||||||
), f"{item} not found in {str(sock.SOCKET_QUEUE.queue)}"
|
), f"{item} not found 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)
|
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||||
log.warning("station2: Exiting!")
|
log.warning("station2: Exiting!")
|
||||||
|
|
|
@ -305,6 +305,6 @@ def t_datac0_2(
|
||||||
assert item not in str(
|
assert item not in str(
|
||||||
sock.SOCKET_QUEUE.queue
|
sock.SOCKET_QUEUE.queue
|
||||||
), f"{item} found in {str(sock.SOCKET_QUEUE.queue)}"
|
), f"{item} found 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)
|
# assert '"arq":"session","status":"close"' in str(sock.SOCKET_QUEUE.queue)
|
||||||
log.warning("station2: Exiting!")
|
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=invalid-name, line-too-long, c-extension-no-member
|
||||||
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||||
|
|
||||||
|
import os
|
||||||
import base64
|
import base64
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
@ -80,8 +81,8 @@ class DATA:
|
||||||
# 3 bytes for the EOF End of File indicator in a data frame
|
# 3 bytes for the EOF End of File indicator in a data frame
|
||||||
self.data_frame_eof = b"EOF"
|
self.data_frame_eof = b"EOF"
|
||||||
|
|
||||||
self.tx_n_max_retries_per_burst = 50
|
self.tx_n_max_retries_per_burst = 40
|
||||||
self.rx_n_max_retries_per_burst = 50
|
self.rx_n_max_retries_per_burst = 40
|
||||||
self.n_retries_per_burst = 0
|
self.n_retries_per_burst = 0
|
||||||
|
|
||||||
# Flag to indicate if we recevied a low bandwidth mode channel opener
|
# Flag to indicate if we recevied a low bandwidth mode channel opener
|
||||||
|
@ -590,7 +591,8 @@ class DATA:
|
||||||
mycallsign = self.mycallsign
|
mycallsign = self.mycallsign
|
||||||
|
|
||||||
# only process data if we are in ARQ and BUSY state else return to quit
|
# 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
|
return
|
||||||
|
|
||||||
self.arq_file_transfer = True
|
self.arq_file_transfer = True
|
||||||
|
@ -810,7 +812,7 @@ class DATA:
|
||||||
|
|
||||||
# transmittion duration
|
# transmittion duration
|
||||||
duration = time.time() - self.rx_start_of_transmission
|
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
|
# Decompress the data frame
|
||||||
|
@ -871,6 +873,26 @@ class DATA:
|
||||||
data=base64_data
|
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(
|
self.send_data_to_socket_queue(
|
||||||
freedata="tnc-message",
|
freedata="tnc-message",
|
||||||
arq="transmission",
|
arq="transmission",
|
||||||
|
@ -1194,6 +1216,7 @@ class DATA:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"[TNC] ARQ | TX | DATA TRANSMITTED!",
|
"[TNC] ARQ | TX | DATA TRANSMITTED!",
|
||||||
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
|
BytesPerMinute=static.ARQ_BYTES_PER_MINUTE,
|
||||||
|
total_bytes=static.TOTAL_BYTES,
|
||||||
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
|
BitsPerSecond=static.ARQ_BITS_PER_SECOND,
|
||||||
overflows=static.BUFFER_OVERFLOW_COUNTER,
|
overflows=static.BUFFER_OVERFLOW_COUNTER,
|
||||||
|
|
||||||
|
@ -1512,7 +1535,7 @@ class DATA:
|
||||||
+ "]>>?<<["
|
+ "]>>?<<["
|
||||||
+ str(self.dxcallsign, "UTF-8")
|
+ 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,
|
state=static.ARQ_SESSION_STATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1818,37 +1841,6 @@ class DATA:
|
||||||
# for calculating transmission statistics
|
# for calculating transmission statistics
|
||||||
# static.ARQ_COMPRESSION_FACTOR = len(data_out) / len(lzma.compress(data_out))
|
# 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)
|
self.arq_open_data_channel(mode, n_frames_per_burst, mycallsign)
|
||||||
|
|
||||||
# wait until data channel is open
|
# wait until data channel is open
|
||||||
|
@ -1923,6 +1915,37 @@ class DATA:
|
||||||
attempt=f"{str(attempt + 1)}/{str(self.data_channel_max_retries)}",
|
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)
|
self.enqueue_frame_for_tx([connection_frame], c2_mode=FREEDV_MODE.datac0.value, copies=1, repeat_delay=0)
|
||||||
|
|
||||||
timeout = time.time() + 3
|
timeout = time.time() + 3
|
||||||
|
@ -1931,6 +1954,8 @@ class DATA:
|
||||||
# Stop waiting if data channel is opened
|
# Stop waiting if data channel is opened
|
||||||
if static.ARQ_STATE:
|
if static.ARQ_STATE:
|
||||||
return True
|
return True
|
||||||
|
if static.TNC_STATE in ["IDLE"]:
|
||||||
|
return False
|
||||||
|
|
||||||
# `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up
|
# `data_channel_max_retries` attempts have been sent. Aborting attempt & cleaning up
|
||||||
|
|
||||||
|
@ -2214,6 +2239,7 @@ class DATA:
|
||||||
received=protocol_version,
|
received=protocol_version,
|
||||||
own=static.ARQ_PROTOCOL_VERSION,
|
own=static.ARQ_PROTOCOL_VERSION,
|
||||||
)
|
)
|
||||||
|
self.stop_transmission()
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
|
||||||
# ---------- PING
|
# ---------- PING
|
||||||
|
@ -2382,6 +2408,18 @@ class DATA:
|
||||||
Force a stop of the running transmission
|
Force a stop of the running transmission
|
||||||
"""
|
"""
|
||||||
self.log.warning("[TNC] Stopping 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 = bytearray(self.length_sig0_frame)
|
||||||
stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value])
|
stop_frame[:1] = bytes([FR_TYPE.ARQ_STOP.value])
|
||||||
stop_frame[1:4] = static.DXCALLSIGN_CRC
|
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
|
# TODO: Not sure if we really need the session id when disconnecting
|
||||||
# stop_frame[1:2] = self.session_id
|
# stop_frame[1:2] = self.session_id
|
||||||
stop_frame[7:13] = helpers.callsign_to_bytes(self.mycallsign)
|
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)
|
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()
|
self.arq_cleanup()
|
||||||
|
|
||||||
def received_stop_transmission(
|
def received_stop_transmission(
|
||||||
|
@ -2414,9 +2444,9 @@ class DATA:
|
||||||
self.send_data_to_socket_queue(
|
self.send_data_to_socket_queue(
|
||||||
freedata="tnc-message",
|
freedata="tnc-message",
|
||||||
arq="transmission",
|
arq="transmission",
|
||||||
|
status="stopped",
|
||||||
mycallsign=str(self.mycallsign, 'UTF-8'),
|
mycallsign=str(self.mycallsign, 'UTF-8'),
|
||||||
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
dxcallsign=str(self.dxcallsign, 'UTF-8'),
|
||||||
status="stopped",
|
|
||||||
uuid=self.transmission_uuid,
|
uuid=self.transmission_uuid,
|
||||||
)
|
)
|
||||||
self.arq_cleanup()
|
self.arq_cleanup()
|
||||||
|
@ -2440,6 +2470,9 @@ class DATA:
|
||||||
not static.ARQ_SESSION
|
not static.ARQ_SESSION
|
||||||
and not self.arq_file_transfer
|
and not self.arq_file_transfer
|
||||||
and not static.BEACON_PAUSE
|
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(
|
self.send_data_to_socket_queue(
|
||||||
freedata="tnc-message",
|
freedata="tnc-message",
|
||||||
|
@ -2454,7 +2487,7 @@ class DATA:
|
||||||
beacon_frame = bytearray(self.length_sig0_frame)
|
beacon_frame = bytearray(self.length_sig0_frame)
|
||||||
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
|
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
|
||||||
beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
|
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)
|
self.log.info("[TNC] ENABLE FSK", state=static.ENABLE_FSK)
|
||||||
|
|
||||||
if static.ENABLE_FSK:
|
if static.ENABLE_FSK:
|
||||||
|
@ -2486,7 +2519,7 @@ class DATA:
|
||||||
"""
|
"""
|
||||||
# here we add the received station to the heard stations buffer
|
# here we add the received station to the heard stations buffer
|
||||||
beacon_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
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(
|
self.send_data_to_socket_queue(
|
||||||
freedata="tnc-message",
|
freedata="tnc-message",
|
||||||
|
@ -2812,8 +2845,8 @@ class DATA:
|
||||||
self.n_retries_per_burst = 0
|
self.n_retries_per_burst = 0
|
||||||
|
|
||||||
# reset max retries possibly overriden by api
|
# reset max retries possibly overriden by api
|
||||||
self.session_connect_max_retries = 15
|
self.session_connect_max_retries = 10
|
||||||
self.data_channel_max_retries = 15
|
self.data_channel_max_retries = 10
|
||||||
|
|
||||||
if not static.ARQ_SESSION:
|
if not static.ARQ_SESSION:
|
||||||
static.TNC_STATE = "IDLE"
|
static.TNC_STATE = "IDLE"
|
||||||
|
@ -2938,7 +2971,8 @@ class DATA:
|
||||||
self.send_burst_nack_frame_watchdog(0)
|
self.send_burst_nack_frame_watchdog(0)
|
||||||
|
|
||||||
# Update data_channel timestamp
|
# 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
|
self.n_retries_per_burst += 1
|
||||||
else:
|
else:
|
||||||
# print((self.data_channel_last_received + self.time_list[self.speed_level])-time.time())
|
# 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(
|
self.enqueue_frame_for_tx(
|
||||||
frame_to_tx=[bytearray(126)], c2_mode=FREEDV_MODE.datac3.value
|
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):
|
def push(self):
|
||||||
|
|
||||||
|
frequency = 0 if static.HAMLIB_FREQUENCY is None else static.HAMLIB_FREQUENCY
|
||||||
if static.HAMLIB_FREQUENCY is not None:
|
|
||||||
frequency = static.HAMLIB_FREQUENCY
|
|
||||||
else:
|
|
||||||
frequency = 0
|
|
||||||
band = "USB"
|
band = "USB"
|
||||||
callsign = str(static.MYCALLSIGN, "utf-8")
|
callsign = str(static.MYCALLSIGN, "utf-8")
|
||||||
gridsquare = str(static.MYGRID, "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)
|
log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth)
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
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)
|
station_data = json.dumps(station_data)
|
||||||
try:
|
try:
|
||||||
response = requests.post(self.explorer_url, json=station_data, headers=headers)
|
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
|
True
|
||||||
False
|
False
|
||||||
"""
|
"""
|
||||||
|
if id_to_check == b'\x00':
|
||||||
|
return False
|
||||||
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
log.debug("[HLP] check_sessionid: Checking:", ownid=id, check=id_to_check)
|
||||||
return id == 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_val = int(code_word & 0b111111111)
|
||||||
int_first, int_sec = divmod(int_val, 18)
|
int_first, int_sec = divmod(int_val, 18)
|
||||||
# int_first = int_val // 18
|
return chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
||||||
# int_sec = int_val % 18
|
|
||||||
grid = chr(int(int_first) + 65) + chr(int(int_sec) + 65) + grid
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
|
|
||||||
def encode_call(call):
|
def encode_call(call):
|
||||||
|
|
12
tnc/main.py
12
tnc/main.py
|
@ -70,6 +70,15 @@ if __name__ == "__main__":
|
||||||
type=str,
|
type=str,
|
||||||
help="Use the default config file config.ini",
|
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(
|
PARSER.add_argument(
|
||||||
"--mycall",
|
"--mycall",
|
||||||
dest="mycall",
|
dest="mycall",
|
||||||
|
@ -269,7 +278,8 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
ARGS = PARSER.parse_args()
|
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:
|
if not ARGS.configfile:
|
||||||
|
|
||||||
|
|
74
tnc/modem.py
74
tnc/modem.py
|
@ -5,6 +5,7 @@ Created on Wed Dec 23 07:04:24 2020
|
||||||
|
|
||||||
@author: DJ2LS
|
@author: DJ2LS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
@ -15,8 +16,9 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import wave
|
||||||
import codec2
|
import codec2
|
||||||
|
import itertools
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sock
|
import sock
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
@ -67,6 +69,7 @@ class RF:
|
||||||
self.AUDIO_CHANNELS = 1
|
self.AUDIO_CHANNELS = 1
|
||||||
self.MODE = 0
|
self.MODE = 0
|
||||||
|
|
||||||
|
|
||||||
# Locking state for mod out so buffer will be filled before we can use it
|
# 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/127
|
||||||
# https://github.com/DJ2LS/FreeDATA/issues/99
|
# https://github.com/DJ2LS/FreeDATA/issues/99
|
||||||
|
@ -355,8 +358,14 @@ class RF:
|
||||||
x = np.frombuffer(data_in48k, dtype=np.int16)
|
x = np.frombuffer(data_in48k, dtype=np.int16)
|
||||||
x = self.resampler.resample48_to_8(x)
|
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
|
# Avoid decoding when transmitting to reduce CPU
|
||||||
if not static.TRANSMITTING:
|
# TODO: Overriding this for testing purposes
|
||||||
|
# if not static.TRANSMITTING:
|
||||||
length_x = len(x)
|
length_x = len(x)
|
||||||
|
|
||||||
# Avoid buffer overflow by filling only if buffer for
|
# Avoid buffer overflow by filling only if buffer for
|
||||||
|
@ -373,12 +382,20 @@ class RF:
|
||||||
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
static.BUFFER_OVERFLOW_COUNTER[index] += 1
|
||||||
elif receive:
|
elif receive:
|
||||||
audiobuffer.push(x)
|
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)
|
data_out48k = np.zeros(frames, dtype=np.int16)
|
||||||
self.fft_data = x
|
self.fft_data = x
|
||||||
else:
|
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()
|
data_out48k = self.modoutqueue.popleft()
|
||||||
self.fft_data = data_out48k
|
self.fft_data = data_out48k
|
||||||
|
|
||||||
|
@ -424,11 +441,12 @@ class RF:
|
||||||
|
|
||||||
static.TRANSMITTING = True
|
static.TRANSMITTING = True
|
||||||
start_of_transmission = time.time()
|
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
|
# Toggle ptt early to save some time and send ptt state via socket
|
||||||
static.PTT_STATE = self.hamlib.set_ptt(True)
|
# static.PTT_STATE = self.hamlib.set_ptt(True)
|
||||||
jsondata = {"ptt": "True"}
|
# jsondata = {"ptt": "True"}
|
||||||
data_out = json.dumps(jsondata)
|
# data_out = json.dumps(jsondata)
|
||||||
sock.SOCKET_QUEUE.put(data_out)
|
# sock.SOCKET_QUEUE.put(data_out)
|
||||||
|
|
||||||
# Open codec2 instance
|
# Open codec2 instance
|
||||||
self.MODE = mode
|
self.MODE = mode
|
||||||
|
@ -456,11 +474,12 @@ class RF:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add empty data to handle ptt toggle time
|
# Add empty data to handle ptt toggle time
|
||||||
data_delay_mseconds = 0 # milliseconds
|
#data_delay_mseconds = 0 # milliseconds
|
||||||
data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
#data_delay = int(self.MODEM_SAMPLE_RATE * (data_delay_mseconds / 1000)) # type: ignore
|
||||||
mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
#mod_out_silence = ctypes.create_string_buffer(data_delay * 2)
|
||||||
txbuffer = bytes(mod_out_silence)
|
#txbuffer = bytes(mod_out_silence)
|
||||||
|
# TODO: Disabled this one for testing
|
||||||
|
txbuffer = bytes()
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
"[MDM] TRANSMIT", mode=self.MODE, payload=payload_bytes_per_frame
|
||||||
)
|
)
|
||||||
|
@ -832,8 +851,16 @@ class RF:
|
||||||
)
|
)
|
||||||
|
|
||||||
scatterdata = []
|
scatterdata = []
|
||||||
for i in range(codec2.MODEM_STATS_NC_MAX):
|
# original function before itertool
|
||||||
for j in range(1, codec2.MODEM_STATS_NR_MAX, 2):
|
#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]}")
|
# print(f"{modemStats.rx_symbols[i][j]} - {modemStats.rx_symbols[i][j]}")
|
||||||
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
xsymbols = round(modemStats.rx_symbols[i][j - 1] // 1000)
|
||||||
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
ysymbols = round(modemStats.rx_symbols[i][j] // 1000)
|
||||||
|
@ -890,12 +917,11 @@ class RF:
|
||||||
- static.HAMLIB_BANDWIDTH
|
- static.HAMLIB_BANDWIDTH
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
threading.Event().wait(0.5)
|
threading.Event().wait(0.25)
|
||||||
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
static.HAMLIB_FREQUENCY = self.hamlib.get_frequency()
|
||||||
static.HAMLIB_MODE = self.hamlib.get_mode()
|
static.HAMLIB_MODE = self.hamlib.get_mode()
|
||||||
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
static.HAMLIB_BANDWIDTH = self.hamlib.get_bandwidth()
|
||||||
static.HAMLIB_STATUS = self.hamlib.get_status()
|
static.HAMLIB_STATUS = self.hamlib.get_status()
|
||||||
|
|
||||||
def calculate_fft(self) -> None:
|
def calculate_fft(self) -> None:
|
||||||
"""
|
"""
|
||||||
Calculate an average signal strength of the channel to assess
|
Calculate an average signal strength of the channel to assess
|
||||||
|
@ -945,8 +971,16 @@ class RF:
|
||||||
# calculate RMS and then dBFS
|
# calculate RMS and then dBFS
|
||||||
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
# TODO: Need to change static.AUDIO_RMS to AUDIO_DBFS somewhen
|
||||||
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
|
# https://dsp.stackexchange.com/questions/8785/how-to-compute-dbfs
|
||||||
|
# try except for avoiding runtime errors by division/0
|
||||||
|
try:
|
||||||
rms = int(np.sqrt(np.max(d ** 2)))
|
rms = int(np.sqrt(np.max(d ** 2)))
|
||||||
static.AUDIO_DBFS = 20 * np.log10(rms / 32768)
|
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
|
rms_counter = 0
|
||||||
|
|
||||||
|
@ -968,11 +1002,7 @@ class RF:
|
||||||
# 3200Hz = 315
|
# 3200Hz = 315
|
||||||
|
|
||||||
# define the area, we are detecting busy state
|
# define the area, we are detecting busy state
|
||||||
if static.LOW_BANDWIDTH_MODE:
|
dfft = dfft[120:176] if static.LOW_BANDWIDTH_MODE else dfft[65:231]
|
||||||
dfft = dfft[120:176]
|
|
||||||
else:
|
|
||||||
dfft = dfft[65:231]
|
|
||||||
|
|
||||||
|
|
||||||
# Check for signals higher than average by checking for "100"
|
# Check for signals higher than average by checking for "100"
|
||||||
# If we have a signal, increment our channel_busy delay counter
|
# If we have a signal, increment our channel_busy delay counter
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#
|
#
|
||||||
# modified and adjusted to FreeDATA needs by DJ2LS
|
# modified and adjusted to FreeDATA needs by DJ2LS
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import structlog
|
import structlog
|
||||||
|
@ -101,7 +102,7 @@ class radio:
|
||||||
self.sock.close()
|
self.sock.close()
|
||||||
self.connected = False
|
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,
|
"""Send a command to the connected rotctld instance,
|
||||||
and return the return value.
|
and return the return value.
|
||||||
|
|
||||||
|
@ -122,7 +123,9 @@ class radio:
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
try:
|
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:
|
except Exception:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"[RIGCTLD] No command response!",
|
"[RIGCTLD] No command response!",
|
||||||
|
@ -146,11 +149,14 @@ class radio:
|
||||||
def get_mode(self):
|
def get_mode(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"m")
|
data = self.send_command(b"m", True)
|
||||||
data = data.split(b"\n")
|
data = data.split(b"\n")
|
||||||
data = data[0].decode("utf-8")
|
data = data[0].decode("utf-8")
|
||||||
if 'RPRT' not in data:
|
if 'RPRT' not in data:
|
||||||
self.mode = data
|
try:
|
||||||
|
data = int(data)
|
||||||
|
except ValueError:
|
||||||
|
self.mode = str(data)
|
||||||
|
|
||||||
return self.mode
|
return self.mode
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -159,11 +165,12 @@ class radio:
|
||||||
def get_bandwidth(self):
|
def get_bandwidth(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"m")
|
data = self.send_command(b"m", True)
|
||||||
data = data.split(b"\n")
|
data = data.split(b"\n")
|
||||||
data = data[1].decode("utf-8")
|
data = data[1].decode("utf-8")
|
||||||
|
|
||||||
if 'RPRT' not in data:
|
if 'RPRT' not in data and data not in ['']:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
self.bandwidth = int(data)
|
self.bandwidth = int(data)
|
||||||
return self.bandwidth
|
return self.bandwidth
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -172,11 +179,14 @@ class radio:
|
||||||
def get_frequency(self):
|
def get_frequency(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
data = self.send_command(b"f")
|
data = self.send_command(b"f", True)
|
||||||
data = data.decode("utf-8")
|
data = data.decode("utf-8")
|
||||||
if 'RPRT' not in 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
|
self.frequency = data
|
||||||
|
|
||||||
return self.frequency
|
return self.frequency
|
||||||
except Exception:
|
except Exception:
|
||||||
return self.frequency
|
return self.frequency
|
||||||
|
@ -184,7 +194,7 @@ class radio:
|
||||||
def get_ptt(self):
|
def get_ptt(self):
|
||||||
""" """
|
""" """
|
||||||
try:
|
try:
|
||||||
return self.send_command(b"t")
|
return self.send_command(b"t", True)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -199,9 +209,9 @@ class radio:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if state:
|
if state:
|
||||||
self.send_command(b"T 1")
|
self.send_command(b"T 1", False)
|
||||||
else:
|
else:
|
||||||
self.send_command(b"T 0")
|
self.send_command(b"T 0", False)
|
||||||
return state
|
return state
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
25
tnc/sock.py
25
tnc/sock.py
|
@ -24,6 +24,7 @@ import socketserver
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import wave
|
||||||
|
|
||||||
import helpers
|
import helpers
|
||||||
import static
|
import static
|
||||||
|
@ -227,6 +228,28 @@ def process_tnc_commands(data):
|
||||||
"[SCK] CQ command execution error", e=err, command=received_json
|
"[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 -----------------------------------------------------
|
# SET ENABLE/DISABLE RESPOND TO CALL -----------------------------------------------------
|
||||||
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
|
if received_json["type"] == "set" and received_json["command"] == "respond_to_call":
|
||||||
try:
|
try:
|
||||||
|
@ -612,6 +635,7 @@ def send_tnc_state():
|
||||||
"dxgrid": str(static.DXGRID, encoding),
|
"dxgrid": str(static.DXGRID, encoding),
|
||||||
"hamlib_status": static.HAMLIB_STATUS,
|
"hamlib_status": static.HAMLIB_STATUS,
|
||||||
"listen": str(static.LISTEN),
|
"listen": str(static.LISTEN),
|
||||||
|
"audio_recording": str(static.AUDIO_RECORD),
|
||||||
}
|
}
|
||||||
|
|
||||||
# add heard stations to heard stations object
|
# add heard stations to heard stations object
|
||||||
|
@ -864,3 +888,4 @@ def command_response(command, status):
|
||||||
jsondata = {"command_response": command, "status": s_status}
|
jsondata = {"command_response": command, "status": s_status}
|
||||||
data_out = json.dumps(jsondata)
|
data_out = json.dumps(jsondata)
|
||||||
SOCKET_QUEUE.put(data_out)
|
SOCKET_QUEUE.put(data_out)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ Not nice, suggestions are appreciated :-)
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
VERSION = "0.6.8-alpha.1"
|
VERSION = "0.6.9-alpha.1-exp"
|
||||||
|
|
||||||
ENABLE_EXPLORER = False
|
ENABLE_EXPLORER = False
|
||||||
|
|
||||||
|
@ -82,6 +82,8 @@ AUDIO_INPUT_DEVICES: list = []
|
||||||
AUDIO_OUTPUT_DEVICES: list = []
|
AUDIO_OUTPUT_DEVICES: list = []
|
||||||
AUDIO_INPUT_DEVICE: int = -2
|
AUDIO_INPUT_DEVICE: int = -2
|
||||||
AUDIO_OUTPUT_DEVICE: int = -2
|
AUDIO_OUTPUT_DEVICE: int = -2
|
||||||
|
AUDIO_RECORD: bool = False
|
||||||
|
AUDIO_RECORD_FILE = ''
|
||||||
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
BUFFER_OVERFLOW_COUNTER: list = [0, 0, 0, 0, 0]
|
||||||
|
|
||||||
AUDIO_DBFS: int = 0
|
AUDIO_DBFS: int = 0
|
||||||
|
@ -90,7 +92,7 @@ ENABLE_FFT: bool = True
|
||||||
CHANNEL_BUSY: bool = False
|
CHANNEL_BUSY: bool = False
|
||||||
|
|
||||||
# ARQ PROTOCOL VERSION
|
# ARQ PROTOCOL VERSION
|
||||||
ARQ_PROTOCOL_VERSION: int = 4
|
ARQ_PROTOCOL_VERSION: int = 5
|
||||||
|
|
||||||
# ARQ statistics
|
# ARQ statistics
|
||||||
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
ARQ_BYTES_PER_MINUTE_BURST: int = 0
|
||||||
|
@ -101,6 +103,8 @@ ARQ_COMPRESSION_FACTOR: int = 0
|
||||||
ARQ_TRANSMISSION_PERCENT: int = 0
|
ARQ_TRANSMISSION_PERCENT: int = 0
|
||||||
ARQ_SPEED_LEVEL: int = 0
|
ARQ_SPEED_LEVEL: int = 0
|
||||||
TOTAL_BYTES: 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'
|
# CHANNEL_STATE = 'RECEIVING_SIGNALLING'
|
||||||
TNC_STATE: str = "IDLE"
|
TNC_STATE: str = "IDLE"
|
||||||
|
|
|
@ -35,6 +35,10 @@ ip, port = args.socket_host, args.socket_port
|
||||||
connected = True
|
connected = True
|
||||||
data = bytes()
|
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):
|
def decode_and_save_data(encoded_data):
|
||||||
decoded_data = base64.b64decode(encoded_data)
|
decoded_data = base64.b64decode(encoded_data)
|
||||||
|
@ -69,15 +73,20 @@ def decode_and_save_data(encoded_data):
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
sock.connect((ip, port))
|
sock.connect((ip, port))
|
||||||
|
print(sock)
|
||||||
while connected:
|
while connected:
|
||||||
chunk = sock.recv(2)
|
chunk = sock.recv(1024)
|
||||||
data += chunk
|
data += chunk
|
||||||
|
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"")
|
||||||
|
|
||||||
if data.startswith(b'{') and data.endswith(b'}\n'):
|
# iterate through data list
|
||||||
|
for command in data:
|
||||||
|
|
||||||
jsondata = json.loads(data.split(b'\n')[0])
|
jsondata = json.loads(command)
|
||||||
data = bytes()
|
|
||||||
|
|
||||||
if jsondata.get('command') == "tnc_state":
|
if jsondata.get('command') == "tnc_state":
|
||||||
pass
|
pass
|
||||||
|
@ -96,9 +105,10 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
log.info(f"rx buffer {rxdata.get('uuid')}")
|
log.info(f"rx buffer {rxdata.get('uuid')}")
|
||||||
decode_and_save_data(rxdata.get('data'))
|
decode_and_save_data(rxdata.get('data'))
|
||||||
|
|
||||||
if jsondata.get('status') == 'received':
|
if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission':
|
||||||
decode_and_save_data(jsondata["data"])
|
decode_and_save_data(jsondata["data"])
|
||||||
|
|
||||||
# clear data buffer as soon as data has been read
|
# clear data buffer as soon as data has been read
|
||||||
data = bytes()
|
data = bytes()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import base64
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
|
import crcengine
|
||||||
|
|
||||||
# --------------------------------------------GET PARAMETER INPUTS
|
# --------------------------------------------GET PARAMETER INPUTS
|
||||||
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
parser = argparse.ArgumentParser(description='Simons TEST TNC')
|
||||||
|
@ -47,26 +48,32 @@ else:
|
||||||
|
|
||||||
# convert binary data to base64
|
# convert binary data to base64
|
||||||
#base64_data = base64.b64encode(file).decode("UTF-8")
|
#base64_data = base64.b64encode(file).decode("UTF-8")
|
||||||
split_char = b'\0;'
|
split_char = b'\0;\1;'
|
||||||
|
|
||||||
filetype = b"unknown"
|
filetype = b"unknown"
|
||||||
timestamp = str(int(time.time()))
|
timestamp = str(int(time.time()))
|
||||||
|
|
||||||
# timestamp = timestamp.to_bytes(4, byteorder="big")
|
# timestamp = timestamp.to_bytes(4, byteorder="big")
|
||||||
timestamp = bytes(timestamp, "utf-8")
|
timestamp = bytes(timestamp, "utf-8")
|
||||||
msg_with_attachment = chatmessage + \
|
msg_with_attachment = timestamp + \
|
||||||
|
split_char + \
|
||||||
|
chatmessage + \
|
||||||
split_char + \
|
split_char + \
|
||||||
filename + \
|
filename + \
|
||||||
split_char + \
|
split_char + \
|
||||||
filetype + \
|
filetype + \
|
||||||
split_char + \
|
split_char + \
|
||||||
file + \
|
file
|
||||||
split_char + \
|
|
||||||
timestamp
|
# 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"
|
datatype = b"m"
|
||||||
command = b"send_message"
|
command = b"send_message"
|
||||||
checksum = b"123"
|
checksum = bytes(crc_data.hex(), "utf-8")
|
||||||
uuid_4 = bytes(str(uuid.uuid4()), "utf-8")
|
uuid_4 = bytes(str(uuid.uuid4()), "utf-8")
|
||||||
|
|
||||||
data = datatype + \
|
data = datatype + \
|
||||||
|
@ -98,9 +105,13 @@ command = {"type": "arq",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
command = json.dumps(command)
|
command = json.dumps(command)
|
||||||
|
print(command)
|
||||||
command = bytes(command + "\n", 'utf-8')
|
command = bytes(command + "\n", 'utf-8')
|
||||||
# Create a socket (SOCK_STREAM means a TCP socket)
|
# Create a socket (SOCK_STREAM means a TCP socket)
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
# Connect to server and send data
|
# Connect to server and send data
|
||||||
sock.connect((HOST, PORT))
|
sock.connect((HOST, PORT))
|
||||||
sock.sendall(command)
|
sock.sendall(command)
|
||||||
|
timeout = time.time() + 5
|
||||||
|
while time.time() < timeout:
|
||||||
|
pass
|
Loading…
Reference in a new issue