mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge pull request #449 from DJ2LS/ls-mesh
This commit is contained in:
commit
9205d23390
21 changed files with 1213 additions and 51 deletions
|
@ -227,7 +227,8 @@ exports.startTNC = function (
|
||||||
auto_tune,
|
auto_tune,
|
||||||
tx_delay,
|
tx_delay,
|
||||||
tci_ip,
|
tci_ip,
|
||||||
tci_port
|
tci_port,
|
||||||
|
enable_mesh
|
||||||
) {
|
) {
|
||||||
var json_command = JSON.stringify({
|
var json_command = JSON.stringify({
|
||||||
type: "set",
|
type: "set",
|
||||||
|
@ -264,6 +265,7 @@ exports.startTNC = function (
|
||||||
tx_delay: tx_delay,
|
tx_delay: tx_delay,
|
||||||
tci_ip: tci_ip,
|
tci_ip: tci_ip,
|
||||||
tci_port: tci_port,
|
tci_port: tci_port,
|
||||||
|
enable_mesh: enable_mesh
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
40
gui/main.js
40
gui/main.js
|
@ -101,7 +101,8 @@ const configDefaultSettings =
|
||||||
"enable_auto_retry" : "False", \
|
"enable_auto_retry" : "False", \
|
||||||
"tx_delay" : 0, \
|
"tx_delay" : 0, \
|
||||||
"auto_start": 0, \
|
"auto_start": 0, \
|
||||||
"enable_sys_notification": 1 \
|
"enable_sys_notification": 1, \
|
||||||
|
"enable_mesh_features": "False" \
|
||||||
}';
|
}';
|
||||||
|
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
|
@ -127,7 +128,6 @@ for (key in parsedConfig) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sysInfo.info("------------------------------------------ ");
|
sysInfo.info("------------------------------------------ ");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
var chatDB = path.join(configFolder, 'chatDB.json')
|
var chatDB = path.join(configFolder, 'chatDB.json')
|
||||||
// create chat database file if not exists
|
// create chat database file if not exists
|
||||||
|
@ -166,6 +166,7 @@ fs.mkdir(receivedFilesFolder, {
|
||||||
let win = null;
|
let win = null;
|
||||||
let data = null;
|
let data = null;
|
||||||
let logViewer = null;
|
let logViewer = null;
|
||||||
|
let meshViewer = null;
|
||||||
var daemonProcess = null;
|
var daemonProcess = null;
|
||||||
|
|
||||||
// create a splash screen
|
// create a splash screen
|
||||||
|
@ -249,6 +250,29 @@ function createWindow() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
meshViewer = new BrowserWindow({
|
||||||
|
height: 900,
|
||||||
|
width: 600,
|
||||||
|
show: false,
|
||||||
|
//parent: win,
|
||||||
|
webPreferences: {
|
||||||
|
preload: require.resolve("./preload-mesh.js"),
|
||||||
|
nodeIntegration: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
meshViewer.loadFile("src/mesh-module.html");
|
||||||
|
meshViewer.setMenuBarVisibility(false);
|
||||||
|
|
||||||
|
// Emitted when the window is closed.
|
||||||
|
meshViewer.on("close", function (evt) {
|
||||||
|
if (meshViewer !== null) {
|
||||||
|
evt.preventDefault();
|
||||||
|
meshViewer.hide();
|
||||||
|
} else {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
// Emitted when the window is closed.
|
// Emitted when the window is closed.
|
||||||
win.on("closed", function () {
|
win.on("closed", function () {
|
||||||
console.log("closing all windows.....");
|
console.log("closing all windows.....");
|
||||||
|
@ -392,6 +416,11 @@ ipcMain.on("request-clear-chat-connected", () => {
|
||||||
chat.webContents.send("action-clear-reception-status");
|
chat.webContents.send("action-clear-reception-status");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on("request-update-dbclean-spinner", () => {
|
||||||
|
//Turn off dbclean spinner
|
||||||
|
win.webContents.send("action-update-dbclean-spinner");
|
||||||
|
});
|
||||||
|
|
||||||
// UPDATE TNC CONNECTION
|
// UPDATE TNC CONNECTION
|
||||||
ipcMain.on("request-update-tnc-ip", (event, data) => {
|
ipcMain.on("request-update-tnc-ip", (event, data) => {
|
||||||
win.webContents.send("action-update-tnc-ip", data);
|
win.webContents.send("action-update-tnc-ip", data);
|
||||||
|
@ -404,6 +433,7 @@ ipcMain.on("request-update-daemon-ip", (event, data) => {
|
||||||
|
|
||||||
ipcMain.on("request-update-tnc-state", (event, arg) => {
|
ipcMain.on("request-update-tnc-state", (event, arg) => {
|
||||||
win.webContents.send("action-update-tnc-state", arg);
|
win.webContents.send("action-update-tnc-state", arg);
|
||||||
|
meshViewer.send("action-update-mesh-table", arg)
|
||||||
//data.webContents.send('action-update-tnc-state', arg);
|
//data.webContents.send('action-update-tnc-state', arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -483,6 +513,11 @@ ipcMain.on("request-open-tnc-log", () => {
|
||||||
logViewer.show();
|
logViewer.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on("request-open-mesh-module", () => {
|
||||||
|
meshViewer.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//file selector
|
//file selector
|
||||||
ipcMain.on("get-file-path", (event, data) => {
|
ipcMain.on("get-file-path", (event, data) => {
|
||||||
dialog
|
dialog
|
||||||
|
@ -901,6 +936,7 @@ function close_all() {
|
||||||
win.destroy();
|
win.destroy();
|
||||||
chat.destroy();
|
chat.destroy();
|
||||||
logViewer.destroy();
|
logViewer.destroy();
|
||||||
|
meshViewer.destroy();
|
||||||
|
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -683,7 +683,9 @@ ipcRenderer.on("action-update-transmission-status", (event, arg) => {
|
||||||
|
|
||||||
document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign;
|
document.getElementById("txtConnectedWithChat").textContent = data.dxcallsign;
|
||||||
|
|
||||||
console.log(data.status);
|
if (typeof data.uuid === undefined) return;
|
||||||
|
|
||||||
|
//console.log(data.status);
|
||||||
if (data.uuid !== "no-uuid") {
|
if (data.uuid !== "no-uuid") {
|
||||||
db.get(data.uuid, {
|
db.get(data.uuid, {
|
||||||
attachments: true,
|
attachments: true,
|
||||||
|
@ -1054,13 +1056,13 @@ update_chat = function (obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if wrong status message
|
// check if wrong status message
|
||||||
if (obj.status == "transmit" && obj.percent == 0) {
|
if (obj.status == "transmit" && obj.type == "transmit" && obj.percent < 100) {
|
||||||
var TimeDifference = new Date().getTime() / 1000 - obj.timestamp;
|
var TimeDifference = new Date().getTime() / 1000 - obj.timestamp;
|
||||||
if (TimeDifference > 3600) {
|
if (TimeDifference > 21600) { //Six hours
|
||||||
|
console.log("Resetting message to failed state since in transmit status for over 6 hours:")
|
||||||
|
console.log(obj);
|
||||||
db.upsert(obj._id, function (doc) {
|
db.upsert(obj._id, function (doc) {
|
||||||
if (!doc.status) {
|
|
||||||
doc.status = "failed";
|
doc.status = "failed";
|
||||||
}
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
obj.status = "failed";
|
obj.status = "failed";
|
||||||
|
@ -1440,12 +1442,14 @@ update_chat = function (obj) {
|
||||||
if (obj.status == "failed") {
|
if (obj.status == "failed") {
|
||||||
var progressbar_bg = "bg-danger";
|
var progressbar_bg = "bg-danger";
|
||||||
var percent_value = "TRANSMISSION FAILED";
|
var percent_value = "TRANSMISSION FAILED";
|
||||||
|
//Set to 100 so progressbar background populates
|
||||||
|
obj.percent=100;
|
||||||
} else if (obj.status == "transmitted") {
|
} else if (obj.status == "transmitted") {
|
||||||
var progressbar_bg = "bg-success";
|
var progressbar_bg = "bg-success";
|
||||||
var percent_value = "TRANSMITTED";
|
var percent_value = "TRANSMITTED";
|
||||||
} else {
|
} else {
|
||||||
var progressbar_bg = "bg-primary";
|
var progressbar_bg = "bg-primary";
|
||||||
var percent_value = obj.percent;
|
var percent_value = obj.percent + " %";
|
||||||
}
|
}
|
||||||
|
|
||||||
//Sneak in low graphics mode if so enabled for progress bars
|
//Sneak in low graphics mode if so enabled for progress bars
|
||||||
|
@ -1487,9 +1491,9 @@ update_chat = function (obj) {
|
||||||
}%;" aria-valuenow="${
|
}%;" aria-valuenow="${
|
||||||
obj.percent
|
obj.percent
|
||||||
}" aria-valuemin="0" aria-valuemax="100"></div>
|
}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
<p class="justify-content-center d-flex position-absolute m-0 p-0 w-100 text-white ${progressbar_bg}" style="font-size: xx-small" id="msg-${
|
<p class="justify-content-center d-flex position-absolute m-0 p-0 w-100 text-white" style="font-size: xx-small" id="msg-${
|
||||||
obj._id
|
obj._id
|
||||||
}-progress-information">${percent_value} % - ${obj.bytesperminute} Bpm</p>
|
}-progress-information">${percent_value} - ${obj.bytesperminute} Bpm</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1510,8 +1514,8 @@ update_chat = function (obj) {
|
||||||
// console.log(obj.attempt)
|
// console.log(obj.attempt)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!obj.status == "broadcast_transmit" ||
|
obj.status != "broadcast_transmit" ||
|
||||||
!obj.status == "broadcast_received"
|
obj.status != "broadcast_received"
|
||||||
) {
|
) {
|
||||||
document.getElementById("msg-" + obj._id + "-status").innerHTML =
|
document.getElementById("msg-" + obj._id + "-status").innerHTML =
|
||||||
get_icon_for_state(obj.status);
|
get_icon_for_state(obj.status);
|
||||||
|
@ -1577,8 +1581,8 @@ update_chat = function (obj) {
|
||||||
"msg-" + obj._id + "-progress-information"
|
"msg-" + obj._id + "-progress-information"
|
||||||
).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm";
|
).innerHTML = "TRANSMITTED - " + obj.bytesperminute + " Bpm";
|
||||||
} else if (
|
} else if (
|
||||||
!obj.status == "broadcast_transmit" ||
|
obj.status != "broadcast_transmit" ||
|
||||||
!obj.status == "broadcast_received"
|
obj.status != "broadcast_received"
|
||||||
) {
|
) {
|
||||||
document
|
document
|
||||||
.getElementById("msg-" + obj._id + "-progress")
|
.getElementById("msg-" + obj._id + "-progress")
|
||||||
|
@ -2909,4 +2913,5 @@ async function dbClean() {
|
||||||
itemCount +
|
itemCount +
|
||||||
" items removed from database. It's recommended you now restart the GUI."
|
" items removed from database. It's recommended you now restart the GUI."
|
||||||
);
|
);
|
||||||
|
ipcRenderer.send("request-update-dbclean-spinner");
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ var appDataFolder =
|
||||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||||
var configPath = path.join(configFolder, "config.json");
|
var configPath = path.join(configFolder, "config.json");
|
||||||
var config = require(configPath);
|
var config = require(configPath);
|
||||||
|
|
||||||
const contrib = [
|
const contrib = [
|
||||||
"DK5SM",
|
"DK5SM",
|
||||||
"DL4IAZ",
|
"DL4IAZ",
|
||||||
|
@ -343,9 +344,9 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.low_bandwidth_mode == "True") {
|
if (config.low_bandwidth_mode == "True") {
|
||||||
document.getElementById("500HzModeSwitch").checked = true;
|
document.getElementById("250HzModeSwitch").checked = true;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("500HzModeSwitch").checked = false;
|
document.getElementById("250HzModeSwitch").checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.high_graphics == "True") {
|
if (config.high_graphics == "True") {
|
||||||
|
@ -396,6 +397,21 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
document.getElementById("NotificationSwitch").checked = false;
|
document.getElementById("NotificationSwitch").checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(config.enable_mesh_features.toLowerCase() == "true"){
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||||
|
document.getElementById("liMeshTable").style.display = "block";
|
||||||
|
document.getElementById("enableMeshSwitch").checked = true;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||||
|
document.getElementById("liMeshTable").style.display = "none";
|
||||||
|
document.getElementById("enableMeshSwitch").checked = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// theme selector
|
// theme selector
|
||||||
changeGuiDesign(config.theme);
|
changeGuiDesign(config.theme);
|
||||||
|
|
||||||
|
@ -1142,8 +1158,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// enable 500z Switch clicked
|
// enable 500z Switch clicked
|
||||||
document.getElementById("500HzModeSwitch").addEventListener("click", () => {
|
document.getElementById("250HzModeSwitch").addEventListener("click", () => {
|
||||||
if (document.getElementById("500HzModeSwitch").checked == true) {
|
if (document.getElementById("250HzModeSwitch").checked == true) {
|
||||||
config.low_bandwidth_mode = "True";
|
config.low_bandwidth_mode = "True";
|
||||||
} else {
|
} else {
|
||||||
config.low_bandwidth_mode = "False";
|
config.low_bandwidth_mode = "False";
|
||||||
|
@ -1243,6 +1259,22 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
FD.saveConfig(config, configPath);
|
FD.saveConfig(config, configPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// enable MESH Switch clicked
|
||||||
|
document.getElementById("enableMeshSwitch").addEventListener("click", () => {
|
||||||
|
if (document.getElementById("enableMeshSwitch").checked == true) {
|
||||||
|
config.enable_mesh_features = "True";
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||||
|
document.getElementById("liMeshTable").style.display = "block";
|
||||||
|
} else {
|
||||||
|
config.enable_mesh_features = "False";
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||||
|
document.getElementById("liMeshTable").style.display = "none";
|
||||||
|
}
|
||||||
|
//fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
|
FD.saveConfig(config, configPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// enable is writing switch clicked
|
// enable is writing switch clicked
|
||||||
document.getElementById("enable_is_writing").addEventListener("click", () => {
|
document.getElementById("enable_is_writing").addEventListener("click", () => {
|
||||||
if (document.getElementById("enable_is_writing").checked == true) {
|
if (document.getElementById("enable_is_writing").checked == true) {
|
||||||
|
@ -1430,6 +1462,16 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
var handshake = document.getElementById("hamlib_handshake").value;
|
var handshake = document.getElementById("hamlib_handshake").value;
|
||||||
var tx_delay = document.getElementById("tx_delay").value;
|
var tx_delay = document.getElementById("tx_delay").value;
|
||||||
|
|
||||||
|
if (document.getElementById("enableMeshSwitch").checked == true) {
|
||||||
|
var enable_mesh_features = "True";
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "visible";
|
||||||
|
document.getElementById("liMeshTable").style.display = "block";
|
||||||
|
} else {
|
||||||
|
var enable_mesh_features = "False";
|
||||||
|
document.getElementById("liMeshTable").style.visibility = "hidden";
|
||||||
|
document.getElementById("liMeshTable").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
if (document.getElementById("scatterSwitch").checked == true) {
|
if (document.getElementById("scatterSwitch").checked == true) {
|
||||||
var enable_scatter = "True";
|
var enable_scatter = "True";
|
||||||
} else {
|
} else {
|
||||||
|
@ -1442,7 +1484,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
var enable_fft = "False";
|
var enable_fft = "False";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.getElementById("500HzModeSwitch").checked == true) {
|
if (document.getElementById("250HzModeSwitch").checked == true) {
|
||||||
var low_bandwidth_mode = "True";
|
var low_bandwidth_mode = "True";
|
||||||
} else {
|
} else {
|
||||||
var low_bandwidth_mode = "False";
|
var low_bandwidth_mode = "False";
|
||||||
|
@ -1515,7 +1557,6 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
var tx_audio_level = document.getElementById("audioLevelTX").value;
|
var tx_audio_level = document.getElementById("audioLevelTX").value;
|
||||||
var rx_buffer_size = document.getElementById("rx_buffer_size").value;
|
var rx_buffer_size = document.getElementById("rx_buffer_size").value;
|
||||||
|
|
||||||
config.radiocontrol = radiocontrol;
|
config.radiocontrol = radiocontrol;
|
||||||
config.mycall = callsign_ssid;
|
config.mycall = callsign_ssid;
|
||||||
config.mygrid = mygrid;
|
config.mygrid = mygrid;
|
||||||
|
@ -1544,6 +1585,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
config.tx_delay = tx_delay;
|
config.tx_delay = tx_delay;
|
||||||
config.tci_ip = tci_ip;
|
config.tci_ip = tci_ip;
|
||||||
config.tci_port = tci_port;
|
config.tci_port = tci_port;
|
||||||
|
config.enable_mesh_features = enable_mesh_features;
|
||||||
|
|
||||||
//fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
//fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
FD.saveConfig(config, configPath);
|
FD.saveConfig(config, configPath);
|
||||||
|
@ -1593,7 +1635,8 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
auto_tune,
|
auto_tune,
|
||||||
tx_delay,
|
tx_delay,
|
||||||
tci_ip,
|
tci_ip,
|
||||||
tci_port
|
tci_port,
|
||||||
|
enable_mesh_features
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1601,6 +1644,10 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
ipcRenderer.send("request-open-tnc-log");
|
ipcRenderer.send("request-open-tnc-log");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("meshtable").addEventListener("click", () => {
|
||||||
|
ipcRenderer.send("request-open-mesh-module");
|
||||||
|
});
|
||||||
|
|
||||||
// stopTNC button clicked
|
// stopTNC button clicked
|
||||||
document.getElementById("stopTNC").addEventListener("click", () => {
|
document.getElementById("stopTNC").addEventListener("click", () => {
|
||||||
if (!confirm("Stop the TNC?")) return;
|
if (!confirm("Stop the TNC?")) return;
|
||||||
|
@ -1610,6 +1657,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
// btnCleanDB button clicked
|
// btnCleanDB button clicked
|
||||||
document.getElementById("btnCleanDB").addEventListener("click", () => {
|
document.getElementById("btnCleanDB").addEventListener("click", () => {
|
||||||
|
document.getElementById("divCleanDBSpinner").classList.remove("invisible");
|
||||||
ipcRenderer.send("request-clean-db");
|
ipcRenderer.send("request-clean-db");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1755,8 +1803,15 @@ window.addEventListener("DOMContentLoaded", () => {
|
||||||
resetSortIcon();
|
resetSortIcon();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
autostart_rigctld();
|
autostart_rigctld();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
//End of domcontentloaded
|
||||||
|
|
||||||
function resetSortIcon() {
|
function resetSortIcon() {
|
||||||
document.getElementById("hslSort").remove();
|
document.getElementById("hslSort").remove();
|
||||||
|
@ -1801,6 +1856,10 @@ function connectedStation(data) {
|
||||||
prefix + data.dxcallsign;
|
prefix + data.dxcallsign;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Called by chat to turn off db clean spinner
|
||||||
|
ipcRenderer.on("action-update-dbclean-spinner",() =>{
|
||||||
|
document.getElementById("divCleanDBSpinner").classList.add("invisible");
|
||||||
|
});
|
||||||
//Listen for events caused by tnc 'tnc-message' rx
|
//Listen for events caused by tnc 'tnc-message' rx
|
||||||
ipcRenderer.on("action-update-reception-status", (event, arg) => {
|
ipcRenderer.on("action-update-reception-status", (event, arg) => {
|
||||||
var data = arg["data"][0];
|
var data = arg["data"][0];
|
||||||
|
@ -2938,6 +2997,7 @@ ipcRenderer.on("action-update-unread-messages-main", (event, data) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcRenderer.on("run-tnc-command", (event, arg) => {
|
ipcRenderer.on("run-tnc-command", (event, arg) => {
|
||||||
|
|
||||||
if (arg.command == "save_my_call") {
|
if (arg.command == "save_my_call") {
|
||||||
sock.saveMyCall(arg.callsign);
|
sock.saveMyCall(arg.callsign);
|
||||||
}
|
}
|
||||||
|
@ -3017,6 +3077,12 @@ ipcRenderer.on("run-tnc-command", (event, arg) => {
|
||||||
if (arg.command == "responseSharedFile") {
|
if (arg.command == "responseSharedFile") {
|
||||||
sock.sendResponseSharedFile(arg.dxcallsign, arg.file, arg.filedata);
|
sock.sendResponseSharedFile(arg.dxcallsign, arg.file, arg.filedata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg.command == "mesh_ping") {
|
||||||
|
sock.sendMeshPing(arg.dxcallsign);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// IPC ACTION FOR AUTO UPDATER
|
// IPC ACTION FOR AUTO UPDATER
|
||||||
|
|
188
gui/preload-mesh.js
Normal file
188
gui/preload-mesh.js
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
const path = require("path");
|
||||||
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/26227660
|
||||||
|
var appDataFolder =
|
||||||
|
process.env.APPDATA ||
|
||||||
|
(process.platform == "darwin"
|
||||||
|
? process.env.HOME + "/Library/Application Support"
|
||||||
|
: process.env.HOME + "/.config");
|
||||||
|
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||||
|
var configPath = path.join(configFolder, "config.json");
|
||||||
|
const config = require(configPath);
|
||||||
|
|
||||||
|
// WINDOW LISTENER
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
|
// startPing button clicked
|
||||||
|
document.getElementById("transmit_mesh_ping").addEventListener("click", () => {
|
||||||
|
var dxcallsign = document.getElementById("dxCallMesh").value.toUpperCase();
|
||||||
|
if (dxcallsign == "" || dxcallsign == null || dxcallsign == undefined)
|
||||||
|
return;
|
||||||
|
//pauseButton(document.getElementById("transmit_mesh_ping"), 2000);
|
||||||
|
ipcRenderer.send("run-tnc-command", {
|
||||||
|
command: "mesh_ping",
|
||||||
|
dxcallsign: dxcallsign,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
ipcRenderer.on("action-update-mesh-table", (event, arg) => {
|
||||||
|
var routes = arg.routing_table;
|
||||||
|
|
||||||
|
if (typeof routes == "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var tbl = document.getElementById("mesh-table");
|
||||||
|
if (tbl !== null) {
|
||||||
|
tbl.innerHTML = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < routes.length; i++) {
|
||||||
|
|
||||||
|
var row = document.createElement("tr");
|
||||||
|
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
|
||||||
|
navigator.language,{
|
||||||
|
hourCycle: 'h23',
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var timestamp = document.createElement("td");
|
||||||
|
var timestampText = document.createElement("span");
|
||||||
|
timestampText.innerText = datetime;
|
||||||
|
timestamp.appendChild(timestampText);
|
||||||
|
|
||||||
|
var dxcall = document.createElement("td");
|
||||||
|
var dxcallText = document.createElement("span");
|
||||||
|
dxcallText.innerText = routes[i]["dxcall"];
|
||||||
|
dxcall.appendChild(dxcallText);
|
||||||
|
|
||||||
|
var router = document.createElement("td");
|
||||||
|
var routerText = document.createElement("span");
|
||||||
|
routerText.innerText = routes[i]["router"];
|
||||||
|
router.appendChild(routerText);
|
||||||
|
|
||||||
|
var hops = document.createElement("td");
|
||||||
|
var hopsText = document.createElement("span");
|
||||||
|
hopsText.innerText = routes[i]["hops"];
|
||||||
|
hops.appendChild(hopsText);
|
||||||
|
|
||||||
|
var score = document.createElement("td");
|
||||||
|
var scoreText = document.createElement("span");
|
||||||
|
scoreText.innerText = routes[i]["score"];
|
||||||
|
score.appendChild(scoreText);
|
||||||
|
|
||||||
|
var snr = document.createElement("td");
|
||||||
|
var snrText = document.createElement("span");
|
||||||
|
snrText.innerText = routes[i]["snr"];
|
||||||
|
snr.appendChild(snrText);
|
||||||
|
|
||||||
|
row.appendChild(timestamp);
|
||||||
|
row.appendChild(dxcall);
|
||||||
|
row.appendChild(router);
|
||||||
|
row.appendChild(hops);
|
||||||
|
row.appendChild(score);
|
||||||
|
row.appendChild(snr);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tbl.appendChild(row);
|
||||||
|
}
|
||||||
|
/*-------------------------------------------*/
|
||||||
|
var routes = arg.mesh_signalling_table;
|
||||||
|
|
||||||
|
console.log(routes)
|
||||||
|
if (typeof routes == "undefined") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var tbl = document.getElementById("mesh-signalling-table");
|
||||||
|
if (tbl !== null) {
|
||||||
|
tbl.innerHTML = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < routes.length; i++) {
|
||||||
|
|
||||||
|
var row = document.createElement("tr");
|
||||||
|
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
|
||||||
|
navigator.language,{
|
||||||
|
hourCycle: 'h23',
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var timestamp = document.createElement("td");
|
||||||
|
var timestampText = document.createElement("span");
|
||||||
|
timestampText.innerText = datetime;
|
||||||
|
timestamp.appendChild(timestampText);
|
||||||
|
|
||||||
|
var destination = document.createElement("td");
|
||||||
|
var destinationText = document.createElement("span");
|
||||||
|
destinationText.innerText = routes[i]["destination"];
|
||||||
|
destination.appendChild(destinationText);
|
||||||
|
|
||||||
|
var router = document.createElement("td");
|
||||||
|
var routerText = document.createElement("span");
|
||||||
|
routerText.innerText = routes[i]["router"];
|
||||||
|
router.appendChild(routerText);
|
||||||
|
|
||||||
|
var frametype = document.createElement("td");
|
||||||
|
var frametypeText = document.createElement("span");
|
||||||
|
frametypeText.innerText = routes[i]["frametype"];
|
||||||
|
frametype.appendChild(frametypeText);
|
||||||
|
|
||||||
|
var payload = document.createElement("td");
|
||||||
|
var payloadText = document.createElement("span");
|
||||||
|
payloadText.innerText = routes[i]["payload"];
|
||||||
|
payload.appendChild(payloadText);
|
||||||
|
|
||||||
|
var attempt = document.createElement("td");
|
||||||
|
var attemptText = document.createElement("span");
|
||||||
|
attemptText.innerText = routes[i]["attempt"];
|
||||||
|
attempt.appendChild(attemptText);
|
||||||
|
|
||||||
|
var status = document.createElement("td");
|
||||||
|
var statusText = document.createElement("span");
|
||||||
|
statusText.innerText = routes[i]["status"];
|
||||||
|
status.appendChild(statusText);
|
||||||
|
|
||||||
|
|
||||||
|
row.appendChild(timestamp);
|
||||||
|
row.appendChild(destination);
|
||||||
|
row.appendChild(router);
|
||||||
|
row.appendChild(frametype);
|
||||||
|
row.appendChild(payload);
|
||||||
|
row.appendChild(attempt);
|
||||||
|
row.appendChild(status);
|
||||||
|
|
||||||
|
tbl.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (tbl !== null) {
|
||||||
|
|
||||||
|
// scroll to bottom of page
|
||||||
|
// https://stackoverflow.com/a/11715670
|
||||||
|
//window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
}
|
||||||
|
});
|
13
gui/sock.js
13
gui/sock.js
|
@ -218,6 +218,8 @@ client.on("data", function (socketdata) {
|
||||||
total_bytes: data["total_bytes"],
|
total_bytes: data["total_bytes"],
|
||||||
arq_transmission_percent: data["arq_transmission_percent"],
|
arq_transmission_percent: data["arq_transmission_percent"],
|
||||||
stations: data["stations"],
|
stations: data["stations"],
|
||||||
|
routing_table: data["routing_table"],
|
||||||
|
mesh_signalling_table: data["mesh_signalling_table"],
|
||||||
beacon_state: data["beacon_state"],
|
beacon_state: data["beacon_state"],
|
||||||
hamlib_status: data["hamlib_status"],
|
hamlib_status: data["hamlib_status"],
|
||||||
listen: data["listen"],
|
listen: data["listen"],
|
||||||
|
@ -586,6 +588,15 @@ exports.sendPing = function (dxcallsign) {
|
||||||
writeTncCommand(command);
|
writeTncCommand(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Send Mesh Ping
|
||||||
|
exports.sendMeshPing = function (dxcallsign) {
|
||||||
|
command =
|
||||||
|
'{"type" : "mesh", "command" : "ping", "dxcallsign" : "' +
|
||||||
|
dxcallsign +
|
||||||
|
'"}';
|
||||||
|
writeTncCommand(command);
|
||||||
|
};
|
||||||
|
|
||||||
// Send CQ
|
// Send CQ
|
||||||
exports.sendCQ = function () {
|
exports.sendCQ = function () {
|
||||||
command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
||||||
|
@ -851,6 +862,8 @@ exports.sendFecIsWriting = function (mycallsign) {
|
||||||
writeTncCommand(command);
|
writeTncCommand(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// SEND FEC TO BROADCASTCHANNEL
|
// SEND FEC TO BROADCASTCHANNEL
|
||||||
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
|
exports.sendBroadcastChannel = function (channel, data_out, uuid) {
|
||||||
let checksum = "";
|
let checksum = "";
|
||||||
|
|
|
@ -215,10 +215,18 @@
|
||||||
Settings
|
Settings
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
<button class="dropdown-item" id="tncLog" type="button">
|
<button class="dropdown-item" id="tncLog" type="button">
|
||||||
<i class="bi bi-card-text me-2"></i>
|
<i class="bi bi-card-text me-2"></i>
|
||||||
TNC Live Logs
|
TNC Live Logs
|
||||||
</button>
|
</button>
|
||||||
|
</li>
|
||||||
|
<li id="liMeshTable">
|
||||||
|
<button class="dropdown-item" id="meshtable" type="button">
|
||||||
|
<i class="bi bi-rocket-takeoff me-2"></i>
|
||||||
|
Mesh Table Prototype
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
|
@ -3663,17 +3671,17 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group input-group-sm mb-1">
|
<div class="input-group input-group-sm mb-1">
|
||||||
<label class="input-group-text w-50"
|
<label class="input-group-text w-50"
|
||||||
>Enable 563Hz only mode</label
|
>Enable 250Hz only mode</label
|
||||||
>
|
>
|
||||||
<label class="input-group-text w-50">
|
<label class="input-group-text w-50">
|
||||||
<div class="form-check form-switch form-check-inline">
|
<div class="form-check form-switch form-check-inline">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id="500HzModeSwitch"
|
id="250HzModeSwitch"
|
||||||
/>
|
/>
|
||||||
<label class="form-check-label" for="500HzModeSwitch"
|
<label class="form-check-label" for="250HzModeSwitch"
|
||||||
>563Hz</label
|
>250Hz</label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
@ -3796,6 +3804,21 @@
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="input-group input-group-sm mb-1">
|
||||||
|
<label class="input-group-text w-50">Enable MESH protocol</label>
|
||||||
|
<label class="input-group-text w-50">
|
||||||
|
<div class="form-check form-switch form-check-inline ms-2">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="enableMeshSwitch"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="enableMeshSwitch"
|
||||||
|
>experimental! REALLY!</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="input-group input-group-sm mb-1">
|
<div class="input-group input-group-sm mb-1">
|
||||||
<label class="input-group-text w-50"
|
<label class="input-group-text w-50"
|
||||||
>Database maintenance</label
|
>Database maintenance</label
|
||||||
|
@ -3807,7 +3830,8 @@
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Clean
|
Clean
|
||||||
</button>
|
</button>
|
||||||
|
<div class="spinner-border text-warning invisible" role="status" id="divCleanDBSpinner"></div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
|
|
95
gui/src/mesh-module.html
Normal file
95
gui/src/mesh-module.html
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self';" />
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="../node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||||
|
/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||||
|
<title>FreeDATA - Mesh Table</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- bootstrap -->
|
||||||
|
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<!-- chart.js -->
|
||||||
|
|
||||||
|
<nav class="navbar fixed-top bg-light ms-2 me-2">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
style="max-width: 6rem; text-transform: uppercase"
|
||||||
|
placeholder="DXcall"
|
||||||
|
pattern="[A-Z]*"
|
||||||
|
id="dxCallMesh"
|
||||||
|
maxlength="11"
|
||||||
|
aria-label="Input group"
|
||||||
|
aria-describedby="btnGroupAddon"
|
||||||
|
/>
|
||||||
|
<button id="transmit_mesh_ping" type="button" class="btn btn-primary">mesh ping</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid h-100 mt-5">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="tableFixHead h-50">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Timestamp</th>
|
||||||
|
<th scope="col">DXCall</th>
|
||||||
|
<th scope="col">Router</th>
|
||||||
|
<th scope="col">Hops</th>
|
||||||
|
<th scope="col">Score</th>
|
||||||
|
<th scope="col">SNR</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="mesh-table">
|
||||||
|
<!-- ... -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="tableFixHead h-50">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Timestamp</th>
|
||||||
|
<th scope="col">Destination</th>
|
||||||
|
<th scope="col">Router</th>
|
||||||
|
<th scope="col">Frametype</th>
|
||||||
|
<th scope="col">Payload</th>
|
||||||
|
<th scope="col">Attempt</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="mesh-signalling-table">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
tnc/.gitignore
vendored
3
tnc/.gitignore
vendored
|
@ -127,3 +127,6 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# FreeDATA config
|
||||||
|
config.ini
|
|
@ -84,6 +84,10 @@ class CONFIG:
|
||||||
'port': data[23]
|
'port': data[23]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.config['MESH'] = {'#TCI settings': None,
|
||||||
|
'enable_protocol': data[24]
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.config_name, 'w') as configfile:
|
with open(self.config_name, 'w') as configfile:
|
||||||
self.config.write(configfile)
|
self.config.write(configfile)
|
||||||
|
|
|
@ -286,8 +286,11 @@ class DAEMON:
|
||||||
options.append("--tx-delay")
|
options.append("--tx-delay")
|
||||||
options.append(data[21])
|
options.append(data[21])
|
||||||
|
|
||||||
|
#Mesh
|
||||||
|
print(data[24])
|
||||||
|
if data[24] == "True":
|
||||||
|
options.append("--mesh")
|
||||||
|
print(options)
|
||||||
# safe data to config file
|
# safe data to config file
|
||||||
config.write_entire_config(data)
|
config.write_entire_config(data)
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import structlog
|
||||||
import stats
|
import stats
|
||||||
import ujson as json
|
import ujson as json
|
||||||
from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS
|
from codec2 import FREEDV_MODE, FREEDV_MODE_USED_SLOTS
|
||||||
from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER
|
from queues import DATA_QUEUE_RECEIVED, DATA_QUEUE_TRANSMIT, RX_BUFFER, MESH_RECEIVED_QUEUE
|
||||||
from static import FRAME_TYPE as FR_TYPE
|
from static import FRAME_TYPE as FR_TYPE
|
||||||
import broadcast
|
import broadcast
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ import time
|
||||||
from datetime import datetime,timezone
|
from datetime import datetime,timezone
|
||||||
import crcengine
|
import crcengine
|
||||||
import static
|
import static
|
||||||
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
|
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam
|
||||||
import structlog
|
import structlog
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import threading
|
import threading
|
||||||
|
import mesh
|
||||||
|
|
||||||
log = structlog.get_logger("helpers")
|
log = structlog.get_logger("helpers")
|
||||||
|
|
||||||
|
|
19
tnc/main.py
19
tnc/main.py
|
@ -29,11 +29,11 @@ import helpers
|
||||||
import log_handler
|
import log_handler
|
||||||
import modem
|
import modem
|
||||||
import static
|
import static
|
||||||
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
|
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam
|
||||||
import structlog
|
import structlog
|
||||||
import explorer
|
import explorer
|
||||||
import json
|
import json
|
||||||
|
import mesh
|
||||||
log = structlog.get_logger("main")
|
log = structlog.get_logger("main")
|
||||||
|
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
|
@ -246,6 +246,13 @@ if __name__ == "__main__":
|
||||||
type=int,
|
type=int,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PARSER.add_argument(
|
||||||
|
"--mesh",
|
||||||
|
dest="enable_mesh",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable mesh protocol",
|
||||||
|
)
|
||||||
|
|
||||||
ARGS = PARSER.parse_args()
|
ARGS = PARSER.parse_args()
|
||||||
|
|
||||||
# set save to folder state for allowing downloading files to local file system
|
# set save to folder state for allowing downloading files to local file system
|
||||||
|
@ -299,6 +306,7 @@ if __name__ == "__main__":
|
||||||
TCIParam.ip = ARGS.tci_ip
|
TCIParam.ip = ARGS.tci_ip
|
||||||
TCIParam.port = ARGS.tci_port
|
TCIParam.port = ARGS.tci_port
|
||||||
ModemParam.tx_delay = ARGS.tx_delay
|
ModemParam.tx_delay = ARGS.tx_delay
|
||||||
|
MeshParam.enable_protocol = ARGS.enable_mesh
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("[DMN] Error reading config file", exception=e)
|
log.error("[DMN] Error reading config file", exception=e)
|
||||||
|
@ -349,6 +357,7 @@ if __name__ == "__main__":
|
||||||
TCIParam.ip = str(conf.get('TCI', 'tci_ip', 'localhost'))
|
TCIParam.ip = str(conf.get('TCI', 'tci_ip', 'localhost'))
|
||||||
TCIParam.port = int(conf.get('TCI', 'tci_port', '50001'))
|
TCIParam.port = int(conf.get('TCI', 'tci_port', '50001'))
|
||||||
ModemParam.tx_delay = int(conf.get('TNC', 'tx_delay', '0'))
|
ModemParam.tx_delay = int(conf.get('TNC', 'tx_delay', '0'))
|
||||||
|
MeshParam.enable_protocol = conf.get('MESH','mesh_enable','False')
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
log.warning("[CFG] Error reading config file near", key=str(e))
|
log.warning("[CFG] Error reading config file near", key=str(e))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -395,6 +404,12 @@ if __name__ == "__main__":
|
||||||
# start modem
|
# start modem
|
||||||
modem = modem.RF()
|
modem = modem.RF()
|
||||||
|
|
||||||
|
# start mesh protocol only if enabled
|
||||||
|
if MeshParam.enable_protocol:
|
||||||
|
log.info("[MESH] loading module")
|
||||||
|
# start mesh module
|
||||||
|
mesh = mesh.MeshRouter()
|
||||||
|
|
||||||
# optionally start explorer module
|
# optionally start explorer module
|
||||||
if TNC.enable_explorer:
|
if TNC.enable_explorer:
|
||||||
log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=TNC.enable_explorer)
|
log.info("[EXPLORER] Publishing to https://explorer.freedata.app", state=TNC.enable_explorer)
|
||||||
|
|
554
tnc/mesh.py
Normal file
554
tnc/mesh.py
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
"""
|
||||||
|
|
||||||
|
@author: DJ2LS
|
||||||
|
|
||||||
|
HF mesh networking prototype and testing module
|
||||||
|
|
||||||
|
import time
|
||||||
|
MeshParam.routing_table = [['AA1AA', 'direct', 0, 1.0, 25, time.time(), ], ['AA1AA', 'AA2BB', 1, 3.1, 10, time.time(), ],
|
||||||
|
['AA3CC', 'AA2BB', 5, -4.5, -3, time.time(), ]]
|
||||||
|
|
||||||
|
print(MeshParam.routing_table)
|
||||||
|
print("---------------------------------")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TODO: SIGNALLING FOR ACK/NACK:
|
||||||
|
- mesh-signalling burst is datac13
|
||||||
|
- mesh-signalling frame contains [message id, status, hops, score, payload]
|
||||||
|
- frame type is 1 byte
|
||||||
|
- message id is 32bit crc --> 4bytes
|
||||||
|
- status can be ACK, NACK, PING, PINGACK --> 1byte
|
||||||
|
- payload = 14bytes - 8 bytes = 6bytes
|
||||||
|
- create a list for signalling frames, contains [message id, message-status, attempts, state, timestamp]
|
||||||
|
- on "IRS", send ACK/NACK 10 times on receiving beacon?
|
||||||
|
- on "ROUTER", receive ACK/NACK, and store it in table, also send it 10 times
|
||||||
|
- if sent 10 times, set ACK/NACK state to "done"
|
||||||
|
- if done already in list, don't reset retry counter
|
||||||
|
- delete ACK/NACK if "done" and timestamp older than 1day
|
||||||
|
|
||||||
|
TODO: SCORE CALCULATION:
|
||||||
|
SNR: negative --> * 2
|
||||||
|
|
||||||
|
"""
|
||||||
|
# pylint: disable=invalid-name, line-too-long, c-extension-no-member
|
||||||
|
# pylint: disable=import-outside-toplevel, attribute-defined-outside-init
|
||||||
|
|
||||||
|
from static import TNC, MeshParam, FRAME_TYPE, Station, ModemParam, ARQ
|
||||||
|
from codec2 import FREEDV_MODE
|
||||||
|
import numpy as np
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import modem
|
||||||
|
import helpers
|
||||||
|
import structlog
|
||||||
|
import ujson as json
|
||||||
|
|
||||||
|
from queues import MESH_RECEIVED_QUEUE, MESH_QUEUE_TRANSMIT, MESH_SIGNALLING_TABLE
|
||||||
|
|
||||||
|
class MeshRouter():
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
self.log = structlog.get_logger("RF")
|
||||||
|
|
||||||
|
self.transmission_time_list = [
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360,
|
||||||
|
30, 30, 30, 60, 60, 60, 60, 60, 60, 60, 60, 60, 120, 120, 120, 120, 120, 120, 180, 180, 180, 180, 180, 180,
|
||||||
|
360, 360, 360, 360, 360, 360
|
||||||
|
]
|
||||||
|
# for testing only: self.transmission_time_list = [30, 30]
|
||||||
|
self.signalling_max_attempts = len(self.transmission_time_list)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.mesh_broadcasting_thread = threading.Thread(
|
||||||
|
target=self.broadcast_routing_table, name="worker thread receive", daemon=True
|
||||||
|
)
|
||||||
|
self.mesh_broadcasting_thread.start()
|
||||||
|
|
||||||
|
self.mesh_rx_dispatcher_thread = threading.Thread(
|
||||||
|
target=self.mesh_rx_dispatcher, name="worker thread receive", daemon=True
|
||||||
|
)
|
||||||
|
self.mesh_rx_dispatcher_thread.start()
|
||||||
|
|
||||||
|
self.mesh_tx_dispatcher_thread = threading.Thread(
|
||||||
|
target=self.mesh_tx_dispatcher, name="worker thread receive", daemon=True
|
||||||
|
)
|
||||||
|
self.mesh_tx_dispatcher_thread.start()
|
||||||
|
|
||||||
|
self.mesh_signalling_dispatcher_thread = threading.Thread(
|
||||||
|
target=self.mesh_signalling_dispatcher, name="worker thread receive", daemon=True
|
||||||
|
)
|
||||||
|
self.mesh_signalling_dispatcher_thread.start()
|
||||||
|
|
||||||
|
def get_from_heard_stations(self):
|
||||||
|
"""
|
||||||
|
get data from heard stations
|
||||||
|
heard stations format:
|
||||||
|
[dxcallsign,dxgrid,int(time.time()),datatype,snr,offset,frequency]
|
||||||
|
|
||||||
|
TNC.heard_stations.append(
|
||||||
|
[
|
||||||
|
dxcallsign,
|
||||||
|
dxgrid,
|
||||||
|
int(time.time()),
|
||||||
|
datatype,
|
||||||
|
snr,
|
||||||
|
offset,
|
||||||
|
frequency,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
dxcallsign_position = 0
|
||||||
|
dxgrid_position = 1
|
||||||
|
timestamp_position = 2
|
||||||
|
type_position = 3
|
||||||
|
snr_position = 4
|
||||||
|
offset_position = 5
|
||||||
|
frequency_position = 6
|
||||||
|
|
||||||
|
try:
|
||||||
|
for item in TNC.heard_stations:
|
||||||
|
#print("-----------")
|
||||||
|
#print(item)
|
||||||
|
#print(item[snr_position])
|
||||||
|
try:
|
||||||
|
#print(item[snr_position])
|
||||||
|
snr = bytes(item[snr_position], "utf-8").split(b"/")
|
||||||
|
snr = int(float(snr[0]))
|
||||||
|
except Exception as err:
|
||||||
|
snr = int(float(item[snr_position]))
|
||||||
|
|
||||||
|
new_router = [helpers.get_crc_24(item[dxcallsign_position]), helpers.get_crc_24(b'direct'), 0, snr, self.calculate_score_by_snr(snr), item[timestamp_position]]
|
||||||
|
self.add_router_to_routing_table(new_router)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning("[MESH] error fetching data from heard station list", e=e)
|
||||||
|
|
||||||
|
def add_router_to_routing_table(self, new_router):
|
||||||
|
try:
|
||||||
|
# destination callsign # router callsign # hops # rx snr # route quality # timestamp
|
||||||
|
for _, item in enumerate(MeshParam.routing_table):
|
||||||
|
# update routing entry if exists
|
||||||
|
if new_router[0] in item[0] and new_router[1] in item[1]:
|
||||||
|
#print(f"UPDATE {MeshParam.routing_table[_]} >>> {new_router}")
|
||||||
|
self.log.info(f"[MESH] [ROUTING TABLE] [UPDATE]: {MeshParam.routing_table[_]} >>> ",
|
||||||
|
update=new_router)
|
||||||
|
|
||||||
|
MeshParam.routing_table[_] = new_router
|
||||||
|
|
||||||
|
# add new routing entry if not exists
|
||||||
|
if new_router not in MeshParam.routing_table:
|
||||||
|
#print(f"INSERT {new_router} >>> ROUTING TABLE")
|
||||||
|
self.log.info("[MESH] [ROUTING TABLE] [INSERT]:", insert=new_router)
|
||||||
|
|
||||||
|
MeshParam.routing_table.append(new_router)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning("[MESH] error adding data to routing table", e=e, router=new_router)
|
||||||
|
|
||||||
|
def broadcast_routing_table(self, interval=600):
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# always enable receiving for datac4 if broadcasting
|
||||||
|
modem.RECEIVE_DATAC4 = True
|
||||||
|
|
||||||
|
threading.Event().wait(1)
|
||||||
|
if not ARQ.arq_state and not ModemParam.channel_busy:
|
||||||
|
try:
|
||||||
|
|
||||||
|
# wait some time until sending routing table
|
||||||
|
threading.Event().wait(interval)
|
||||||
|
|
||||||
|
# before we are transmitting, let us update our routing table
|
||||||
|
self.get_from_heard_stations()
|
||||||
|
|
||||||
|
#[b'DJ2LS-0', 'direct', 0, 9.6, 9.6, 1684912305]
|
||||||
|
mesh_broadcast_frame_header = bytearray(4)
|
||||||
|
mesh_broadcast_frame_header[:1] = bytes([FRAME_TYPE.MESH_BROADCAST.value])
|
||||||
|
mesh_broadcast_frame_header[1:4] = helpers.get_crc_24(Station.mycallsign)
|
||||||
|
|
||||||
|
# callsign(6), router(6), hops(1), path_score(1) == 14 ==> 14 28 42 ==> 3 mesh routing entries
|
||||||
|
# callsign_crc(3), router_crc(3), hops(1), path_score(1) == 8 --> 6
|
||||||
|
# callsign_crc(3), hops(1), path_score(1) == 5 --> 10
|
||||||
|
|
||||||
|
# Create a new bytearray with a fixed length of 50
|
||||||
|
result = bytearray(50)
|
||||||
|
|
||||||
|
# Iterate over the route subarrays and add the selected entries to the result bytearray
|
||||||
|
index = 0
|
||||||
|
for route_id, route in enumerate(MeshParam.routing_table):
|
||||||
|
# the value 5 is the length of crc24 + hops + score
|
||||||
|
|
||||||
|
dxcall = MeshParam.routing_table[route_id][0]
|
||||||
|
# router = MeshParam.routing_table[i][1]
|
||||||
|
hops = MeshParam.routing_table[route_id][2]
|
||||||
|
# snr = MeshParam.routing_table[i][3]
|
||||||
|
route_score = np.clip(MeshParam.routing_table[route_id][4], 0, 254)
|
||||||
|
# timestamp = MeshParam.routing_table[i][5]
|
||||||
|
result[index:index + 5] = dxcall + bytes([hops]) + bytes([route_score])
|
||||||
|
index += 5
|
||||||
|
|
||||||
|
# Split the result bytearray into a list of fixed-length bytearrays
|
||||||
|
split_result = [result[i:i + 50] for i in range(0, len(result), 50)]
|
||||||
|
frame_list = []
|
||||||
|
for _ in split_result:
|
||||||
|
# make sure payload is always 50
|
||||||
|
_[len(_):] = bytes(50 - len(_))
|
||||||
|
#print(len(_))
|
||||||
|
frame_list.append(mesh_broadcast_frame_header + _)
|
||||||
|
|
||||||
|
TNC.transmitting = True
|
||||||
|
c2_mode = FREEDV_MODE.datac4.value
|
||||||
|
self.log.info("[MESH] broadcasting routing table", frame_list=frame_list, frames=len(split_result))
|
||||||
|
modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, 1, 0, frame_list])
|
||||||
|
|
||||||
|
# Wait while transmitting
|
||||||
|
while TNC.transmitting:
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning("[MESH] broadcasting routing table", e=e)
|
||||||
|
|
||||||
|
|
||||||
|
def mesh_rx_dispatcher(self):
|
||||||
|
while True:
|
||||||
|
data_in = MESH_RECEIVED_QUEUE.get()
|
||||||
|
if int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_BROADCAST.value]:
|
||||||
|
self.received_routing_table(data_in[:-2])
|
||||||
|
elif int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_SIGNALLING_PING.value]:
|
||||||
|
self.received_mesh_ping(data_in[:-2])
|
||||||
|
elif int.from_bytes(data_in[:1], "big") in [FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value]:
|
||||||
|
self.received_mesh_ping_ack(data_in[:-2])
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("wrong mesh data received")
|
||||||
|
#print(data_in)
|
||||||
|
|
||||||
|
def mesh_tx_dispatcher(self):
|
||||||
|
while True:
|
||||||
|
data = MESH_QUEUE_TRANSMIT.get()
|
||||||
|
#print(data)
|
||||||
|
if data[0] == "PING":
|
||||||
|
self.add_mesh_ping_to_signalling_table(helpers.get_crc_24(data[2]).hex(), status="awaiting_ack")
|
||||||
|
else:
|
||||||
|
print("wrong mesh command")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def mesh_signalling_dispatcher(self):
|
||||||
|
# [timestamp, destination, router, frametype, payload, attempt, status]
|
||||||
|
# --------------0------------1---------2---------3--------4---------5--------6 #
|
||||||
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
threading.Event().wait(1.0)
|
||||||
|
for entry in MESH_SIGNALLING_TABLE:
|
||||||
|
#print(entry)
|
||||||
|
attempt = entry[5]
|
||||||
|
status = entry[6]
|
||||||
|
# check for PING cases
|
||||||
|
if entry[3] in ["PING", "PING-ACK"] and attempt < len(self.transmission_time_list) and status not in ["acknowledged"]:
|
||||||
|
|
||||||
|
|
||||||
|
# Calculate the transmission time with exponential increase
|
||||||
|
#transmission_time = timestamp + (2 ** attempt) * 10
|
||||||
|
# Calculate transmission times for attempts 0 to 30 with stronger S-curves in minutes
|
||||||
|
#correction_factor = 750
|
||||||
|
timestamp = entry[0]
|
||||||
|
#transmission_time = timestamp + (4.5 / (1 + np.exp(-1. * (attempt - 5)))) * correction_factor * attempt
|
||||||
|
transmission_time = timestamp + sum(self.transmission_time_list[:attempt])
|
||||||
|
# check if it is time to transmit
|
||||||
|
if time.time() >= transmission_time:
|
||||||
|
entry[5] += 1
|
||||||
|
self.log.info("[MESH] [TX] Ping", destination=entry[1])
|
||||||
|
channel_busy_timeout = time.time() + 5
|
||||||
|
while ModemParam.channel_busy and time.time() < channel_busy_timeout:
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
self.transmit_mesh_signalling_ping(bytes.fromhex(entry[1]))
|
||||||
|
#print("...")
|
||||||
|
|
||||||
|
def received_routing_table(self, data_in):
|
||||||
|
try:
|
||||||
|
print("data received........")
|
||||||
|
print(data_in)
|
||||||
|
|
||||||
|
router = data_in[1:4] # Extract the first 4 bytes (header)
|
||||||
|
payload = data_in[4:] # Extract the payload (excluding the header)
|
||||||
|
|
||||||
|
print("Router:", router) # Output the header bytes
|
||||||
|
|
||||||
|
for i in range(0, len(payload)-1, 5):
|
||||||
|
callsign_checksum = payload[i:i + 3] # First 3 bytes of the information (callsign_checksum)
|
||||||
|
hops = int.from_bytes(payload[i+3:i + 4], "big") # Fourth byte of the information (hops)
|
||||||
|
score = int.from_bytes(payload[i+4:i + 5], "big") # Fifth byte of the information (score)
|
||||||
|
snr = int(ModemParam.snr)
|
||||||
|
score = self.calculate_new_avg_score(score, self.calculate_score_by_snr(snr))
|
||||||
|
timestamp = int(time.time())
|
||||||
|
|
||||||
|
# use case 1: add new router to table only if callsign not empty
|
||||||
|
_use_case1 = callsign_checksum.startswith(b'\x00')
|
||||||
|
|
||||||
|
# use case 2: add new router to table only if not own callsign
|
||||||
|
_use_case2 = callsign_checksum not in [helpers.get_crc_24(Station.mycallsign)]
|
||||||
|
|
||||||
|
# use case 3: increment hop if router not direct
|
||||||
|
if router not in [helpers.get_crc_24(b'direct')] and hops == 0:
|
||||||
|
hops += 1
|
||||||
|
|
||||||
|
# use case 4: if callsign is directly available skip route for only keeping shortest way in db
|
||||||
|
_use_case4 = False
|
||||||
|
for _, call in enumerate(MeshParam.routing_table):
|
||||||
|
# check if callsign already in routing table and is direct connection
|
||||||
|
if callsign_checksum in [MeshParam.routing_table[_][0]] and MeshParam.routing_table[_][1] in [helpers.get_crc_24(b'direct')]:
|
||||||
|
_use_case4 = True
|
||||||
|
|
||||||
|
# use case N: calculate score
|
||||||
|
# TODO...
|
||||||
|
|
||||||
|
if not _use_case1 \
|
||||||
|
and _use_case2\
|
||||||
|
and not _use_case4:
|
||||||
|
|
||||||
|
print("Callsign Checksum:", callsign_checksum)
|
||||||
|
print("Hops:", hops)
|
||||||
|
print("Score:", score)
|
||||||
|
|
||||||
|
new_router = [callsign_checksum, router, hops, snr, score, timestamp]
|
||||||
|
print(new_router)
|
||||||
|
self.add_router_to_routing_table(new_router)
|
||||||
|
|
||||||
|
print("-------------------------")
|
||||||
|
for _, item in enumerate(MeshParam.routing_table):
|
||||||
|
print(MeshParam.routing_table[_])
|
||||||
|
print("-------------------------")
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning("[MESH] error processing received routing broadcast", e=e)
|
||||||
|
|
||||||
|
def calculate_score_by_snr(self, snr):
|
||||||
|
if snr < -12 or snr > 12:
|
||||||
|
raise ValueError("Value must be in the range of -12 to 12")
|
||||||
|
|
||||||
|
score = (snr + 12) * 100 / 24 # Scale the value to the range [0, 100]
|
||||||
|
if score < 0:
|
||||||
|
score = 0 # Ensure the score is not less than 0
|
||||||
|
elif score > 100:
|
||||||
|
score = 100 # Ensure the score is not greater than 100
|
||||||
|
|
||||||
|
return int(score)
|
||||||
|
|
||||||
|
def calculate_new_avg_score(self, value_old, value):
|
||||||
|
return int((value_old + value) / 2)
|
||||||
|
|
||||||
|
def received_mesh_ping(self, data_in):
|
||||||
|
destination = data_in[1:4].hex()
|
||||||
|
if destination == Station.mycallsign_crc.hex():
|
||||||
|
self.log.info("[MESH] [RX] [PING] [REQ]", destination=destination, mycall=Station.mycallsign_crc.hex())
|
||||||
|
# use case 1: set status to acknowleding if we are the receiver of a PING
|
||||||
|
self.add_mesh_ping_to_signalling_table(destination, status="acknowledging")
|
||||||
|
|
||||||
|
channel_busy_timeout = time.time() + 5
|
||||||
|
while ModemParam.channel_busy and time.time() < channel_busy_timeout:
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
|
||||||
|
dxcallsign_crc = Station.mycallsign_crc
|
||||||
|
self.transmit_mesh_signalling_ping_ack(dxcallsign_crc)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.log.info("[MESH] [RX] [PING] [REQ]", destination=destination, mycall=Station.mycallsign_crc.hex())
|
||||||
|
# lookup if entry is already in database - if so, udpate and exit
|
||||||
|
for item in MESH_SIGNALLING_TABLE:
|
||||||
|
if item[1] == destination and item[5] >= self.signalling_max_attempts:
|
||||||
|
# use case 2: set status to forwarded if we are not the receiver of a PING and out of retries
|
||||||
|
self.add_mesh_ping_to_signalling_table(destination, status="forwarded")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("......................")
|
||||||
|
# use case 1: set status to forwarding if we are not the receiver of a PING and we don't have an entry in our database
|
||||||
|
self.add_mesh_ping_to_signalling_table(destination, status="forwarding")
|
||||||
|
|
||||||
|
def received_mesh_ping_ack(self, data_in):
|
||||||
|
# TODO:
|
||||||
|
# Check if we have a ping callsign already in signalling table
|
||||||
|
# if PING, then override and make it a PING-ACK
|
||||||
|
# if not, then add to table
|
||||||
|
|
||||||
|
destination = data_in[1:4].hex()
|
||||||
|
timestamp = time.time()
|
||||||
|
router = ""
|
||||||
|
frametype = "PING-ACK"
|
||||||
|
payload = ""
|
||||||
|
attempt = 0
|
||||||
|
|
||||||
|
|
||||||
|
if destination == Station.mycallsign_crc.hex():
|
||||||
|
self.log.info("[MESH] [RX] [PING] [ACK]", destination=destination, mycall=Station.mycallsign_crc.hex())
|
||||||
|
self.add_mesh_ping_ack_to_signalling_table(destination, status="acknowledged")
|
||||||
|
else:
|
||||||
|
#status = "forwarding"
|
||||||
|
#self.add_mesh_ping_ack_to_signalling_table(destination, status)
|
||||||
|
self.log.info("[MESH] [RX] [PING] [ACK]", destination=destination, mycall=Station.mycallsign_crc.hex())
|
||||||
|
for item in MESH_SIGNALLING_TABLE:
|
||||||
|
if item[1] == destination and item[5] >= self.signalling_max_attempts:
|
||||||
|
# use case 2: set status to forwarded if we are not the receiver of a PING and out of retries
|
||||||
|
self.add_mesh_ping_ack_to_signalling_table(destination, status="forwarded")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.add_mesh_ping_ack_to_signalling_table(destination, status="forwarding")
|
||||||
|
#dxcallsign_crc = bytes.fromhex(destination)
|
||||||
|
#self.transmit_mesh_signalling_ping_ack(dxcallsign_crc)
|
||||||
|
|
||||||
|
print(MESH_SIGNALLING_TABLE)
|
||||||
|
|
||||||
|
|
||||||
|
def add_mesh_ping_to_signalling_table(self, destination, status):
|
||||||
|
timestamp = time.time()
|
||||||
|
router = ""
|
||||||
|
frametype = "PING"
|
||||||
|
payload = ""
|
||||||
|
attempt = 0
|
||||||
|
|
||||||
|
# [timestamp, destination, router, frametype, payload, attempt, status]
|
||||||
|
# --------------0------------1---------2---------3--------4---------5--------6-----#
|
||||||
|
new_entry = [timestamp, destination, router, frametype, payload, attempt, status]
|
||||||
|
for _, item in enumerate(MESH_SIGNALLING_TABLE):
|
||||||
|
# update entry if exists
|
||||||
|
if destination in item[1] and "PING" in item[3]:
|
||||||
|
# reset attempts if entry exists and it failed or is acknowledged
|
||||||
|
attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5]
|
||||||
|
update_entry = [item[0], destination, "", "PING", "",attempt, status]
|
||||||
|
#print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}")
|
||||||
|
|
||||||
|
self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry)
|
||||||
|
|
||||||
|
MESH_SIGNALLING_TABLE[_] = update_entry
|
||||||
|
return
|
||||||
|
|
||||||
|
# add new routing entry if not exists
|
||||||
|
if new_entry not in MESH_SIGNALLING_TABLE:
|
||||||
|
#print(f"INSERT {new_entry} >>> SIGNALLING TABLE")
|
||||||
|
self.log.info("[MESH] [SIGNALLING TABLE] [INSERT]:", insert=new_entry)
|
||||||
|
|
||||||
|
MESH_SIGNALLING_TABLE.append(new_entry)
|
||||||
|
|
||||||
|
def add_mesh_ping_ack_to_signalling_table(self, destination, status):
|
||||||
|
|
||||||
|
timestamp = time.time()
|
||||||
|
router = ""
|
||||||
|
frametype = "PING-ACK"
|
||||||
|
payload = ""
|
||||||
|
attempt = 0
|
||||||
|
new_entry = [timestamp, destination, router, frametype, payload, attempt, status]
|
||||||
|
|
||||||
|
for _, item in enumerate(MESH_SIGNALLING_TABLE):
|
||||||
|
# update entry if exists
|
||||||
|
if destination in item[1] and item[3] in ["PING", "PING-ACK"]:
|
||||||
|
# reset attempts if entry exists and it failed or is acknowledged
|
||||||
|
attempt = 0 if item[6] in ["failed", "acknowledged"] else item[5]
|
||||||
|
update_entry = [item[0], destination, "", "PING-ACK", "", attempt, status]
|
||||||
|
#print(f"UPDATE {MESH_SIGNALLING_TABLE[_]} >>> {update_entry}")
|
||||||
|
self.log.info(f"[MESH] [SIGNALLING TABLE] [UPDATE]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=update_entry)
|
||||||
|
|
||||||
|
MESH_SIGNALLING_TABLE[_] = update_entry
|
||||||
|
return
|
||||||
|
|
||||||
|
# add new routing entry if not exists
|
||||||
|
if new_entry not in MESH_SIGNALLING_TABLE:
|
||||||
|
#print(f"INSERT {new_entry} >>> SIGNALLING TABLE")
|
||||||
|
self.log.info(f"[MESH] [SIGNALLING TABLE] [INSERT]: {MESH_SIGNALLING_TABLE[_]} >>> ", update=new_entry)
|
||||||
|
|
||||||
|
MESH_SIGNALLING_TABLE.append(new_entry)
|
||||||
|
|
||||||
|
def enqueue_frame_for_tx(
|
||||||
|
self,
|
||||||
|
frame_to_tx, # : list[bytearray], # this causes a crash on python 3.7
|
||||||
|
c2_mode=FREEDV_MODE.sig0.value,
|
||||||
|
copies=1,
|
||||||
|
repeat_delay=0,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Send (transmit) supplied frame to TNC
|
||||||
|
|
||||||
|
:param frame_to_tx: Frame data to send
|
||||||
|
:type frame_to_tx: list of bytearrays
|
||||||
|
:param c2_mode: Codec2 mode to use, defaults to datac13
|
||||||
|
:type c2_mode: int, optional
|
||||||
|
:param copies: Number of frame copies to send, defaults to 1
|
||||||
|
:type copies: int, optional
|
||||||
|
:param repeat_delay: Delay time before sending repeat frame, defaults to 0
|
||||||
|
:type repeat_delay: int, optional
|
||||||
|
"""
|
||||||
|
#print(frame_to_tx[0])
|
||||||
|
#print(frame_to_tx)
|
||||||
|
frame_type = FRAME_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name
|
||||||
|
self.log.debug("[TNC] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx,
|
||||||
|
type=frame_type)
|
||||||
|
|
||||||
|
# Set the TRANSMITTING flag before adding an object to the transmit queue
|
||||||
|
# TODO: This is not that nice, we could improve this somehow
|
||||||
|
TNC.transmitting = True
|
||||||
|
modem.MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx])
|
||||||
|
|
||||||
|
# Wait while transmitting
|
||||||
|
while TNC.transmitting:
|
||||||
|
threading.Event().wait(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def transmit_mesh_signalling_ping(self, dxcallsign_crc):
|
||||||
|
|
||||||
|
frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING.value])
|
||||||
|
|
||||||
|
ping_frame = bytearray(14)
|
||||||
|
ping_frame[:1] = frame_type
|
||||||
|
ping_frame[1:4] = dxcallsign_crc
|
||||||
|
ping_frame[4:7] = helpers.get_crc_24(Station.mycallsign)
|
||||||
|
ping_frame[7:13] = helpers.callsign_to_bytes(Station.mycallsign)
|
||||||
|
|
||||||
|
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value)
|
||||||
|
|
||||||
|
def transmit_mesh_signalling_ping_ack(self, dxcallsign_crc):
|
||||||
|
#dxcallsign_crc = bytes.fromhex(data[1])
|
||||||
|
|
||||||
|
frame_type = bytes([FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value])
|
||||||
|
|
||||||
|
ping_frame = bytearray(14)
|
||||||
|
ping_frame[:1] = frame_type
|
||||||
|
ping_frame[1:4] = dxcallsign_crc
|
||||||
|
#ping_frame[4:7] = helpers.get_crc_24(Station.mycallsign)
|
||||||
|
#ping_frame[7:13] = helpers.callsign_to_bytes(Station.mycallsign)
|
||||||
|
|
||||||
|
self.enqueue_frame_for_tx([ping_frame], c2_mode=FREEDV_MODE.sig0.value)
|
22
tnc/modem.py
22
tnc/modem.py
|
@ -31,7 +31,7 @@ import tci
|
||||||
# FIXME: used for def transmit_morse
|
# FIXME: used for def transmit_morse
|
||||||
# import cw
|
# import cw
|
||||||
from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \
|
from queues import DATA_QUEUE_RECEIVED, MODEM_RECEIVED_QUEUE, MODEM_TRANSMIT_QUEUE, RIGCTLD_COMMAND_QUEUE, \
|
||||||
AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE
|
AUDIO_RECEIVED_QUEUE, AUDIO_TRANSMIT_QUEUE, MESH_RECEIVED_QUEUE
|
||||||
|
|
||||||
TESTMODE = False
|
TESTMODE = False
|
||||||
RXCHANNEL = ""
|
RXCHANNEL = ""
|
||||||
|
@ -889,6 +889,7 @@ class RF:
|
||||||
audiobuffer.pop(nin)
|
audiobuffer.pop(nin)
|
||||||
nin = codec2.api.freedv_nin(freedv)
|
nin = codec2.api.freedv_nin(freedv)
|
||||||
if nbytes == bytes_per_frame:
|
if nbytes == bytes_per_frame:
|
||||||
|
print(bytes(bytes_out))
|
||||||
|
|
||||||
# process commands only if TNC.listen = True
|
# process commands only if TNC.listen = True
|
||||||
if TNC.listen:
|
if TNC.listen:
|
||||||
|
@ -905,6 +906,16 @@ class RF:
|
||||||
FRAME_TYPE.ARQ_DC_OPEN_ACK_N.value
|
FRAME_TYPE.ARQ_DC_OPEN_ACK_N.value
|
||||||
]:
|
]:
|
||||||
print("dropp")
|
print("dropp")
|
||||||
|
elif int.from_bytes(bytes(bytes_out[:1]), "big") in [
|
||||||
|
FRAME_TYPE.MESH_BROADCAST.value,
|
||||||
|
FRAME_TYPE.MESH_SIGNALLING_PING.value,
|
||||||
|
FRAME_TYPE.MESH_SIGNALLING_PING_ACK.value,
|
||||||
|
]:
|
||||||
|
self.log.debug(
|
||||||
|
"[MDM] [demod_audio] moving data to mesh dispatcher", nbytes=nbytes
|
||||||
|
)
|
||||||
|
MESH_RECEIVED_QUEUE.put(bytes(bytes_out))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
|
"[MDM] [demod_audio] Pushing received data to received_queue", nbytes=nbytes
|
||||||
|
@ -1299,10 +1310,11 @@ class RF:
|
||||||
raise ZeroDivisionError
|
raise ZeroDivisionError
|
||||||
AudioParam.audio_dbfs = 20 * np.log10(rms / 32768)
|
AudioParam.audio_dbfs = 20 * np.log10(rms / 32768)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.warning(
|
# FIXME: Disabled for cli cleanup
|
||||||
"[MDM] fft calculation error - please check your audio setup",
|
#self.log.warning(
|
||||||
e=e,
|
# "[MDM] fft calculation error - please check your audio setup",
|
||||||
)
|
# e=e,
|
||||||
|
#)
|
||||||
AudioParam.audio_dbfs = -100
|
AudioParam.audio_dbfs = -100
|
||||||
|
|
||||||
rms_counter = 0
|
rms_counter = 0
|
||||||
|
|
|
@ -12,6 +12,12 @@ DATA_QUEUE_RECEIVED = queue.Queue()
|
||||||
MODEM_RECEIVED_QUEUE = queue.Queue()
|
MODEM_RECEIVED_QUEUE = queue.Queue()
|
||||||
MODEM_TRANSMIT_QUEUE = queue.Queue()
|
MODEM_TRANSMIT_QUEUE = queue.Queue()
|
||||||
|
|
||||||
|
# Initialize FIFO queue to store received frames
|
||||||
|
MESH_RECEIVED_QUEUE = queue.Queue()
|
||||||
|
MESH_QUEUE_TRANSMIT = queue.Queue()
|
||||||
|
MESH_SIGNALLING_TABLE = []
|
||||||
|
|
||||||
|
|
||||||
# Initialize FIFO queue to store audio frames
|
# Initialize FIFO queue to store audio frames
|
||||||
AUDIO_RECEIVED_QUEUE = queue.Queue()
|
AUDIO_RECEIVED_QUEUE = queue.Queue()
|
||||||
AUDIO_TRANSMIT_QUEUE = queue.Queue()
|
AUDIO_TRANSMIT_QUEUE = queue.Queue()
|
||||||
|
|
127
tnc/sock.py
127
tnc/sock.py
|
@ -27,12 +27,12 @@ import time
|
||||||
import wave
|
import wave
|
||||||
import helpers
|
import helpers
|
||||||
import static
|
import static
|
||||||
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC
|
from static import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, TNC, MeshParam
|
||||||
import structlog
|
import structlog
|
||||||
from random import randrange
|
from random import randrange
|
||||||
import ujson as json
|
import ujson as json
|
||||||
from exceptions import NoCallsign
|
from exceptions import NoCallsign
|
||||||
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE
|
from queues import DATA_QUEUE_TRANSMIT, RX_BUFFER, RIGCTLD_COMMAND_QUEUE, MESH_QUEUE_TRANSMIT, MESH_SIGNALLING_TABLE
|
||||||
|
|
||||||
SOCKET_QUEUE = queue.Queue()
|
SOCKET_QUEUE = queue.Queue()
|
||||||
DAEMON_QUEUE = queue.Queue()
|
DAEMON_QUEUE = queue.Queue()
|
||||||
|
@ -391,6 +391,16 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
else:
|
else:
|
||||||
self.tnc_set_mode(received_json)
|
self.tnc_set_mode(received_json)
|
||||||
|
|
||||||
|
# GET ROUTING TABLE
|
||||||
|
if received_json["type"] == "get" and received_json["command"] == "routing_table":
|
||||||
|
self.tnc_get_mesh_routing_table(received_json)
|
||||||
|
|
||||||
|
|
||||||
|
# -------------- MESH ---------------- #
|
||||||
|
# MESH PING
|
||||||
|
if received_json["type"] == "mesh" and received_json["command"] == "ping":
|
||||||
|
self.tnc_mesh_ping(received_json)
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log.error("[SCK] JSON decoding error", e=err)
|
log.error("[SCK] JSON decoding error", e=err)
|
||||||
|
|
||||||
|
@ -406,6 +416,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
ARQ.arq_session_state = "disconnecting"
|
ARQ.arq_session_state = "disconnecting"
|
||||||
command_response("disconnect", True)
|
command_response("disconnect", True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
command_response("listen", False)
|
command_response("listen", False)
|
||||||
log.warning(
|
log.warning(
|
||||||
|
@ -559,6 +571,40 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
command=received_json,
|
command=received_json,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tnc_mesh_ping(self, received_json):
|
||||||
|
# send ping frame and wait for ACK
|
||||||
|
try:
|
||||||
|
dxcallsign = received_json["dxcallsign"]
|
||||||
|
if not str(dxcallsign).strip():
|
||||||
|
raise NoCallsign
|
||||||
|
|
||||||
|
# additional step for being sure our callsign is correctly
|
||||||
|
# in case we are not getting a station ssid
|
||||||
|
# then we are forcing a station ssid = 0
|
||||||
|
dxcallsign = helpers.callsign_to_bytes(dxcallsign)
|
||||||
|
dxcallsign = helpers.bytes_to_callsign(dxcallsign)
|
||||||
|
|
||||||
|
# check if specific callsign is set with different SSID than the TNC is initialized
|
||||||
|
try:
|
||||||
|
mycallsign = received_json["mycallsign"]
|
||||||
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
||||||
|
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
mycallsign = Station.mycallsign
|
||||||
|
|
||||||
|
MESH_QUEUE_TRANSMIT.put(["PING", mycallsign, dxcallsign])
|
||||||
|
command_response("ping", True)
|
||||||
|
except NoCallsign:
|
||||||
|
command_response("ping", False)
|
||||||
|
log.warning("[SCK] callsign required for ping", command=received_json)
|
||||||
|
except Exception as err:
|
||||||
|
command_response("ping", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] PING command execution error", e=err, command=received_json
|
||||||
|
)
|
||||||
|
|
||||||
def tnc_ping_ping(self, received_json):
|
def tnc_ping_ping(self, received_json):
|
||||||
# send ping frame and wait for ACK
|
# send ping frame and wait for ACK
|
||||||
|
|
||||||
|
@ -761,6 +807,44 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
"[SCK] STOP command execution error", e=err, command=received_json
|
"[SCK] STOP command execution error", e=err, command=received_json
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def tnc_get_mesh_routing_table(self, received_json):
|
||||||
|
try:
|
||||||
|
if not RX_BUFFER.empty():
|
||||||
|
output = {
|
||||||
|
"command": "routing_table",
|
||||||
|
"routes": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, route in enumerate(MeshParam.routing_table):
|
||||||
|
if MeshParam.routing_table[_][0].hex() == helpers.get_crc_24(b"direct").hex():
|
||||||
|
router = "direct"
|
||||||
|
else:
|
||||||
|
router = MeshParam.routing_table[_][0].hex()
|
||||||
|
output["routes"].append(
|
||||||
|
{
|
||||||
|
"dxcall": MeshParam.routing_table[_][0].hex(),
|
||||||
|
"router": router,
|
||||||
|
"hops": MeshParam.routing_table[_][2],
|
||||||
|
"snr": MeshParam.routing_table[_][3],
|
||||||
|
"score": MeshParam.routing_table[_][4],
|
||||||
|
"timestamp": MeshParam.routing_table[_][5],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
jsondata = json.dumps(output)
|
||||||
|
# self.request.sendall(bytes(jsondata, encoding))
|
||||||
|
SOCKET_QUEUE.put(jsondata)
|
||||||
|
command_response("routing_table", True)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
command_response("routing_table", False)
|
||||||
|
log.warning(
|
||||||
|
"[SCK] Send RX buffer command execution error",
|
||||||
|
e=err,
|
||||||
|
command=received_json,
|
||||||
|
)
|
||||||
|
|
||||||
def tnc_get_rx_buffer(self, received_json):
|
def tnc_get_rx_buffer(self, received_json):
|
||||||
try:
|
try:
|
||||||
if not RX_BUFFER.empty():
|
if not RX_BUFFER.empty():
|
||||||
|
@ -931,6 +1015,7 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
tx_delay = str(helpers.return_key_from_object("0", startparam, "tx_delay"))
|
tx_delay = str(helpers.return_key_from_object("0", startparam, "tx_delay"))
|
||||||
tci_ip = str(helpers.return_key_from_object("127.0.0.1", startparam, "tci_ip"))
|
tci_ip = str(helpers.return_key_from_object("127.0.0.1", startparam, "tci_ip"))
|
||||||
tci_port = str(helpers.return_key_from_object("50001", startparam, "tci_port"))
|
tci_port = str(helpers.return_key_from_object("50001", startparam, "tci_port"))
|
||||||
|
enable_mesh = str(helpers.return_key_from_object("False", startparam, "enable_mesh"))
|
||||||
try:
|
try:
|
||||||
# convert ssid list to python list
|
# convert ssid list to python list
|
||||||
ssid_list = str(helpers.return_key_from_object("0, 1, 2, 3, 4, 5, 6, 7, 8, 9", startparam, "ssid_list"))
|
ssid_list = str(helpers.return_key_from_object("0, 1, 2, 3, 4, 5, 6, 7, 8, 9", startparam, "ssid_list"))
|
||||||
|
@ -973,7 +1058,8 @@ class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
|
||||||
enable_stats,
|
enable_stats,
|
||||||
tx_delay,
|
tx_delay,
|
||||||
tci_ip,
|
tci_ip,
|
||||||
tci_port
|
tci_port,
|
||||||
|
enable_mesh
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
command_response("start_tnc", True)
|
command_response("start_tnc", True)
|
||||||
|
@ -1086,6 +1172,8 @@ def send_tnc_state():
|
||||||
"total_bytes": str(ARQ.total_bytes),
|
"total_bytes": str(ARQ.total_bytes),
|
||||||
"beacon_state": str(Beacon.beacon_state),
|
"beacon_state": str(Beacon.beacon_state),
|
||||||
"stations": [],
|
"stations": [],
|
||||||
|
"routing_table": [],
|
||||||
|
"mesh_signalling_table" : [],
|
||||||
"mycallsign": str(Station.mycallsign, encoding),
|
"mycallsign": str(Station.mycallsign, encoding),
|
||||||
"mygrid": str(Station.mygrid, encoding),
|
"mygrid": str(Station.mygrid, encoding),
|
||||||
"dxcallsign": str(Station.dxcallsign, encoding),
|
"dxcallsign": str(Station.dxcallsign, encoding),
|
||||||
|
@ -1108,6 +1196,39 @@ def send_tnc_state():
|
||||||
"frequency": heard[6],
|
"frequency": heard[6],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for _, route in enumerate(MeshParam.routing_table):
|
||||||
|
if MeshParam.routing_table[_][1].hex() == helpers.get_crc_24(b"direct").hex():
|
||||||
|
router = "direct"
|
||||||
|
else:
|
||||||
|
router = MeshParam.routing_table[_][1].hex()
|
||||||
|
output["routing_table"].append(
|
||||||
|
{
|
||||||
|
"dxcall": MeshParam.routing_table[_][0].hex(),
|
||||||
|
"router": router,
|
||||||
|
"hops": MeshParam.routing_table[_][2],
|
||||||
|
"snr": MeshParam.routing_table[_][3],
|
||||||
|
"score": MeshParam.routing_table[_][4],
|
||||||
|
"timestamp": MeshParam.routing_table[_][5],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, entry in enumerate(MESH_SIGNALLING_TABLE):
|
||||||
|
|
||||||
|
output["mesh_signalling_table"].append(
|
||||||
|
{
|
||||||
|
"timestamp": MESH_SIGNALLING_TABLE[_][0],
|
||||||
|
"destination": MESH_SIGNALLING_TABLE[_][1],
|
||||||
|
"router": MESH_SIGNALLING_TABLE[_][2],
|
||||||
|
"frametype": MESH_SIGNALLING_TABLE[_][3],
|
||||||
|
"payload": MESH_SIGNALLING_TABLE[_][4],
|
||||||
|
"attempt": MESH_SIGNALLING_TABLE[_][5],
|
||||||
|
"status": MESH_SIGNALLING_TABLE[_][6],
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
#print(output)
|
||||||
return json.dumps(output)
|
return json.dumps(output)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,10 @@ class HamlibParam:
|
||||||
hamlib_rf: int = 0
|
hamlib_rf: int = 0
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
class MeshParam:
|
||||||
|
routing_table = []
|
||||||
|
enable_protocol = False
|
||||||
|
@dataclass
|
||||||
class ModemParam:
|
class ModemParam:
|
||||||
tuning_range_fmin: float = -50.0
|
tuning_range_fmin: float = -50.0
|
||||||
tuning_range_fmax: float = 50.0
|
tuning_range_fmax: float = 50.0
|
||||||
|
@ -125,7 +129,7 @@ class TCIParam:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TNC:
|
class TNC:
|
||||||
version = "0.9.4-alpha.1"
|
version = "0.10.0-alpha.1"
|
||||||
host: str = "0.0.0.0"
|
host: str = "0.0.0.0"
|
||||||
port: int = 3000
|
port: int = 3000
|
||||||
SOCKET_TIMEOUT: int = 1 # seconds
|
SOCKET_TIMEOUT: int = 1 # seconds
|
||||||
|
@ -157,6 +161,9 @@ class FRAME_TYPE(Enum):
|
||||||
FR_REPEAT = 62
|
FR_REPEAT = 62
|
||||||
FR_NACK = 63
|
FR_NACK = 63
|
||||||
BURST_NACK = 64
|
BURST_NACK = 64
|
||||||
|
MESH_BROADCAST = 100
|
||||||
|
MESH_SIGNALLING_PING = 101
|
||||||
|
MESH_SIGNALLING_PING_ACK = 102
|
||||||
CQ = 200
|
CQ = 200
|
||||||
QRV = 201
|
QRV = 201
|
||||||
PING = 210
|
PING = 210
|
||||||
|
|
|
@ -29,7 +29,7 @@ def main_menu():
|
||||||
while True:
|
while True:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
title = 'Please select a command you want to run: '
|
title = 'Please select a command you want to run: '
|
||||||
options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES']
|
options = ['BEACON', 'PING', 'ARQ', 'LIST AUDIO DEVICES','LIST ROUTING TABLE']
|
||||||
option, index = pick(options, title)
|
option, index = pick(options, title)
|
||||||
|
|
||||||
# BEACON AREA
|
# BEACON AREA
|
||||||
|
@ -87,6 +87,10 @@ def main_menu():
|
||||||
|
|
||||||
option, index = pick(device_list, "Audio devices")
|
option, index = pick(device_list, "Audio devices")
|
||||||
|
|
||||||
|
elif option == 'LIST ROUTING TABLE':
|
||||||
|
run_network_command({"type": "get", "command": "routing_table"})
|
||||||
|
|
||||||
|
|
||||||
if option == '----- BACK -----':
|
if option == '----- BACK -----':
|
||||||
main_menu()
|
main_menu()
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,8 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
jsondata = json.loads(command)
|
jsondata = json.loads(command)
|
||||||
|
|
||||||
if jsondata.get('command') == "tnc_state":
|
if jsondata.get('command') == "tnc_state":
|
||||||
pass
|
#pass
|
||||||
|
print(jsondata.get("routing_table"))
|
||||||
|
|
||||||
if jsondata.get('freedata') == "tnc-message":
|
if jsondata.get('freedata') == "tnc-message":
|
||||||
log.info(jsondata)
|
log.info(jsondata)
|
||||||
|
@ -108,6 +109,8 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||||
if jsondata.get('status') == 'received' and jsondata.get('arq') == 'transmission':
|
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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue