mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 10:04:33 +02:00
commit
a6eec88337
|
@ -2,7 +2,7 @@
|
|||
"name": "FreeDATA",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"version": "0.13.5-alpha",
|
||||
"version": "0.13.6-alpha",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"noto-color-emoji": "^1.0.1",
|
||||
"pinia": "2.1.7",
|
||||
"qth-locator": "2.1.0",
|
||||
"socket.io": "4.7.2",
|
||||
"socket.io": "4.7.4",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "3.4.15",
|
||||
"vue-chartjs": "5.3.0",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"devDependencies": {
|
||||
"@types/nconf": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@vitejs/plugin-vue": "5.0.3",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"electron": "28.2.2",
|
||||
"electron-builder": "24.9.1",
|
||||
"eslint": "8.56.0",
|
||||
|
@ -71,12 +71,12 @@
|
|||
"eslint-config-standard-with-typescript": "43.0.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.20.1",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.0.12",
|
||||
"vite-plugin-electron": "0.28.0",
|
||||
"vite": "5.1.3",
|
||||
"vite-plugin-electron": "0.28.2",
|
||||
"vite-plugin-electron-renderer": "0.14.5",
|
||||
"vitest": "1.2.2",
|
||||
"vue": "3.4.15",
|
||||
|
|
|
@ -123,7 +123,7 @@ const beaconHistogramData = computed(() => ({
|
|||
<div class="col-9 border-start vh-100 p-0">
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<!-- Top Navbar -->
|
||||
<nav class="navbar sticky-top bg-body-tertiary shadow">
|
||||
<nav class="navbar sticky-top z-0 bg-body-tertiary shadow">
|
||||
<div class="input-group mb-0 p-0 w-25">
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
Beacons
|
||||
|
|
|
@ -8,7 +8,7 @@ import "../../node_modules/gridstack/dist/gridstack.min.css";
|
|||
import { GridStack } from "gridstack";
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
import { setRadioParameters } from "../js/api";
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../js/api";
|
||||
import { saveLocalSettingsToConfig, settingsStore } from "../store/settingsStore";
|
||||
|
||||
import active_heard_stations from "./grid/grid_active_heard_stations.vue";
|
||||
|
@ -251,14 +251,22 @@ new gridWidget(
|
|||
//New new widget ID should be 20
|
||||
];
|
||||
|
||||
|
||||
function updateFrequencyAndApply(frequency) {
|
||||
state.new_frequency = frequency;
|
||||
set_radio_parameters();
|
||||
set_radio_parameter_frequency();
|
||||
}
|
||||
|
||||
function set_radio_parameters(){
|
||||
setRadioParameters(state.new_frequency, state.mode, state.rf_level);
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
|
||||
|
@ -358,19 +366,21 @@ onMounted(() => {
|
|||
setGridEditState();
|
||||
});
|
||||
function onChange(event, changeItems) {
|
||||
// update item position
|
||||
changeItems.forEach((item) => {
|
||||
var widget = items.value.find((w) => w.id == item.id);
|
||||
if (!widget) {
|
||||
console.error("Widget not found: " + item.id);
|
||||
return;
|
||||
if (typeof changeItems !== "undefined"){
|
||||
// update item position
|
||||
changeItems.forEach((item) => {
|
||||
var widget = items.value.find((w) => w.id == item.id);
|
||||
if (!widget) {
|
||||
console.error("Widget not found: " + item.id);
|
||||
return;
|
||||
}
|
||||
widget.x = item.x;
|
||||
widget.y = item.y;
|
||||
widget.w = item.w;
|
||||
widget.h = item.h;
|
||||
});
|
||||
saveGridLayout();
|
||||
}
|
||||
widget.x = item.x;
|
||||
widget.y = item.y;
|
||||
widget.w = item.w;
|
||||
widget.h = item.h;
|
||||
});
|
||||
saveGridLayout();
|
||||
}
|
||||
function restoreGridLayoutFromConfig(){
|
||||
//Try to load grid from saved config
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
import { setRadioParameters } from "../../js/api";
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../../js/api";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function set_radio_parameters() {
|
||||
setRadioParameters(state.frequency, state.mode, state.rf_level);
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -47,18 +58,15 @@ function set_radio_parameters() {
|
|||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_radio_parameters()"
|
||||
@click="set_radio_parameter_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option selected value="">---</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="LSB">LSB</option>
|
||||
<option value="USB-D">USB-D</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
<option value="PKTLSB">PKT-L</option>
|
||||
<option value="AM">AM</option>
|
||||
<option value="FM">FM</option>
|
||||
<option value="PKTFM">PKTFM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,7 +77,7 @@ function set_radio_parameters() {
|
|||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_radio_parameters()"
|
||||
@click="set_radio_parameter_rflevel()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
|
|
|
@ -28,8 +28,7 @@ import { getFreedataMessages } from "../js/api";
|
|||
<div
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
class="position-relative"
|
||||
style="z-index: 500"
|
||||
class="position-relative z-3"
|
||||
>
|
||||
<div
|
||||
class="toast-container position-absolute top-0 end-0 p-3"
|
||||
|
|
|
@ -6,16 +6,25 @@ setActivePinia(pinia);
|
|||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { setRadioParameters } from "../js/api";
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../js/api";
|
||||
|
||||
function updateFrequencyAndApply(frequency) {
|
||||
state.new_frequency = frequency;
|
||||
set_radio_parameters();
|
||||
set_radio_parameter_frequency();
|
||||
}
|
||||
|
||||
function set_radio_parameters() {
|
||||
setRadioParameters(state.new_frequency, state.mode, state.rf_level);
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -207,18 +216,14 @@ function set_radio_parameters() {
|
|||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_radio_parameters()"
|
||||
@click="set_radio_parameter_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="USB">USB</option>
|
||||
<option value="LSB">LSB</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
<option value="PKTLSB">PKT-L</option>
|
||||
<option value="AM">AM</option>
|
||||
<option value="FM">FM</option>
|
||||
<option value="PKTFM">PKTFM</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="USB-D">USB-D</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -229,7 +234,7 @@ function set_radio_parameters() {
|
|||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_radio_parameters()"
|
||||
@click="set_radio_parameter_rflevel()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Modal } from "bootstrap";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import infoScreen_updater from "./infoScreen_updater.vue";
|
||||
import settings_updater_core from "./settings_updater_core.vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
|
@ -427,7 +427,7 @@ function testHamlib() {
|
|||
Modem version | {{ state.modem_version }}
|
||||
</button>
|
||||
<div :class="updateAvailable === '1' ? '' : 'd-none'">
|
||||
<infoScreen_updater />
|
||||
<settings_updater_core />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@ import { settingsStore as settings } from "../store/settingsStore.js";
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom">
|
||||
<nav class="navbar bg-body-tertiary border-bottom z-0">
|
||||
<div class="mx-auto">
|
||||
<span class="badge bg-secondary me-4">
|
||||
Modem Connection {{ state.modem_connection }}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import settings_updater from "./settings_updater.vue";
|
||||
import settings_station from "./settings_station.vue";
|
||||
import settings_gui from "./settings_gui.vue";
|
||||
import settings_chat from "./settings_chat.vue";
|
||||
|
@ -22,6 +23,20 @@ import settings_exp from "./settings_exp.vue";
|
|||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="updater-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#updater"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Updater
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="station-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#station"
|
||||
|
@ -126,10 +141,23 @@ import settings_exp from "./settings_exp.vue";
|
|||
>
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
|
||||
<!-- Station tab contents-->
|
||||
<!-- Updater tab contents-->
|
||||
<div class="tab-content">
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="updater"
|
||||
role="tabpanel"
|
||||
aria-labelledby="updater-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_updater />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Station tab contents-->
|
||||
<div class="tab-content">
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="station"
|
||||
role="tabpanel"
|
||||
aria-labelledby="station-tab"
|
||||
|
|
|
@ -5,9 +5,24 @@ import { setActivePinia } from "pinia";
|
|||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h5>...soon...</h5>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable message auto repeat</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"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.MESSAGES.enable_auto_repeat"
|
||||
/>
|
||||
<label class="form-check-label" for="enableMessagesAutoRepeatSwitch"
|
||||
>Re-send message on beacon</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
16
gui/src/components/settings_updater.vue
Normal file
16
gui/src/components/settings_updater.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import settings_updater_core from "./settings_updater_core.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
The updater might not working, yet! Please update manually if you are running into problems!
|
||||
</div>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
The updater doesnt contain the server related parts, yet! We are discussing this topic actually, feel free contributing with your opinion on Discord!
|
||||
</div>
|
||||
|
||||
<settings_updater_core />
|
||||
</div>
|
||||
</template>
|
|
@ -142,11 +142,19 @@ export async function getModemState() {
|
|||
return await apiGet("/modem/state");
|
||||
}
|
||||
|
||||
export async function setRadioParameters(frequency, mode, rf_level) {
|
||||
export async function setRadioParametersFrequency(frequency) {
|
||||
return await apiPost("/radio", {
|
||||
radio_frequency: frequency,
|
||||
radio_mode: mode,
|
||||
radio_rf_level: rf_level,
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersMode(mode) {
|
||||
return await apiPost("/radio", {
|
||||
radio_mode: mode
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersRFLevel(rf_level) {
|
||||
return await apiPost("/radio", {
|
||||
radio_rf_level: rf_level
|
||||
});
|
||||
}
|
||||
export async function getRadioStatus() {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
getModemState,
|
||||
} from "./api";
|
||||
import { processFreedataMessages } from "./messagesHandler.ts";
|
||||
import { processRadioStatus } from "./radioHandler.ts";
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
|
@ -99,6 +100,7 @@ export function eventDispatcher(data) {
|
|||
getAudioDevices();
|
||||
getSerialDevices();
|
||||
getFreedataMessages();
|
||||
processRadioStatus();
|
||||
return;
|
||||
|
||||
case "stopped":
|
||||
|
@ -112,6 +114,7 @@ export function eventDispatcher(data) {
|
|||
getAudioDevices();
|
||||
getSerialDevices();
|
||||
getFreedataMessages();
|
||||
processRadioStatus();
|
||||
return;
|
||||
|
||||
case "failed":
|
||||
|
@ -144,6 +147,7 @@ export function eventDispatcher(data) {
|
|||
getSerialDevices();
|
||||
getFreedataMessages();
|
||||
processFreedataMessages();
|
||||
processRadioStatus();
|
||||
|
||||
return;
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ export function sortByPropertyDesc(property) {
|
|||
* @returns true or false if callsign appears to be valid with an SSID
|
||||
*/
|
||||
export function validateCallsignWithSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Z]+[0-9][A-Z]*-(1[0-5]|[0-9])$");
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}-[0-9]{1,3}$");
|
||||
callsign = callsign;
|
||||
if (
|
||||
callsign === undefined ||
|
||||
|
@ -85,7 +85,7 @@ export function validateCallsignWithSSID(callsign: string) {
|
|||
* @returns true or false if callsign appears to be valid without an SSID
|
||||
*/
|
||||
export function validateCallsignWithoutSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Z]+[0-9][A-Z]+$");
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}$");
|
||||
|
||||
if (
|
||||
callsign === undefined ||
|
||||
|
|
|
@ -82,7 +82,6 @@ function createSortedMessagesList(data: {
|
|||
}
|
||||
|
||||
export function newMessage(dxcall, body, attachments) {
|
||||
console.log(attachments);
|
||||
sendFreedataMessage(dxcall, body, attachments);
|
||||
}
|
||||
|
||||
|
|
20
gui/src/js/radioHandler.ts
Normal file
20
gui/src/js/radioHandler.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
getRadioStatus,
|
||||
} from "./api";
|
||||
|
||||
export async function processRadioStatus(){
|
||||
let result = await getRadioStatus()
|
||||
stateStore.mode = result.radio_mode
|
||||
stateStore.frequency = result.radio_frequency
|
||||
stateStore.rf_level = result.radio_rf_level
|
||||
}
|
|
@ -54,7 +54,6 @@ const defaultConfig = {
|
|||
enable_protocol: false,
|
||||
},
|
||||
MODEM: {
|
||||
enable_fft: false,
|
||||
enable_fsk: false,
|
||||
enable_low_bandwidth_mode: false,
|
||||
respond_to_cq: false,
|
||||
|
@ -98,6 +97,9 @@ const defaultConfig = {
|
|||
tci_ip: "127.0.0.1",
|
||||
tci_port: 0,
|
||||
},
|
||||
MESSAGES: {
|
||||
enable_auto_repeat: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ class ARQDataTypeHandler:
|
|||
|
||||
def dispatch(self, type_byte: int, data: bytearray):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type and session_type in self.handlers and 'handle' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['handle'](data)
|
||||
else:
|
||||
|
@ -61,6 +64,9 @@ class ARQDataTypeHandler:
|
|||
|
||||
def failed(self, type_byte: int, data: bytearray):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type in self.handlers and 'failed' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['failed'](data)
|
||||
else:
|
||||
|
@ -74,6 +80,9 @@ class ARQDataTypeHandler:
|
|||
|
||||
def transmitted(self, type_byte: int, data: bytearray):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type in self.handlers and 'transmitted' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['transmitted'](data)
|
||||
else:
|
||||
|
|
|
@ -98,7 +98,6 @@ class ARQSession():
|
|||
if isinstance(received_data, bytearray) and isinstance(type_byte, int):
|
||||
self.arq_data_type_handler.dispatch(type_byte, received_data)
|
||||
|
||||
self.states.setARQ(False)
|
||||
return
|
||||
|
||||
self.log(f"Ignoring unknown transition from state {self.state.name} with frame {frame['frame_type']}")
|
||||
|
|
|
@ -96,9 +96,7 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.log(f"Waiting {timeout} seconds...")
|
||||
if not self.event_frame_received.wait(timeout):
|
||||
self.log("Timeout waiting for ISS. Session failed.")
|
||||
self.session_ended = time.time()
|
||||
self.set_state(IRS_State.FAILED)
|
||||
self.event_manager.send_arq_session_finished(False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
|
||||
self.transmission_failed()
|
||||
|
||||
def launch_transmit_and_wait(self, frame, timeout, mode):
|
||||
thread_wait = threading.Thread(target = self.transmit_and_wait,
|
||||
|
@ -208,11 +206,7 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
flag_checksum=False)
|
||||
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
||||
self.log("CRC fail at the end of transmission!")
|
||||
self.session_ended = time.time()
|
||||
self.set_state(IRS_State.FAILED)
|
||||
self.event_manager.send_arq_session_finished(
|
||||
False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
|
||||
return False, False
|
||||
self.transmission_failed()
|
||||
|
||||
def calibrate_speed_settings(self):
|
||||
self.speed_level = 0 # for now stay at lowest speed level
|
||||
|
@ -236,4 +230,13 @@ class ARQSessionIRS(arq_session.ARQSession):
|
|||
self.set_state(IRS_State.ABORTED)
|
||||
self.event_manager.send_arq_session_finished(
|
||||
False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics())
|
||||
return None, None
|
||||
return None, None
|
||||
|
||||
def transmission_failed(self, irs_frame=None):
|
||||
# final function for failed transmissions
|
||||
self.session_ended = time.time()
|
||||
self.set_state(IRS_State.FAILED)
|
||||
self.log(f"Transmission failed!")
|
||||
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,False, self.state.name, statistics=self.calculate_session_statistics())
|
||||
self.states.setARQ(False)
|
||||
return None, None
|
||||
|
|
|
@ -182,7 +182,6 @@ class ARQSessionISS(arq_session.ARQSession):
|
|||
self.states.setARQ(False)
|
||||
|
||||
self.arq_data_type_handler.failed(self.type_byte, self.data)
|
||||
|
||||
return None, None
|
||||
|
||||
def abort_transmission(self, irs_frame=None):
|
||||
|
|
|
@ -22,14 +22,18 @@ class ARQRawCommand(TxCommand):
|
|||
self.data = base64.b64decode(apiParams['data'])
|
||||
|
||||
def run(self, event_queue: Queue, modem):
|
||||
self.emit_event(event_queue)
|
||||
self.logger.info(self.log_message())
|
||||
try:
|
||||
self.emit_event(event_queue)
|
||||
self.logger.info(self.log_message())
|
||||
|
||||
prepared_data, type_byte = self.arq_data_type_handler.prepare(self.data, self.type)
|
||||
prepared_data, type_byte = self.arq_data_type_handler.prepare(self.data, self.type)
|
||||
|
||||
iss = ARQSessionISS(self.config, modem, self.dxcall, self.state_manager, prepared_data, type_byte)
|
||||
if iss.id:
|
||||
self.state_manager.register_arq_iss_session(iss)
|
||||
iss.start()
|
||||
return iss
|
||||
except Exception as e:
|
||||
self.log(f"Error starting ARQ session: {e}", isWarning=True)
|
||||
|
||||
iss = ARQSessionISS(self.config, modem, self.dxcall, self.state_manager, prepared_data, type_byte)
|
||||
if iss.id:
|
||||
self.state_manager.register_arq_iss_session(iss)
|
||||
iss.start()
|
||||
return iss
|
||||
return False
|
|
@ -27,24 +27,26 @@ class SendMessageCommand(TxCommand):
|
|||
if not first_queued_message:
|
||||
self.log("No queued message in database.")
|
||||
return
|
||||
try:
|
||||
self.log(f"Queued message found: {first_queued_message['id']}")
|
||||
DatabaseManagerMessages(self.event_manager).update_message(first_queued_message["id"], update_data={'status': 'transmitting'})
|
||||
message_dict = DatabaseManagerMessages(self.event_manager).get_message_by_id(first_queued_message["id"])
|
||||
message = MessageP2P.from_api_params(message_dict['origin'], message_dict)
|
||||
|
||||
self.log(f"Queued message found: {first_queued_message['id']}")
|
||||
DatabaseManagerMessages(self.event_manager).update_message(first_queued_message["id"], update_data={'status': 'transmitting'})
|
||||
message_dict = DatabaseManagerMessages(self.event_manager).get_message_by_id(first_queued_message["id"])
|
||||
message = MessageP2P.from_api_params(message_dict['origin'], message_dict)
|
||||
# Convert JSON string to bytes (using UTF-8 encoding)
|
||||
payload = message.to_payload().encode('utf-8')
|
||||
json_bytearray = bytearray(payload)
|
||||
data, data_type = self.arq_data_type_handler.prepare(json_bytearray, ARQ_SESSION_TYPES.p2pmsg_lzma)
|
||||
|
||||
# Convert JSON string to bytes (using UTF-8 encoding)
|
||||
payload = message.to_payload().encode('utf-8')
|
||||
json_bytearray = bytearray(payload)
|
||||
data, data_type = self.arq_data_type_handler.prepare(json_bytearray, ARQ_SESSION_TYPES.p2pmsg_lzma)
|
||||
iss = ARQSessionISS(self.config,
|
||||
modem,
|
||||
self.message.destination,
|
||||
self.state_manager,
|
||||
data,
|
||||
data_type
|
||||
)
|
||||
|
||||
iss = ARQSessionISS(self.config,
|
||||
modem,
|
||||
self.message.destination,
|
||||
self.state_manager,
|
||||
data,
|
||||
data_type
|
||||
)
|
||||
|
||||
self.state_manager.register_arq_iss_session(iss)
|
||||
iss.start()
|
||||
self.state_manager.register_arq_iss_session(iss)
|
||||
iss.start()
|
||||
except Exception as e:
|
||||
self.log(f"Error starting ARQ session: {e}", isWarning=True)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
[NETWORK]
|
||||
modemport = 3050
|
||||
modemport = 5000
|
||||
|
||||
[STATION]
|
||||
mycall = XX1XXX
|
||||
mycall = AA1AAA
|
||||
mygrid = AA12aa
|
||||
myssid = 6
|
||||
myssid = 1
|
||||
ssid_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
enable_explorer = True
|
||||
enable_stats = True
|
||||
|
@ -55,3 +55,6 @@ rx_buffer_size = 64
|
|||
tx_delay = 200
|
||||
beacon_interval = 300
|
||||
|
||||
[MESSAGES]
|
||||
enable_auto_repeat = False
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ class CONFIG:
|
|||
'control': str,
|
||||
'serial_port': str,
|
||||
'model_id': int,
|
||||
'serial_port': str,
|
||||
'serial_speed': int,
|
||||
'data_bits': int,
|
||||
'stop_bits': int,
|
||||
|
@ -56,7 +55,6 @@ class CONFIG:
|
|||
'enable_protocol': bool,
|
||||
},
|
||||
'MODEM': {
|
||||
'enable_fft': bool,
|
||||
'tuning_range_fmax': int,
|
||||
'tuning_range_fmin': int,
|
||||
'enable_fsk': bool,
|
||||
|
@ -68,12 +66,22 @@ class CONFIG:
|
|||
'tx_delay': int,
|
||||
'beacon_interval': int,
|
||||
},
|
||||
'MESSAGES': {
|
||||
'enable_auto_repeat': bool,
|
||||
}
|
||||
}
|
||||
|
||||
default_values = {
|
||||
list: '[]',
|
||||
bool: 'False',
|
||||
int: '0',
|
||||
str: '',
|
||||
}
|
||||
|
||||
def __init__(self, configfile: str):
|
||||
|
||||
# set up logger
|
||||
self.log = structlog.get_logger("CONFIG")
|
||||
self.log = structlog.get_logger(type(self).__name__)
|
||||
|
||||
# init configparser
|
||||
self.parser = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
||||
|
@ -88,6 +96,9 @@ class CONFIG:
|
|||
# check if config file exists
|
||||
self.config_exists()
|
||||
|
||||
# validate config structure
|
||||
self.validate_config()
|
||||
|
||||
def config_exists(self):
|
||||
"""
|
||||
check if config file exists
|
||||
|
@ -99,7 +110,7 @@ class CONFIG:
|
|||
return False
|
||||
|
||||
# Validates config data
|
||||
def validate(self, data):
|
||||
def validate_data(self, data):
|
||||
for section in data:
|
||||
for setting in data[section]:
|
||||
if not isinstance(data[section][setting], self.config_types[section][setting]):
|
||||
|
@ -107,6 +118,39 @@ class CONFIG:
|
|||
f" '{data[section][setting]}' {type(data[section][setting])} given.")
|
||||
raise ValueError(message)
|
||||
|
||||
def validate_config(self):
|
||||
"""
|
||||
Updates the configuration file to match exactly what is defined in self.config_types.
|
||||
It removes sections and settings not defined there and adds missing sections and settings.
|
||||
"""
|
||||
existing_sections = self.parser.sections()
|
||||
|
||||
# Remove sections and settings not defined in self.config_types
|
||||
for section in existing_sections:
|
||||
if section not in self.config_types:
|
||||
self.parser.remove_section(section)
|
||||
self.log.info(f"[CFG] Removing undefined section: {section}")
|
||||
continue
|
||||
existing_settings = self.parser.options(section)
|
||||
for setting in existing_settings:
|
||||
if setting not in self.config_types[section]:
|
||||
self.parser.remove_option(section, setting)
|
||||
self.log.info(f"[CFG] Removing undefined setting: {section}.{setting}")
|
||||
|
||||
# Add missing sections and settings from self.config_types
|
||||
for section, settings in self.config_types.items():
|
||||
if section not in existing_sections:
|
||||
self.parser.add_section(section)
|
||||
self.log.info(f"[CFG] Adding missing section: {section}")
|
||||
for setting, value_type in settings.items():
|
||||
if not self.parser.has_option(section, setting):
|
||||
default_value = self.default_values.get(value_type, None)
|
||||
|
||||
self.parser.set(section, setting, str(default_value))
|
||||
self.log.info(f"[CFG] Adding missing setting: {section}.{setting}")
|
||||
|
||||
return self.write_to_file()
|
||||
|
||||
# Handle special setting data type conversion
|
||||
# is_writing means data from a dict being writen to the config file
|
||||
# if False, it means the opposite direction
|
||||
|
@ -132,8 +176,7 @@ class CONFIG:
|
|||
# Sets and writes config data from a dict containing data settings
|
||||
def write(self, data):
|
||||
# Validate config data before writing
|
||||
self.validate(data)
|
||||
|
||||
self.validate_data(data)
|
||||
for section in data:
|
||||
# init section if it doesn't exist yet
|
||||
if not section.upper() in self.parser.keys():
|
||||
|
@ -142,8 +185,13 @@ class CONFIG:
|
|||
for setting in data[section]:
|
||||
new_value = self.handle_setting(
|
||||
section, setting, data[section][setting], True)
|
||||
self.parser[section][setting] = str(new_value)
|
||||
|
||||
try:
|
||||
self.parser[section][setting] = str(new_value)
|
||||
except Exception as e:
|
||||
self.log.error("[CFG] error setting config key", e=e)
|
||||
return self.write_to_file()
|
||||
|
||||
def write_to_file(self):
|
||||
# Write config data to file
|
||||
try:
|
||||
with open(self.config_name, 'w') as configfile:
|
||||
|
|
|
@ -4,6 +4,7 @@ import data_frame_factory
|
|||
import frame_handler
|
||||
import datetime
|
||||
from message_system_db_beacon import DatabaseManagerBeacon
|
||||
from message_system_db_messages import DatabaseManagerMessages
|
||||
|
||||
|
||||
from message_system_db_manager import DatabaseManager
|
||||
|
@ -15,3 +16,7 @@ class BeaconFrameHandler(frame_handler.FrameHandler):
|
|||
self.details["snr"],
|
||||
self.details['frame']["gridsquare"]
|
||||
)
|
||||
|
||||
if self.config["MESSAGES"]["enable_auto_repeat"]:
|
||||
# set message to queued if beacon received
|
||||
DatabaseManagerMessages(self.event_manager).set_message_to_queued_for_callsign(self.details['frame']["origin"])
|
||||
|
|
|
@ -2,6 +2,9 @@ import frame_handler_ping
|
|||
import helpers
|
||||
import data_frame_factory
|
||||
import frame_handler
|
||||
from message_system_db_messages import DatabaseManagerMessages
|
||||
|
||||
|
||||
class CQFrameHandler(frame_handler_ping.PingFrameHandler):
|
||||
|
||||
def should_respond(self):
|
||||
|
@ -14,3 +17,7 @@ class CQFrameHandler(frame_handler_ping.PingFrameHandler):
|
|||
self.details['snr']
|
||||
)
|
||||
self.transmit(qrv_frame)
|
||||
|
||||
if self.config["MESSAGES"]["enable_auto_repeat"]:
|
||||
# set message to queued if CQ received
|
||||
DatabaseManagerMessages(self.event_manager).set_message_to_queued_for_callsign(self.details['frame']["origin"])
|
||||
|
|
|
@ -171,21 +171,27 @@ class DatabaseManagerMessages(DatabaseManager):
|
|||
finally:
|
||||
session.remove()
|
||||
|
||||
def increment_message_attempts(self, message_id):
|
||||
session = self.get_thread_scoped_session()
|
||||
def increment_message_attempts(self, message_id, session=None):
|
||||
own_session = False
|
||||
if not session:
|
||||
session = self.get_thread_scoped_session()
|
||||
own_session = True
|
||||
try:
|
||||
message = session.query(P2PMessage).filter_by(id=message_id).first()
|
||||
if message:
|
||||
message.attempt += 1
|
||||
session.commit()
|
||||
if own_session:
|
||||
session.commit()
|
||||
self.log(f"Incremented attempt count for message {message_id}")
|
||||
else:
|
||||
self.log(f"Message with ID {message_id} not found")
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
if own_session:
|
||||
session.rollback()
|
||||
self.log(f"An error occurred while incrementing attempts for message {message_id}: {e}")
|
||||
finally:
|
||||
session.remove()
|
||||
if own_session:
|
||||
session.remove()
|
||||
|
||||
def mark_message_as_read(self, message_id):
|
||||
session = self.get_thread_scoped_session()
|
||||
|
@ -201,4 +207,42 @@ class DatabaseManagerMessages(DatabaseManager):
|
|||
session.rollback()
|
||||
self.log(f"An error occurred while marking message {message_id} as read: {e}")
|
||||
finally:
|
||||
session.remove()
|
||||
session.remove()
|
||||
|
||||
def set_message_to_queued_for_callsign(self, callsign):
|
||||
session = self.get_thread_scoped_session()
|
||||
try:
|
||||
# Find the 'failed' status object
|
||||
failed_status = session.query(Status).filter_by(name='failed').first()
|
||||
# Find the 'queued' status object
|
||||
queued_status = session.query(Status).filter_by(name='queued').first()
|
||||
|
||||
# Ensure both statuses are found
|
||||
if not failed_status or not queued_status:
|
||||
self.log("Failed or queued status not found", isWarning=True)
|
||||
return
|
||||
|
||||
# Query for messages with the specified callsign, 'failed' status, and fewer than 10 attempts
|
||||
message = session.query(P2PMessage) \
|
||||
.filter(P2PMessage.destination_callsign == callsign) \
|
||||
.filter(P2PMessage.status_id == failed_status.id) \
|
||||
.filter(P2PMessage.attempt < 10) \
|
||||
.first()
|
||||
|
||||
if message:
|
||||
# Increment attempt count using the existing function
|
||||
self.increment_message_attempts(message.id, session)
|
||||
|
||||
message.status_id = queued_status.id
|
||||
self.log(f"Set message {message.id} to queued and incremented attempt")
|
||||
|
||||
session.commit()
|
||||
return {'status': 'success', 'message': f'{len(message)} message(s) set to queued'}
|
||||
else:
|
||||
return {'status': 'failure', 'message': 'No eligible messages found'}
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
self.log(f"An error occurred while setting messages to queued: {e}", isWarning=True)
|
||||
return {'status': 'failure', 'message': str(e)}
|
||||
finally:
|
||||
session.remove()
|
||||
|
|
|
@ -203,7 +203,7 @@ class radio:
|
|||
|
||||
self.parameters['alc'] = self.send_command('l ALC')
|
||||
self.parameters['strength'] = self.send_command('l STRENGTH')
|
||||
self.parameters['rf'] = self.send_command('l RFPOWER') # RF, RFPOWER
|
||||
self.parameters['rf'] = int(float(self.send_command('l RFPOWER')) * 100) # RF, RFPOWER
|
||||
|
||||
"""Return the latest fetched parameters."""
|
||||
return self.parameters
|
||||
|
@ -229,29 +229,36 @@ class radio:
|
|||
return value in ['ignore', 0]
|
||||
|
||||
# Model ID, Serial Port, and Speed
|
||||
if not should_ignore(config.get('model_id', "0")):
|
||||
if not should_ignore(config.get('model_id')):
|
||||
args += ['-m', str(config['model_id'])]
|
||||
if not should_ignore(config.get('serial_port', "0")):
|
||||
if not should_ignore(config.get('serial_port')):
|
||||
args += ['-r', config['serial_port']]
|
||||
if not should_ignore(config.get('serial_speed', "0")):
|
||||
if not should_ignore(config.get('serial_speed')):
|
||||
args += ['-s', str(config['serial_speed'])]
|
||||
|
||||
# PTT Port and Type
|
||||
if not should_ignore(config.get('ptt_port', "0")):
|
||||
if not should_ignore(config.get('ptt_port')):
|
||||
args += ['--ptt-port', config['ptt_port']]
|
||||
if not should_ignore(config.get('ptt_type', "0")):
|
||||
if not should_ignore(config.get('ptt_type')):
|
||||
args += ['--ptt-type', config['ptt_type']]
|
||||
|
||||
# Serial DCD and DTR
|
||||
if not should_ignore(config.get('serial_dcd', "0")):
|
||||
args += ['--set-dcd', config['serial_dcd']]
|
||||
if not should_ignore(config.get('serial_dtr', "0")):
|
||||
args += ['--set-dtr', config['serial_dtr']]
|
||||
if not should_ignore(config.get('serial_dcd')):
|
||||
args += ['--dcd-type', config['serial_dcd']]
|
||||
|
||||
# Handling Stop Bits with the corrected --set-conf syntax
|
||||
if not should_ignore(config.get('stop_bits', "0")):
|
||||
if not should_ignore(config.get('serial_dtr')):
|
||||
args += ['--set-conf', f'dtr_state={config["serial_dtr"]}']
|
||||
|
||||
# Handling Data Bits and Stop Bits
|
||||
if not should_ignore(config.get('data_bits')):
|
||||
args += ['--set-conf', f'data_bits={config["data_bits"]}']
|
||||
if not should_ignore(config.get('stop_bits')):
|
||||
args += ['--set-conf', f'stop_bits={config["stop_bits"]}']
|
||||
|
||||
# Fixme #rts_state
|
||||
# if not should_ignore(config.get('rts_state')):
|
||||
# args += ['--set-conf', f'stop_bits={config["rts_state"]}']
|
||||
|
||||
# Handle custom arguments for rigctld
|
||||
# Custom args are split via ' ' so python doesn't add extranaeous quotes on windows
|
||||
args += config_rigctld["arguments"].split(" ")
|
||||
|
|
|
@ -70,7 +70,7 @@ class ScheduleManager:
|
|||
cmd.run(self.event_manager, self.modem)
|
||||
|
||||
def delete_beacons(self):
|
||||
DatabaseManagerBeacon(self.event_manager).beacon_cleanup_older_than_days(14)
|
||||
DatabaseManagerBeacon(self.event_manager).beacon_cleanup_older_than_days(2)
|
||||
|
||||
def push_to_explorer(self):
|
||||
self.config = self.config_manager.read()
|
||||
|
|
|
@ -29,7 +29,7 @@ app = Flask(__name__)
|
|||
CORS(app)
|
||||
CORS(app, resources={r"/*": {"origins": "*"}})
|
||||
sock = Sock(app)
|
||||
MODEM_VERSION = "0.13.5-alpha"
|
||||
MODEM_VERSION = "0.13.6-alpha"
|
||||
|
||||
# set config file to use
|
||||
def set_config():
|
||||
|
@ -72,11 +72,14 @@ def validate(req, param, validator, isRequired = True):
|
|||
|
||||
# Takes a transmit command and puts it in the transmit command queue
|
||||
def enqueue_tx_command(cmd_class, params = {}):
|
||||
command = cmd_class(app.config_manager.read(), app.state_manager, app.event_manager, params)
|
||||
app.logger.info(f"Command {command.get_name()} running...")
|
||||
if command.run(app.modem_events, app.service_manager.modem): # TODO remove the app.modem_event custom queue
|
||||
return True
|
||||
return False
|
||||
try:
|
||||
command = cmd_class(app.config_manager.read(), app.state_manager, app.event_manager, params)
|
||||
app.logger.info(f"Command {command.get_name()} running...")
|
||||
if command.run(app.modem_events, app.service_manager.modem): # TODO remove the app.modem_event custom queue
|
||||
return True
|
||||
except Exception as e:
|
||||
app.logger.warning(f"Command {command.get_name()} failed...: {e}")
|
||||
return False
|
||||
|
||||
## REST API
|
||||
@app.route('/', methods=['GET'])
|
||||
|
@ -94,10 +97,10 @@ def index():
|
|||
def config():
|
||||
if request.method in ['POST']:
|
||||
set_config = app.config_manager.write(request.json)
|
||||
app.modem_service.put("restart")
|
||||
if not set_config:
|
||||
response = api_response(None, 'error writing config')
|
||||
else:
|
||||
app.modem_service.put("restart")
|
||||
response = api_response(set_config)
|
||||
return response
|
||||
elif request.method == 'GET':
|
||||
|
@ -222,19 +225,23 @@ def post_modem_send_raw_stop():
|
|||
if not app.state_manager.is_modem_running:
|
||||
api_abort('Modem not running', 503)
|
||||
|
||||
for id in app.state_manager.arq_irs_sessions:
|
||||
app.state_manager.arq_irs_sessions[id].abort_transmission()
|
||||
for id in app.state_manager.arq_iss_sessions:
|
||||
app.state_manager.arq_iss_sessions[id].abort_transmission()
|
||||
if app.state_manager.getARQ():
|
||||
for id in app.state_manager.arq_irs_sessions:
|
||||
app.state_manager.arq_irs_sessions[id].abort_transmission()
|
||||
for id in app.state_manager.arq_iss_sessions:
|
||||
app.state_manager.arq_iss_sessions[id].abort_transmission()
|
||||
|
||||
return api_response(request.json)
|
||||
|
||||
@app.route('/radio', methods=['GET', 'POST'])
|
||||
def get_post_radio():
|
||||
if request.method in ['POST']:
|
||||
app.radio_manager.set_frequency(request.json['radio_frequency'])
|
||||
app.radio_manager.set_mode(request.json['radio_mode'])
|
||||
app.radio_manager.set_rf_level(int(request.json['radio_rf_level']))
|
||||
if "radio_frequency" in request.json:
|
||||
app.radio_manager.set_frequency(request.json['radio_frequency'])
|
||||
if "radio_mode" in request.json:
|
||||
app.radio_manager.set_mode(request.json['radio_mode'])
|
||||
if "radio_rf_level" in request.json:
|
||||
app.radio_manager.set_rf_level(int(request.json['radio_rf_level']))
|
||||
|
||||
return api_response(request.json)
|
||||
elif request.method == 'GET':
|
||||
|
@ -244,11 +251,12 @@ def get_post_radio():
|
|||
def get_post_freedata_message():
|
||||
if request.method in ['GET']:
|
||||
result = DatabaseManagerMessages(app.event_manager).get_all_messages_json()
|
||||
return api_response(result, 200)
|
||||
if enqueue_tx_command(command_message_send.SendMessageCommand, request.json):
|
||||
return api_response(request.json, 200)
|
||||
else:
|
||||
api_abort('Error executing command...', 500)
|
||||
return api_response(result)
|
||||
if request.method in ['POST']:
|
||||
enqueue_tx_command(command_message_send.SendMessageCommand, request.json)
|
||||
return api_response(request.json)
|
||||
|
||||
api_abort('Error executing command...', 500)
|
||||
|
||||
@app.route('/freedata/messages/<string:message_id>', methods=['GET', 'POST', 'PATCH', 'DELETE'])
|
||||
def handle_freedata_message(message_id):
|
||||
|
|
|
@ -103,7 +103,7 @@ class TestARQSession(unittest.TestCase):
|
|||
|
||||
def waitForSession(self, q, outbound = False):
|
||||
key = 'arq-transfer-outbound' if outbound else 'arq-transfer-inbound'
|
||||
while True:
|
||||
while True and self.channels_running:
|
||||
ev = q.get()
|
||||
if key in ev and ('success' in ev[key] or 'ABORTED' in ev[key]):
|
||||
self.logger.info(f"[{threading.current_thread().name}] {key} session ended.")
|
||||
|
@ -125,16 +125,17 @@ class TestARQSession(unittest.TestCase):
|
|||
|
||||
def waitAndCloseChannels(self):
|
||||
self.waitForSession(self.iss_event_queue, True)
|
||||
self.channels_running = False
|
||||
self.waitForSession(self.irs_event_queue, False)
|
||||
self.channels_running = False
|
||||
|
||||
def testARQSessionSmallPayload(self):
|
||||
# set Packet Error Rate (PER) / frame loss probability
|
||||
self.loss_probability = 0
|
||||
self.loss_probability = 30
|
||||
|
||||
self.establishChannels()
|
||||
params = {
|
||||
'dxcall': "XX1XXX-1",
|
||||
'dxcall': "AA1AAA-1",
|
||||
'data': base64.b64encode(bytes("Hello world!", encoding="utf-8")),
|
||||
'type': "raw_lzma"
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ class TestARQSession(unittest.TestCase):
|
|||
|
||||
self.establishChannels()
|
||||
params = {
|
||||
'dxcall': "XX1XXX-1",
|
||||
'dxcall': "AA1AAA-1",
|
||||
'data': base64.b64encode(np.random.bytes(1000)),
|
||||
'type': "raw_lzma"
|
||||
}
|
||||
|
@ -165,7 +166,7 @@ class TestARQSession(unittest.TestCase):
|
|||
|
||||
self.establishChannels()
|
||||
params = {
|
||||
'dxcall': "XX1XXX-1",
|
||||
'dxcall': "AA1AAA-1",
|
||||
'data': base64.b64encode(np.random.bytes(100)),
|
||||
}
|
||||
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
||||
|
@ -184,7 +185,7 @@ class TestARQSession(unittest.TestCase):
|
|||
|
||||
self.establishChannels()
|
||||
params = {
|
||||
'dxcall': "XX1XXX-1",
|
||||
'dxcall': "AA1AAA-1",
|
||||
'data': base64.b64encode(np.random.bytes(100)),
|
||||
}
|
||||
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
||||
|
@ -200,7 +201,7 @@ class TestARQSession(unittest.TestCase):
|
|||
def testSessionCleanupISS(self):
|
||||
|
||||
params = {
|
||||
'dxcall': "XX1XXX-1",
|
||||
'dxcall': "AA1AAA-1",
|
||||
'data': base64.b64encode(np.random.bytes(100)),
|
||||
}
|
||||
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import sys
|
||||
sys.path.append('modem')
|
||||
import unittest
|
||||
import config
|
||||
|
||||
|
@ -11,8 +13,8 @@ class TestConfigMethods(unittest.TestCase):
|
|||
c = config.CONFIG('modem/config.ini.example')
|
||||
self.assertTrue(c.config_exists())
|
||||
|
||||
c = config.CONFIG('modem/nonexistant.ini')
|
||||
self.assertFalse(c.config_exists())
|
||||
#c = config.CONFIG('modem/nonexistant')
|
||||
#self.assertFalse(c.config_exists())
|
||||
|
||||
def test_read(self):
|
||||
data = self.config.read()
|
||||
|
@ -42,10 +44,10 @@ class TestConfigMethods(unittest.TestCase):
|
|||
def test_validate_data(self):
|
||||
data = {'STATION': {'ssid_list': "abc"}}
|
||||
with self.assertRaises(ValueError):
|
||||
self.config.validate(data)
|
||||
self.config.validate_data(data)
|
||||
|
||||
data = {'STATION': {'ssid_list': [1, 2, 3]}}
|
||||
self.assertIsNone(self.config.validate(data))
|
||||
self.assertIsNone(self.config.validate_data(data))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -108,7 +108,7 @@ class TestMessageProtocol(unittest.TestCase):
|
|||
|
||||
def waitForSession(self, q, outbound=False):
|
||||
key = 'arq-transfer-outbound' if outbound else 'arq-transfer-inbound'
|
||||
while True:
|
||||
while True and self.channels_running:
|
||||
ev = q.get()
|
||||
if key in ev and ('success' in ev[key] or 'ABORTED' in ev[key]):
|
||||
self.logger.info(f"[{threading.current_thread().name}] {key} session ended.")
|
||||
|
@ -130,6 +130,7 @@ class TestMessageProtocol(unittest.TestCase):
|
|||
|
||||
def waitAndCloseChannels(self):
|
||||
self.waitForSession(self.iss_event_queue, True)
|
||||
self.channels_running = False
|
||||
self.waitForSession(self.irs_event_queue, False)
|
||||
self.channels_running = False
|
||||
|
||||
|
@ -139,7 +140,7 @@ class TestMessageProtocol(unittest.TestCase):
|
|||
|
||||
self.establishChannels()
|
||||
params = {
|
||||
'destination': "XX1XXX-1",
|
||||
'destination': "AA1AAA-1",
|
||||
'body': 'Hello World',
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class TestProtocols(unittest.TestCase):
|
|||
|
||||
def testPingWithAck(self):
|
||||
# Run ping command
|
||||
api_params = { "dxcall": "XX1XXX-6"}
|
||||
api_params = { "dxcall": "AA1AAA-1"}
|
||||
ping_cmd = PingCommand(self.config, self.state_manager, self.event_manager, api_params)
|
||||
#ping_cmd.run(self.event_queue, self.modem)
|
||||
frame = ping_cmd.test(self.event_queue)
|
||||
|
|
Loading…
Reference in a new issue