Merge pull request #416 from DJ2LS/ls-broadcast

first broadcast prototype
This commit is contained in:
DJ2LS 2023-05-17 13:25:10 +02:00 committed by GitHub
commit fe8bb04a04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 538 additions and 138 deletions

View file

@ -50,7 +50,7 @@ add_test(NAME tnc_irs_iss
python3 test_tnc.py")
set_tests_properties(tnc_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
# disabled this test as its actually broken since we entroduced session IDs
# disabled this test as its actually broken since we introduced session IDs
#add_test(NAME chat_text
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
# export PYTHONPATH=../tnc;
@ -65,12 +65,13 @@ add_test(NAME datac13_frames
python3 test_datac13.py")
set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME datac13_frames_negative
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
export PYTHONPATH=../tnc;
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
python3 test_datac13_negative.py")
set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
# disabled this test as its actually broken since we introduced dataclasses
#add_test(NAME datac13_frames_negative
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
# export PYTHONPATH=../tnc;
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
# python3 test_datac13_negative.py")
# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
add_test(NAME helper_routines
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;

View file

@ -48,6 +48,8 @@ var selected_callsign = "";
var lastIsWritingBroadcast = new Date().getTime();
var defaultUserIcon =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWJvdW5kaW5nLWJveCIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMS41IDFhLjUuNSAwIDAgMC0uNS41djNhLjUuNSAwIDAgMS0xIDB2LTNBMS41IDEuNSAwIDAgMSAxLjUgMGgzYS41LjUgMCAwIDEgMCAxaC0zek0xMSAuNWEuNS41IDAgMCAxIC41LS41aDNBMS41IDEuNSAwIDAgMSAxNiAxLjV2M2EuNS41IDAgMCAxLTEgMHYtM2EuNS41IDAgMCAwLS41LS41aC0zYS41LjUgMCAwIDEtLjUtLjV6TS41IDExYS41LjUgMCAwIDEgLjUuNXYzYS41LjUgMCAwIDAgLjUuNWgzYS41LjUgMCAwIDEgMCAxaC0zQTEuNSAxLjUgMCAwIDEgMCAxNC41di0zYS41LjUgMCAwIDEgLjUtLjV6bTE1IDBhLjUuNSAwIDAgMSAuNS41djNhMS41IDEuNSAwIDAgMS0xLjUgMS41aC0zYS41LjUgMCAwIDEgMC0xaDNhLjUuNSAwIDAgMCAuNS0uNXYtM2EuNS41IDAgMCAxIC41LS41eiIvPgogIDxwYXRoIGQ9Ik0zIDE0cy0xIDAtMS0xIDEtNCA2LTQgNiAzIDYgNC0xIDEtMSAxSDN6bTgtOWEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+Cjwvc3ZnPg==";
var defaultGroupIcon =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVvcGxlLWZpbGwiIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTcgMTRzLTEgMC0xLTEgMS00IDUtNCA1IDMgNSA0LTEgMS0xIDFIN1ptNC02YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNlptLTUuNzg0IDZBMi4yMzggMi4yMzggMCAwIDEgNSAxM2MwLTEuMzU1LjY4LTIuNzUgMS45MzYtMy43MkE2LjMyNSA2LjMyNSAwIDAgMCA1IDljLTQgMC01IDMtNSA0czEgMSAxIDFoNC4yMTZaTTQuNSA4YTIuNSAyLjUgMCAxIDAgMC01IDIuNSAyLjUgMCAwIDAgMCA1WiIvPgo8L3N2Zz4=";
// -----------------------------------
// Initially fill sharedFolderFileList
@ -136,6 +138,9 @@ var chatFilter = [
{ type: "received" },
{ type: "transmit" },
{ type: "ping-ack" },
{ type: "broadcast_received" },
{ type: "broadcast_transmit" },
//{ type: "request" },
//{ type: "response" },
];
@ -245,6 +250,7 @@ window.addEventListener("DOMContentLoaded", () => {
element.style.display = "none";
}
});
document
.getElementById("delete_selected_chat")
.addEventListener("click", () => {
@ -451,45 +457,71 @@ window.addEventListener("DOMContentLoaded", () => {
"bi bi-chevron-compact-up";
document.getElementById("expand_textarea").checked = false;
console.log(file);
console.log(filename);
console.log(filetype);
//console.log(file);
//console.log(filename);
//console.log(filetype);
if (filetype == "") {
filetype = "plain/text";
}
var timestamp = Math.floor(Date.now() / 1000);
var file_checksum = crc32(file).toString(16).toUpperCase();
console.log(file_checksum);
var data_with_attachment =
timestamp +
split_char +
chatmessage +
split_char +
filename +
split_char +
filetype +
split_char +
file;
document.getElementById("selectFilesButton").innerHTML = ``;
var uuid = uuidv4();
let uuidlast = uuid.lastIndexOf("-");
uuidlast += 1;
if (uuidlast > 0) {
uuid = uuid.substring(uuidlast);
}
console.log(data_with_attachment);
let Data = {
command: "msg",
dxcallsign: dxcallsign,
mode: 255,
frames: 5,
data: data_with_attachment,
checksum: file_checksum,
uuid: uuid,
};
ipcRenderer.send("run-tnc-command", Data);
// check if broadcast
if (dxcallsign.startsWith("BC-")) {
//let broadcastChannelId = dxcallsign.split("BC-")[1];
//broadcastChannelIdCRC = crc32(broadcastChannelId)
// .toString(16)
// .toUpperCase();
//dxcallsignWithID = "BC-" + broadcastChannelIdCRC;
var tnc_command = "broadcast";
var message_type = "broadcast_transmit";
// slice uuid for reducing overhead
uuid = uuid.slice(-4);
let Data = {
command: tnc_command,
broadcastChannel: dxcallsign,
data: chatmessage,
uuid: uuid,
};
ipcRenderer.send("run-tnc-command", Data);
} else {
var message_type = "transmit";
var file_checksum = crc32(file).toString(16).toUpperCase();
var tnc_command = "msg";
var data_with_attachment =
timestamp +
split_char +
chatmessage +
split_char +
filename +
split_char +
filetype +
split_char +
file;
document.getElementById("selectFilesButton").innerHTML = ``;
console.log(data_with_attachment);
let Data = {
command: tnc_command,
dxcallsign: dxcallsign,
mode: 255,
frames: 5,
data: data_with_attachment,
checksum: file_checksum,
uuid: uuid,
};
ipcRenderer.send("run-tnc-command", Data);
}
db.post({
_id: uuid,
timestamp: timestamp,
@ -497,7 +529,7 @@ window.addEventListener("DOMContentLoaded", () => {
dxgrid: "null",
msg: chatmessage,
checksum: file_checksum,
type: "transmit",
type: message_type,
status: "transmit",
attempt: 1,
uuid: uuid,
@ -708,11 +740,49 @@ ipcRenderer.on("action-new-msg-received", (event, arg) => {
var new_msg = arg.data;
new_msg.forEach(function (item) {
console.log(item.status);
let obj = new Object();
//handle ping
if (item.ping == "received") {
//handle broadcast
if (item.fec == "broadcast") {
console.log("BROADCAST RECEIVED");
console.log(item);
var transmitting_station = item.dxcallsign;
var encoded_data = FD.atob_FD(item.data);
var splitted_data = encoded_data.split(split_char);
console.log(splitted_data);
console.log(transmitting_station);
// add callsign to message:
var message = splitted_data[3];
console.log(message);
obj.timestamp = Math.floor(Date.now() / 1000);
obj.dxcallsign = splitted_data[1];
obj.dxgrid = "null";
obj.uuid = splitted_data[2];
obj.broadcast_sender = transmitting_station;
obj.command = "msg";
obj.checksum = "null";
obj.msg = message;
obj.status = "received";
obj.snr = item.snr;
obj.type = "broadcast_received";
obj.filename = "null";
obj.filetype = "null";
obj.file = "null";
console.log(obj);
add_obj_to_database(obj);
update_chat_obj_by_uuid(obj.uuid);
db.find({
selector: {
dxcallsign: obj.dxcallsign,
},
}).then(function (result) {
// handle result
console.log(result);
});
//handle ping
} else if (item.ping == "received") {
obj.timestamp = parseInt(item.timestamp);
obj.dxcallsign = item.dxcallsign;
obj.dxgrid = item.dxgrid;
@ -957,7 +1027,7 @@ update_chat = function (obj) {
} else {
var max_retry_attempts = parseInt(config.max_retry_attempts);
}
console.log(obj.msg);
// define shortmessage
if (obj.msg == "null" || obj.msg == "NULL") {
var shortmsg = obj.type;
@ -1068,16 +1138,28 @@ update_chat = function (obj) {
selected_callsign = dxcallsign;
}
getSetUserInformation(dxcallsign);
getSetUserSharedFolder(dxcallsign);
if (dxcallsign.startsWith("BC-")) {
var user_image =
'<img id="user-image-' +
dxcallsign +
'" class="p-1 rounded-circle" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVvcGxlLWZpbGwiIHZpZXdCb3g9IjAgMCAxNiAxNiI+CiAgPHBhdGggZD0iTTcgMTRzLTEgMC0xLTEgMS00IDUtNCA1IDMgNSA0LTEgMS0xIDFIN1ptNC02YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNlptLTUuNzg0IDZBMi4yMzggMi4yMzggMCAwIDEgNSAxM2MwLTEuMzU1LjY4LTIuNzUgMS45MzYtMy43MkE2LjMyNSA2LjMyNSAwIDAgMCA1IDljLTQgMC01IDMtNSA0czEgMSAxIDFoNC4yMTZaTTQuNSA4YTIuNSAyLjUgMCAxIDAgMC01IDIuNSAyLjUgMCAwIDAgMCA1WiIvPgo8L3N2Zz4="></img>';
} else {
var user_image =
'<img id="user-image-' +
dxcallsign +
'" class="p-1 rounded-circle" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWNpcmNsZSIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMTEgNmEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCA4YTggOCAwIDEgMSAxNiAwQTggOCAwIDAgMSAwIDh6bTgtN2E3IDcgMCAwIDAtNS40NjggMTEuMzdDMy4yNDIgMTEuMjI2IDQuODA1IDEwIDggMTBzNC43NTcgMS4yMjUgNS40NjggMi4zN0E3IDcgMCAwIDAgOCAxeiIvPgo8L3N2Zz4="></img>';
getSetUserInformation(dxcallsign);
getSetUserSharedFolder(dxcallsign);
}
var new_callsign = `
<a class="list-group-item list-group-item-action rounded-4 rounded-top rounded-bottom border-1 mb-2 ${callsign_selected}" id="chat-${dxcallsign}-list" data-bs-toggle="list" href="#chat-${dxcallsign}" role="tab" aria-controls="chat-${dxcallsign}">
<div class="d-flex w-100 justify-content-between">
<div class="rounded-circle p-0">
<img id="user-image-${dxcallsign}" class="p-1 rounded-circle" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0iY3VycmVudENvbG9yIiBjbGFzcz0iYmkgYmktcGVyc29uLWNpcmNsZSIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNMTEgNmEzIDMgMCAxIDEtNiAwIDMgMyAwIDAgMSA2IDB6Ii8+CiAgPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMCA4YTggOCAwIDEgMSAxNiAwQTggOCAwIDAgMSAwIDh6bTgtN2E3IDcgMCAwIDAtNS40NjggMTEuMzdDMy4yNDIgMTEuMjI2IDQuODA1IDEwIDggMTBzNC43NTcgMS4yMjUgNS40NjggMi4zN0E3IDcgMCAwIDAgOCAxeiIvPgo8L3N2Zz4="></img>
<!--<i class="bi bi-person-circle p-1" style="font-size:2rem;"></i>-->
${user_image}
</div>
<span style="font-size:1.2rem;"><strong>${dxcallsign}</strong></span>
@ -1211,7 +1293,76 @@ update_chat = function (obj) {
</div>
`;
}
if (obj.type == "broadcast_received") {
console.log(obj);
var new_message = `
<div class="d-flex align-items-center" style="margin-left: auto;"> <!-- max-width: 75%; -->
<div class="mt-3 rounded-3 mb-0" style="max-width: 75%;" id="msg-${obj._id}">
<!--<p class="font-monospace text-small mb-0 text-muted text-break">${timestamp}</p>-->
<div class="card border-light bg-light" id="msg-${obj._id}">
<div class="card-body rounded-3 p-0">
<p class="card-text p-2 mb-0 text-break text-wrap">${message_html}</p>
<p class="text-right mb-0 p-1 text-white" style="text-align: left; font-size : 0.9rem">
<span class="badge bg-light text-muted">${timestamp}</span>
</p>
<span id="msg-${obj._id}-dxcallsign-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-secondary border border-white">
<span id="msg-${obj._id}-attempts" class="">${obj.broadcast_sender}</span>
<span class="visually-hidden">dxcallsign</span>
</span>
</div>
</div>
</div>
<div class="me-auto" id="msg-${obj._id}-control-area">
<button class="btn bg-transparent p-1 m-1"><i class="bi bi-trash link-secondary" id="del-msg-${obj._id}" style="font-size: 1.2rem;"></i></button>
</div>
</div>
`;
}
if (obj.type == "broadcast_transmit") {
var new_message = `
<div class="d-flex align-items-center">
<div class="ms-auto" id="msg-${obj._id}-control-area">
<!--<button class="btn bg-transparent p-1 m-1"><i class="bi bi-arrow-repeat link-secondary" id="retransmit-msg-${
obj._id
}" style="font-size: 1.2rem;"></i></button>-->
<button class="btn bg-transparent p-1 m-1"><i class="bi bi-trash link-secondary" id="del-msg-${
obj._id
}" style="font-size: 1.2rem;"></i></button>
</div>
<div class="rounded-3 mt-3 mb-0 me-2" style="max-width: 75%;">
<div class="card border-primary bg-primary" id="msg-${obj._id}">
<div class="card-body rounded-3 p-0 text-right bg-primary">
<p class="card-text p-1 mb-0 text-white text-break text-wrap">${message_html}</p>
<p class="text-right mb-0 p-1 text-white" style="text-align: right; font-size : 0.9rem">
<span class="text-light" style="font-size: 0.7rem;">${timestamp} - </span>
<span class="text-white" id="msg-${
obj._id
}-status" style="font-size:0.8rem;">${get_icon_for_state(
obj.status
)}</span>
</p>
<span id="msg-${
obj._id
}-attempts-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-1 bg-primary border border-white">
<span id="msg-${
obj._id
}-attempts" class="">${attempt}/${max_retry_attempts}</span>
<span class="visually-hidden">retries</span>
</span>
</div>
</div>
</div>
`;
}
if (obj.type == "transmit") {
console.log(obj);
//console.log('msg-' + obj._id + '-status')
if (obj.status == "failed") {
@ -1285,28 +1436,26 @@ update_chat = function (obj) {
console.log("element already exists......");
console.log(obj);
console.log(
if (
!obj.status == "broadcast_transmit" ||
!obj.status == "broadcast_received"
) {
document.getElementById("msg-" + obj._id + "-status").innerHTML =
get_icon_for_state(obj.status);
document
.getElementById("msg-" + obj._id + "-progress")
.getAttribute("aria-valuenow")
);
document.getElementById("msg-" + obj._id + "-status").innerHTML =
get_icon_for_state(obj.status);
document
.getElementById("msg-" + obj._id + "-progress")
.setAttribute("aria-valuenow", obj.percent);
document
.getElementById("msg-" + obj._id + "-progress")
.setAttribute("style", "width:" + obj.percent + "%;");
document.getElementById(
"msg-" + obj._id + "-progress-information"
).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm";
document.getElementById("msg-" + obj._id + "-attempts").innerHTML =
obj.attempt + "/" + max_retry_attempts;
.setAttribute("aria-valuenow", obj.percent);
document
.getElementById("msg-" + obj._id + "-progress")
.setAttribute("style", "width:" + obj.percent + "%;");
document.getElementById(
"msg-" + obj._id + "-progress-information"
).innerHTML = obj.percent + "% - " + obj.bytesperminute + " Bpm";
document.getElementById("msg-" + obj._id + "-attempts").innerHTML =
obj.attempt + "/" + max_retry_attempts;
}
if (obj.status == "transmitted") {
//document.getElementById('msg-' + obj._id + '-progress').classList.remove("progress-bar-striped");
document
@ -1323,7 +1472,10 @@ update_chat = function (obj) {
document.getElementById(
"msg-" + obj._id + "-progress-information"
).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm";
} else {
} else if (
!obj.status == "broadcast_transmit" ||
!obj.status == "broadcast_received"
) {
document
.getElementById("msg-" + obj._id + "-progress")
.classList.add("progress-bar-striped");
@ -1607,6 +1759,7 @@ add_obj_to_database = function (obj) {
db.put({
_id: obj.uuid,
timestamp: parseInt(obj.timestamp),
broadcast_sender: obj.broadcast_sender,
uuid: obj.uuid,
dxcallsign: obj.dxcallsign,
dxgrid: obj.dxgrid,
@ -1631,9 +1784,20 @@ add_obj_to_database = function (obj) {
.catch(function (err) {
console.log("already exists");
console.log(err);
console.log(obj);
db.upsert(obj.uuid, function (doc) {
doc = obj;
return doc;
})
.then(function (response) {
console.log("upsert");
console.log(response);
})
.catch(function (err) {
console.log(err);
});
});
};
/* users database functions */
addUserToDatabaseIfNotExists = function (obj) {
/*
@ -1934,9 +2098,10 @@ async function updateAllChat(clear) {
],
})
.then(async function (result) {
console.log(result);
// handle result async
//document.getElementById("blurOverlay").classList.add("bg-primary");
console.log(result);
if (typeof result !== "undefined") {
for (const item of result.docs) {
//await otherwise history will not be in chronological order
@ -1981,6 +2146,14 @@ function getSetUserSharedFolder(selected_callsign) {
console.log("return triggered");
return;
}
// disable button if broadcast
if (selected_callsign.startsWith("BC-")) {
document.getElementById("sharedFolderDXButton").disabled = true;
} else {
document.getElementById("sharedFolderDXButton").disabled = false;
}
returnObjFromCallsign(users, selected_callsign)
.then(function (data) {
console.log(data);
@ -2110,6 +2283,16 @@ function getSetUserInformation(selected_callsign) {
console.log("return triggered");
return;
}
// disable button if broadcast
if (selected_callsign.startsWith("BC-")) {
document.getElementById("userModalDXButton").disabled = true;
document.getElementById("ping").disabled = true;
} else {
document.getElementById("userModalDXButton").disabled = false;
document.getElementById("ping").disabled = false;
}
document.getElementById("dx_user_info_callsign").innerHTML =
selected_callsign;
@ -2127,6 +2310,10 @@ function getSetUserInformation(selected_callsign) {
// split data string by "base64" for separating image type from base64 string
atob(data.user_info_image.split(";base64,")[1]);
if (selected_callsign.startsWith("BC-")) {
data.user_info_image = defaultGroupIcon;
}
document.getElementById("dx_user_info_image").src =
data.user_info_image;
document.getElementById("user-image-" + selected_callsign).src =
@ -2134,9 +2321,22 @@ function getSetUserInformation(selected_callsign) {
} catch (e) {
console.log(e);
console.log("corrupted image data");
if (selected_callsign.startsWith("BC-")) {
var userIcon = defaultGroupIcon;
document
.getElementById("chatModuleMessage")
.setAttribute("maxlength", 16);
} else {
var userIcon = defaultUserIcon;
document
.getElementById("chatModuleMessage")
.setAttribute("maxlength", 524288);
}
document.getElementById("user-image-" + selected_callsign).src =
defaultUserIcon;
document.getElementById("dx_user_info_image").src = defaultUserIcon;
userIcon;
document.getElementById("dx_user_info_image").src = userIcon;
}
} else {
// throw error and use placeholder data
@ -2192,9 +2392,20 @@ function getSetUserInformation(selected_callsign) {
console.log("writing user info to modal failed");
console.log(err);
if (selected_callsign.startsWith("BC-")) {
document
.getElementById("chatModuleMessage")
.setAttribute("maxlength", 16);
var userIcon = defaultGroupIcon;
} else {
var userIcon = defaultUserIcon;
document
.getElementById("chatModuleMessage")
.setAttribute("maxlength", 524288);
}
// Callsign list elements
document.getElementById("user-image-" + selected_callsign).src =
defaultUserIcon;
document.getElementById("user-image-" + selected_callsign).src = userIcon;
document.getElementById("user-image-" + selected_callsign).className =
"p-1 rounded-circle w-100";
document.getElementById("user-image-" + selected_callsign).style =

View file

@ -1606,29 +1606,62 @@ window.addEventListener("DOMContentLoaded", () => {
if (hslLastSort == 0 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(0);
resetSortIcon();
});
document.getElementById("thFreq").addEventListener("click", () => {
if (hslLastSort == 1 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(1);
resetSortIcon();
});
document.getElementById("thDxcall").addEventListener("click", () => {
if (hslLastSort == 3 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(3);
resetSortIcon();
});
document.getElementById("thDxgrid").addEventListener("click", () => {
if (hslLastSort == 4 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(4);
resetSortIcon();
});
document.getElementById("thDist").addEventListener("click", () => {
if (hslLastSort == 5 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(5);
resetSortIcon();
});
document.getElementById("thType").addEventListener("click", () => {
if (hslLastSort == 6 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(6);
resetSortIcon();
});
document.getElementById("thSnr").addEventListener("click", () => {
if (hslLastSort == 7 && hslLastSortDir == "asc") hslLastSortDir = "desc";
else hslLastSortDir = "asc";
sorthslTable(7);
resetSortIcon();
});
});
function resetSortIcon() {
document.getElementById("hslSort").remove();
let headers = document.querySelectorAll(
"#tblHeardStationList > thead > tr > th"
);
if (hslLastSortDir == "desc")
text =
'<i id="hslSort" class="bi bi-sort-up"></i>' +
headers[hslLastSort].innerText;
else
text =
'<i id="hslSort" class="bi bi-sort-down"></i>' +
headers[hslLastSort].innerText;
headers[hslLastSort].innerHTML = text;
}
function connectedStation(data) {
if (typeof data.dxcallsign == "undefined") {
return;
@ -2791,6 +2824,9 @@ ipcRenderer.on("run-tnc-command", (event, arg) => {
arg.command
);
}
if (arg.command == "broadcast") {
sock.sendBroadcastChannel(arg.broadcastChannel, arg.data, arg.uuid);
}
if (arg.command == "stop_transmission") {
sock.stopTransmission();
}
@ -3549,7 +3585,7 @@ function changeGuiDesign(design) {
}
var hslLastSort = 0;
var hslLastSortDir = "asc";
var hslLastSortDir = "desc";
//https://www.w3schools.com/howto/howto_js_sort_table.asp
function sorthslTable(n) {

View file

@ -242,6 +242,22 @@ client.on("data", function (socketdata) {
data: [data],
});
break;
case "broadcast":
// RX'd FEC BROADCAST
var encoded_data = FD.atob_FD(data["data"]);
var splitted_data = encoded_data.split(split_char);
var messageArray = [];
if (splitted_data[0] == "m") {
messageArray.push(data);
console.log(data);
}
let Messages = {
data: messageArray,
};
ipcRenderer.send("request-new-msg-received", Messages);
break;
}
switch (data["cq"]) {
@ -835,6 +851,30 @@ exports.sendFecIsWriting = function (mycallsign) {
writeTncCommand(command);
};
// SEND FEC TO BROADCASTCHANNEL
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
let checksum = "";
let command = "";
let data = FD.btoa_FD(
"m" +
split_char +
channel +
//split_char +
//checksum +
split_char +
uuid +
split_char +
data_out
);
console.log(data.length);
let payload = data;
command =
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
payload +
'"}';
writeTncCommand(command);
};
// RECORD AUDIO
exports.record_audio = function () {
command = '{"type" : "set", "command" : "record_audio"}';

View file

@ -97,39 +97,7 @@
id="list-tab"
role="tablist"
style="height: calc(100vh - 70px)"
>
<a
class="list-group-item list-group-item-action rounded-4 rounded-top rounded-bottom border-1 mb-0 broadcast"
id="chat-broadcast-list"
data-bs-toggle="list"
href="#chat-broadcast"
role="tab"
aria-controls="chat-${dxcallsign}"
>
<div class="d-flex w-100 justify-content-between">
<div class="rounded-circle p-0">
<i
class="bi bi-people-fill p-1"
style="font-size: 2rem"
></i>
</div>
<span style="font-size: 1.2rem"
><strong>BROADCAST @ALL</strong></span
>
<span style="font-size: 0.8rem" id="chat-broadcast-list-time"
>---</span
>
<span
class="position-absolute m-2 bottom-0 end-0"
style="font-size: 0.8rem"
id="chat-broadcast-list-shortmsg"
>---</span
>
</div>
</a>
<hr class="m-2" />
</div>
></div>
</div>
</div>
<div class="col-8 border vh-100 p-0">
@ -307,25 +275,7 @@
id="message-container"
style="height: calc(100% - 150px)"
>
<div class="tab-content" id="nav-tabContent">
<div
class="tab-pane fade broadcast"
id="chat-broadcast"
role="tabpanel"
aria-labelledby="chat-broadcast-list"
>
<h3>Broadcast work in progress...</h3>
ToDo:<br />
- disable request buttons and ping when selecting broadcast
field<br />
- enable request buttons and ping when selecting normal
messages<br />
- received broadcast messages must find its place here<br />
- transmitted messages here need a broadcast handler <br />
- prepare tnc for sending and receiving broadcasts with wakeup
frame <br />
</div>
</div>
<div class="tab-content" id="nav-tabContent"></div>
<!--<div class="container position-absolute bottom-0">-->
</div>
<!-- </div>-->

View file

@ -1152,14 +1152,16 @@
<table class="table table-sm" id="tblHeardStationList">
<thead>
<tr>
<th scope="col" id="thTime">Time</th>
<th scope="col" id="thTime">
<i id="hslSort" class="bi bi-sort-up"></i>Time
</th>
<th scope="col" id="thFreq">Frequency</th>
<th>&nbsp;</th>
<th scope="col" id="thDxcall">DXCall</th>
<th scope="col" id="thDxgrid">DXGrid</th>
<th scope="col" id="thDist">Distance</th>
<th scope="col">Type</th>
<th scope="col">SNR (rx/dx)</th>
<th scope="col" id="thType">Type</th>
<th scope="col" id="thSnr">SNR (rx/dx)</th>
<!--<th scope="col">Off</th>-->
</tr>
</thead>

118
tnc/broadcast.py Normal file
View file

@ -0,0 +1,118 @@
import structlog
import threading
import helpers
import time
import modem
import base64
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
import sock
import ujson as json
class broadcastHandler:
"""Terminal Node Controller for FreeDATA"""
log = structlog.get_logger("BROADCAST")
def __init__(self) -> None:
self.fec_wakeup_callsign = bytes()
self.longest_duration = 6
self.wakeup_received = False
self.broadcast_timeout_reached = False
self.broadcast_payload_bursts = 1
self.broadcast_watchdog = threading.Thread(
target=self.watchdog, name="watchdog thread", daemon=True
)
self.broadcast_watchdog.start()
def received_fec_wakeup(self, data_in: bytes):
self.fec_wakeup_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
self.wakeup_mode = int.from_bytes(bytes(data_in[7:8]), "big")
bursts = int.from_bytes(bytes(data_in[8:9]), "big")
self.wakeup_received = True
modem.RECEIVE_DATAC4 = True
self.send_data_to_socket_queue(
freedata="tnc-message",
fec="wakeup",
mode=self.wakeup_mode,
bursts=bursts,
dxcallsign=str(self.fec_wakeup_callsign, "UTF-8")
)
self.log.info(
"[TNC] FRAME WAKEUP RCVD ["
+ str(self.fec_wakeup_callsign, "UTF-8")
+ "] ", mode=self.wakeup_mode, bursts=bursts,
)
def received_fec(self, data_in: bytes):
print(self.fec_wakeup_callsign)
self.send_data_to_socket_queue(
freedata="tnc-message",
fec="broadcast",
dxcallsign=str(self.fec_wakeup_callsign, "UTF-8"),
data=base64.b64encode(data_in[1:]).decode("UTF-8")
)
self.log.info("[TNC] FEC DATA RCVD")
def send_data_to_socket_queue(self, **jsondata):
"""
Send information to the UI via JSON and the sock.SOCKET_QUEUE.
Args:
Dictionary containing the data to be sent, in the format:
key=value, for each item. E.g.:
self.send_data_to_socket_queue(
freedata="tnc-message",
arq="received",
status="success",
uuid=self.transmission_uuid,
timestamp=timestamp,
mycallsign=str(self.mycallsign, "UTF-8"),
dxcallsign=str(Station.dxcallsign, "UTF-8"),
dxgrid=str(Station.dxgrid, "UTF-8"),
data=base64_data,
)
"""
# add mycallsign and dxcallsign to network message if they not exist
# and make sure we are not overwrite them if they exist
try:
if "mycallsign" not in jsondata:
jsondata["mycallsign"] = str(Station.mycallsign, "UTF-8")
if "dxcallsign" not in jsondata:
jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8")
except Exception as e:
self.log.debug("[TNC] error adding callsigns to network message", e=e)
# run json dumps
json_data_out = json.dumps(jsondata)
self.log.debug("[TNC] send_data_to_socket_queue:", jsondata=json_data_out)
# finally push data to our network queue
sock.SOCKET_QUEUE.put(json_data_out)
def watchdog(self):
while 1:
if self.wakeup_received:
timeout = time.time() + (self.longest_duration * self.broadcast_payload_bursts) + 2
while time.time() < timeout:
threading.Event().wait(0.01)
self.broadcast_timeout_reached = True
self.log.info(
"[TNC] closing broadcast slot ["
+ str(self.fec_wakeup_callsign, "UTF-8")
+ "] ", mode=self.wakeup_mode, bursts=self.broadcast_payload_bursts,
)
# TODO: We need a dynamic way of modifying this
modem.RECEIVE_DATAC4 = False
self.fec_wakeup_callsign = bytes()
self.wakeup_received = False
else:
threading.Event().wait(0.01)

View file

@ -35,6 +35,7 @@ class FREEDV_MODE(Enum):
fsk_ldpc_0 = 200
fsk_ldpc_1 = 201
class FREEDV_MODE_USED_SLOTS(Enum):
"""
Enumeration for codec2 used slots

View file

@ -10,14 +10,14 @@ ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[AUDIO]
#audio settings
rx = 10
tx = 10
rx = 0
tx = 0
txaudiolevel = 250
auto_tune = False
[RADIO]
#radio settings
radiocontrol = rigctld
radiocontrol = disabled
rigctld_ip = 127.0.0.1
rigctld_port = 4532
@ -38,3 +38,4 @@ tx_delay = 50
[TCI]
ip = 127.0.0.1
port = 50001

View file

@ -28,7 +28,7 @@ import ujson as json
from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS
from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER
from static import FRAME_TYPE as FR_TYPE
import broadcast
TESTMODE = False
@ -101,6 +101,8 @@ class DATA:
self.rx_n_frames_per_burst = 0
self.max_n_frames_per_burst = 1
self.broadcast = broadcast.broadcastHandler()
# Flag to indicate if we received a low bandwidth mode channel opener
self.received_LOW_BANDWIDTH_MODE = False
@ -221,6 +223,8 @@ class DATA:
FR_TYPE.PING.value: (self.received_ping, "PING"),
FR_TYPE.QRV.value: (self.received_qrv, "QRV"),
FR_TYPE.IS_WRITING.value: (self.received_is_writing, "IS_WRITING"),
FR_TYPE.FEC.value: (self.broadcast.received_fec, "FEC"),
FR_TYPE.FEC_WAKEUP.value: (self.broadcast.received_fec_wakeup, "FEC WAKEUP"),
}
self.command_dispatcher = {
@ -324,15 +328,18 @@ class DATA:
# [5] attempts
self.open_dc_and_transmit(data[1], data[2], data[3], data[4], data[5])
elif data[0] == "FEC":
# [1] DATA_OUT bytes
# [2] MODE str datac0/1/3...
self.send_fec_frame(data[1], data[2])
elif data[0] == "FEC_IS_WRITING":
# [1] DATA_OUT bytes
# [2] MODE str datac0/1/3...
self.send_fec_is_writing(data[1])
elif data[0] == "FEC":
# [1] WAKEUP bool
# [2] MODE str datac0/1/3...
# [3] PAYLOAD
# [4] MYCALLSIGN
self.send_fec(data[1], data[2], data[3], data[4])
else:
self.log.error(
"[TNC] worker_transmit: received invalid command:", data=data
@ -392,6 +399,8 @@ class DATA:
FR_TYPE.PING.value,
FR_TYPE.BEACON.value,
FR_TYPE.IS_WRITING.value,
FR_TYPE.FEC.value,
FR_TYPE.FEC_WAKEUP.value,
]
):
@ -2967,6 +2976,8 @@ class DATA:
HamlibParam.hamlib_frequency,
)
def received_is_writing(self, data_in: bytes) -> None:
"""
Called when we receive a IS WRITING frame
@ -3447,13 +3458,30 @@ class DATA:
frame_to_tx=[test_frame], c2_mode=FREEDV_MODE.datac13.value
)
def send_fec_frame(self, payload, mode) -> None:
def send_fec(self, mode, wakeup, payload, mycallsign):
"""Send an empty test frame"""
print(wakeup)
print(payload)
print(mycallsign)
mode_int = codec2.freedv_get_mode_value_by_name(mode)
payload_per_frame = modem.get_bytes_per_frame(mode_int) - 2
fec_payload_length = payload_per_frame - 1
if wakeup:
mode_int_wakeup = codec2.freedv_get_mode_value_by_name("sig0")
payload_per_wakeup_frame = modem.get_bytes_per_frame(mode_int_wakeup) - 2
fec_wakeup_frame = bytearray(payload_per_wakeup_frame)
fec_wakeup_frame[:1] = bytes([FR_TYPE.FEC_WAKEUP.value])
fec_wakeup_frame[1:7] = helpers.callsign_to_bytes(mycallsign)
fec_wakeup_frame[7:8] = bytes([mode_int])
fec_wakeup_frame[8:9] = bytes([1]) # n payload bursts
print(mode_int_wakeup)
self.enqueue_frame_for_tx(
frame_to_tx=[fec_wakeup_frame], c2_mode=codec2.FREEDV_MODE["sig1"].value
)
time.sleep(1)
fec_frame = bytearray(payload_per_frame)
fec_frame[:1] = bytes([FR_TYPE.FEC.value])
fec_frame[1:payload_per_frame] = bytes(payload[:fec_payload_length])
@ -3462,7 +3490,7 @@ class DATA:
)
def send_fec_is_writing(self, mycallsign) -> None:
"""Send an empty test frame"""
"""Send an fec is writing frame"""
fec_frame = bytearray(14)
fec_frame[:1] = bytes([FR_TYPE.IS_WRITING.value])
@ -3477,6 +3505,7 @@ class DATA:
else:
return False
def save_data_to_folder(self,
transmission_uuid,
timestamp,

View file

@ -482,12 +482,22 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
def tnc_fec_transmit(self, received_json):
try:
mode = received_json["mode"]
wakeup = received_json["wakeup"]
base64data = received_json["payload"]
if len(base64data) % 4:
raise TypeError
payload = base64.b64decode(base64data)
DATA_QUEUE_TRANSMIT.put(["FEC", payload, mode])
try:
mycallsign = received_json["mycallsign"]
mycallsign = helpers.callsign_to_bytes(mycallsign)
mycallsign = helpers.bytes_to_callsign(mycallsign)
except Exception:
mycallsign = Station.mycallsign
DATA_QUEUE_TRANSMIT.put(["FEC", mode, wakeup, payload, mycallsign])
command_response("fec_transmit", True)
except Exception as err:
command_response("fec_transmit", False)

View file

@ -127,7 +127,7 @@ class TCIParam:
@dataclass
class TNC:
version = "0.9.1-alpha.3"
version = "0.9.2-alpha.1"
host: str = "0.0.0.0"
port: int = 3000
SOCKET_TIMEOUT: int = 1 # seconds
@ -174,6 +174,7 @@ class FRAME_TYPE(Enum):
ARQ_STOP = 249
BEACON = 250
FEC = 251
FEC_WAKEUP = 252
IDENT = 254
TEST_FRAME = 255