mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
- Rename all MODE.* config commands to STATION.* (including SET_GROUPS and SET_AVOID_ALLCALL) - Add @note API 2.6+ to each new @brief block (12 commands total) - Translate French comments to English * Move STATION commands to STATION section for Doxygen grouping Co-authored-by: BrunoKlu <brunoklu@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wyatt Miler <wmiler1@gmail.com>
797 lines
27 KiB
C++
797 lines
27 KiB
C++
/**
|
|
* @file networkMessage.cpp
|
|
* @brief member function of the UI_Constructor class
|
|
* API commands for external control and data retrieval.
|
|
* sends data to external clients via the Network Message API
|
|
* defgroup API Network Message API
|
|
*/
|
|
|
|
#include "JS8_UI/mainwindow.h"
|
|
|
|
/**
|
|
* @brief Processes an incoming API network message
|
|
* This function acts as the primary router for the JS8Call API. It handles
|
|
* RIG, STATION, RX, and TX commands by either updating the application
|
|
* state or querying current values to send back to the client.
|
|
*
|
|
* @param message The network message to process
|
|
*/
|
|
void UI_Constructor::networkMessage(Message const &message) {
|
|
auto type = message.type();
|
|
|
|
if (type == "PING") {
|
|
return;
|
|
}
|
|
|
|
auto id = message.id();
|
|
|
|
qCDebug(mainwindow_js8) << "try processing network message" << type << id;
|
|
|
|
// Inspired by FLDigi
|
|
// TODO: MAIN.RX - Turn on RX
|
|
// TODO: MAIN.TX - Transmit
|
|
// TODO: MAIN.AUTO - Auto
|
|
// TODO: MAIN.HB - HB
|
|
|
|
// RIG.GET_PTT - Returns PTT status
|
|
// RIG.SET_TUNE - Turns TUNE on and off
|
|
// RIG.TX_HALT - Stops transmission immediately
|
|
// RIG.GET_FREQ - Get the current Frequency
|
|
// RIG.SET_FREQ - Set the current Frequency
|
|
/**
|
|
* @name RIG Commands
|
|
* RIG related API calls
|
|
*/
|
|
/** @{ */
|
|
|
|
/** @brief RIG.GET_PTT
|
|
* Returns the PTT status
|
|
* @note API 2.6+
|
|
*/
|
|
if (type == "RIG.GET_PTT") {
|
|
bool isPTT = m_transmitting;
|
|
sendNetworkMessage("RIG.PTT_STATUS", "",
|
|
{
|
|
{"_ID", id},
|
|
{"PTT", QVariant(isPTT)},
|
|
{"MESSAGE", QVariant(isPTT ? m_currentMessage : "")}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief RIG.SET_TUNE
|
|
* Turns TUNE on and off
|
|
* @note API 2.6+
|
|
*/
|
|
if (type == "RIG.SET_TUNE") {
|
|
auto value = QVariant(message.value());
|
|
UI_Constructor::on_tuneButton_clicked(value.toBool());
|
|
sendNetworkMessage("RIG.SET_TUNE", "", {
|
|
{"_ID", id},
|
|
{"value", ui->tuneButton->isChecked()}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief RIG.TX_HALT
|
|
* Stops transmission immediately, consider this an E-stop for the rig
|
|
* @note API 2.6+
|
|
*/
|
|
if (type == "RIG.TX_HALT") {
|
|
auto value = QVariant(message.value());
|
|
UI_Constructor::on_stopTxButton_clicked();
|
|
sendNetworkMessage("RIG.TX_HALT", "", {
|
|
{"_ID", id},
|
|
{"value", ui->monitorTxButton->isChecked()}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief RIG.GET_FREQ: Retrieves the current dial and offset frequencies.
|
|
*
|
|
* If the WSJT-X protocol is enabled, this also triggers a status update
|
|
* via the @ref m_wsjtxMessageMapper.
|
|
*/
|
|
if (type == "RIG.GET_FREQ") {
|
|
// Send WSJT-X Status message if protocol is enabled
|
|
if (m_wsjtxMessageMapper && m_config.wsjtx_protocol_enabled()) {
|
|
QString dx_call = callsignSelected();
|
|
QString dx_grid = "";
|
|
if (!dx_call.isEmpty() && m_callActivity.contains(dx_call)) {
|
|
dx_grid = m_callActivity[dx_call].grid;
|
|
}
|
|
QString tx_message = m_transmitting ? m_currentMessage : "";
|
|
|
|
m_wsjtxMessageMapper->sendStatusUpdate(
|
|
dialFrequency(), freq(),
|
|
"JS8", // mode
|
|
dx_call, m_config.my_callsign(), m_config.my_grid(), dx_grid,
|
|
true, // tx_enabled
|
|
m_transmitting,
|
|
m_decoderBusy || m_monitoring, // decoding
|
|
tx_message);
|
|
}
|
|
|
|
// Send native JSON message only if not conflicting with WSJT-X
|
|
bool skip_json = false;
|
|
if (m_config.wsjtx_protocol_enabled() &&
|
|
m_config.wsjtx_server_port() == m_config.udp_server_port() &&
|
|
m_config.wsjtx_server_name() == m_config.udp_server_name()) {
|
|
skip_json = true;
|
|
}
|
|
|
|
if (!skip_json) {
|
|
sendNetworkMessage(
|
|
"RIG.FREQ", "",
|
|
{{"_ID", id},
|
|
{"FREQ", QVariant((quint64)dialFrequency() + freq())},
|
|
{"DIAL", QVariant((quint64)dialFrequency())},
|
|
{"OFFSET", QVariant((quint64)freq())}});
|
|
}
|
|
return;
|
|
}
|
|
/**
|
|
* @brief RIG.SET_FREQ: Updates the rig dial frequency and/or frequency
|
|
* offset.
|
|
*/
|
|
if (type == "RIG.SET_FREQ") {
|
|
auto params = message.params();
|
|
if (params.contains("DIAL")) {
|
|
bool ok = false;
|
|
auto f = params["DIAL"].toInt(&ok);
|
|
if (ok) {
|
|
setRig(f);
|
|
displayDialFrequency();
|
|
}
|
|
}
|
|
if (params.contains("OFFSET")) {
|
|
bool ok = false;
|
|
auto f = params["OFFSET"].toInt(&ok);
|
|
if (ok) {
|
|
setFreqOffsetForRestore(f, false);
|
|
}
|
|
}
|
|
}
|
|
/** @} */ // End RIG Commands
|
|
|
|
// STATION refers to JS8Call station settings
|
|
// STATION.GET_CALLSIGN - Get the current callsign
|
|
// STATION.GET_GRID - Get the current grid locator
|
|
// STATION.SET_GRID - Set the current grid locator
|
|
// STATION.GET_INFO - Get the current station qth
|
|
// STATION.SET_INFO - Set the current station qth
|
|
// STATION.GET_SPOT - Get the current spotting status
|
|
// STATION.SET_SPOT - Set the current spotting status
|
|
// STATION.GET_OS - Get basic info about the OS we are running on
|
|
// STATION.VERSION - Get the JS8Call version
|
|
// STATION.GET_CONFIG - Get all config states (auto_reply, js8hb, hback, etc.)
|
|
// STATION.SET_AUTO_REPLY - Toggle auto-reply on/off
|
|
// STATION.SET_JS8HB - Toggle JS8 heartbeat on/off
|
|
// STATION.SET_HBACK - Toggle heartbeat acknowledgments on/off
|
|
// STATION.SET_MULTI_DECODER - Toggle multi-decoder on/off
|
|
// STATION.SET_HB_INTERVAL - Set heartbeat interval (seconds)
|
|
// STATION.SET_HB_TIMER - Start/stop heartbeat timer
|
|
// STATION.SEND_HB - Send heartbeat immediately
|
|
// STATION.SET_AUTOREPLY_CONFIRMATION - Toggle autoreply confirmation via TCP
|
|
// STATION.AUTOREPLY_CONFIRM_RESPONSE - Accept/reject pending autoreply
|
|
// STATION.SET_GROUPS - Replace subscribed groups list
|
|
// STATION.SET_AVOID_ALLCALL - Toggle @ALLCALL opt-out
|
|
/**
|
|
* @name STATION Commands
|
|
* STATION related API calls
|
|
* These calls refer to JS8Call station settings
|
|
*/
|
|
/** @{ */
|
|
|
|
/** @brief STATION.GET_CALLSIGN: Returns the configured station callsign. */
|
|
if (type == "STATION.GET_CALLSIGN") {
|
|
sendNetworkMessage("STATION.CALLSIGN", m_config.my_callsign(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.GET_GRID: Returns the current Maidenhead grid locator. */
|
|
if (type == "STATION.GET_GRID") {
|
|
sendNetworkMessage("STATION.GRID", m_config.my_grid(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.SET_GRID: Updates the dynamic grid locator for the
|
|
* station. */
|
|
if (type == "STATION.SET_GRID") {
|
|
m_config.set_dynamic_location(message.value());
|
|
sendNetworkMessage("STATION.GRID", m_config.my_grid(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.GET_INFO: Retrieves the station information (QTH). */
|
|
if (type == "STATION.GET_INFO") {
|
|
sendNetworkMessage("STATION.INFO", m_config.my_info(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.SET_INFO: Updates the dynamic station information (QTH).
|
|
*/
|
|
if (type == "STATION.SET_INFO") {
|
|
m_config.set_dynamic_station_info(message.value());
|
|
sendNetworkMessage("STATION.INFO", m_config.my_info(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.GET_STATUS: Retrieves the current station status message.
|
|
*/
|
|
if (type == "STATION.GET_STATUS") {
|
|
sendNetworkMessage("STATION.STATUS", m_config.my_status(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief STATION.SET_STATUS: Updates the dynamic station status message.
|
|
*/
|
|
if (type == "STATION.SET_STATUS") {
|
|
m_config.set_dynamic_station_status(message.value());
|
|
sendNetworkMessage("STATION.STATUS", m_config.my_status(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.VERSION
|
|
* Returns the JS8Call version
|
|
* @note API 2.6+
|
|
*/
|
|
if (type == "STATION.VERSION") {
|
|
QString ver = version();
|
|
sendNetworkMessage("STATION.VERSION", "",
|
|
{
|
|
{"_ID", id},
|
|
{"VERSION", QVariant(ver)}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.GET_OS
|
|
* Returns OS information for the station
|
|
* @note API 2.6+
|
|
*
|
|
* Thanks to N0GQ Jeff Francis
|
|
*/
|
|
if(type == "STATION.GET_OS"){
|
|
sendNetworkMessage("STATION.GET_OS", "", {
|
|
{"OS_NAME", QSysInfo::prettyProductName()},
|
|
{"OS_KERNEL", QSysInfo::kernelType()},
|
|
{"OS_KERNEL_VERSION", QSysInfo::kernelVersion()},
|
|
{"_ID", id}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.GET_SPOT
|
|
* Get the current spotting status
|
|
* @note API 2.6+
|
|
*
|
|
* Thanks to N0GQ Jeff Francis
|
|
*/
|
|
if(type == "STATION.GET_SPOT") {
|
|
sendNetworkMessage("STATION.SPOT", "", {
|
|
{"value", ui->spotButton->isChecked()},
|
|
{"_ID", id}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_SPOT
|
|
* Set the current spotting status
|
|
* @note API 2.6+
|
|
*
|
|
* Thanks to N0GQ Jeff Francis
|
|
*/
|
|
if(type == "STATION.SET_SPOT") {
|
|
auto value = QVariant(message.value());
|
|
UI_Constructor::on_spotButton_clicked(value.toBool());
|
|
sendNetworkMessage("STATION.SPOT", "", {
|
|
{"value", ui->spotButton->isChecked()},
|
|
{"_ID", id}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.GET_CONFIG: Returns all mode/config states for remote control.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.GET_CONFIG") {
|
|
QVariantList groupsList;
|
|
for (auto const &g : m_config.my_groups()) {
|
|
groupsList.append(g);
|
|
}
|
|
sendNetworkMessage("STATION.CONFIG", "", {
|
|
{"_ID", id},
|
|
{"AUTO_REPLY", QVariant(ui->actionModeAutoreply->isChecked())},
|
|
{"JS8HB", QVariant(ui->actionModeJS8HB->isChecked())},
|
|
{"HBACK", QVariant(ui->actionHeartbeatAcknowledgements->isChecked())},
|
|
{"MULTI_DECODER", QVariant(ui->actionModeMultiDecoder->isChecked())},
|
|
{"HB_INTERVAL", QVariant(m_hbInterval)},
|
|
{"HB_TIMER_ACTIVE", QVariant(m_hb_loop->isActive())},
|
|
{"MONITOR", QVariant(ui->monitorButton->isChecked())},
|
|
{"TX_ENABLED", QVariant(ui->monitorTxButton->isChecked())},
|
|
{"SPEED", QVariant(m_nSubMode)},
|
|
{"CAN_HB", QVariant(canCurrentModeSendHeartbeat())},
|
|
{"AUTOREPLY_CONFIRMATION", QVariant(m_config.autoreply_confirmation())},
|
|
{"MY_GROUPS", QVariant(groupsList)},
|
|
{"AVOID_ALLCALL", QVariant(m_config.avoid_allcall())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_AUTO_REPLY: Toggle auto-reply mode.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_AUTO_REPLY") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
ui->actionModeAutoreply->setChecked(checked);
|
|
sendNetworkMessage("STATION.SET_AUTO_REPLY", "", {
|
|
{"_ID", id},
|
|
{"AUTO_REPLY", QVariant(ui->actionModeAutoreply->isChecked())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_JS8HB: Toggle heartbeat networking mode.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_JS8HB") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
ui->actionModeJS8HB->setChecked(checked);
|
|
sendNetworkMessage("STATION.SET_JS8HB", "", {
|
|
{"_ID", id},
|
|
{"JS8HB", QVariant(ui->actionModeJS8HB->isChecked())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_HBACK: Toggle heartbeat acknowledgments.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_HBACK") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
ui->actionHeartbeatAcknowledgements->setChecked(checked);
|
|
sendNetworkMessage("STATION.SET_HBACK", "", {
|
|
{"_ID", id},
|
|
{"HBACK", QVariant(ui->actionHeartbeatAcknowledgements->isChecked())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_MULTI_DECODER: Toggle multi-decoder mode.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_MULTI_DECODER") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
ui->actionModeMultiDecoder->setChecked(checked);
|
|
sendNetworkMessage("STATION.SET_MULTI_DECODER", "", {
|
|
{"_ID", id},
|
|
{"MULTI_DECODER", QVariant(ui->actionModeMultiDecoder->isChecked())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_HB_INTERVAL: Set heartbeat interval in minutes.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_HB_INTERVAL") {
|
|
auto interval = message.params().value("INTERVAL").toInt();
|
|
m_hbInterval = interval;
|
|
if (ui->hbMacroButton->isChecked() && interval > 0) {
|
|
m_hb_loop->onTxLoopPeriodChangeStart(interval * (qint64)60000);
|
|
}
|
|
updateHBButtonDisplay();
|
|
sendNetworkMessage("STATION.SET_HB_INTERVAL", "", {
|
|
{"_ID", id},
|
|
{"HB_INTERVAL", QVariant(m_hbInterval)},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_HB_TIMER: Start/stop heartbeat timer loop.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_HB_TIMER") {
|
|
auto start = QVariant(message.value()).toBool();
|
|
ui->hbMacroButton->setChecked(start);
|
|
sendNetworkMessage("STATION.SET_HB_TIMER", "", {
|
|
{"_ID", id},
|
|
{"HB_TIMER_ACTIVE", QVariant(ui->hbMacroButton->isChecked())},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SEND_HB: Send an immediate heartbeat.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SEND_HB") {
|
|
sendHB();
|
|
sendNetworkMessage("STATION.SEND_HB", "", {
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_AUTOREPLY_CONFIRMATION: Toggle autoreply confirmation via TCP.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_AUTOREPLY_CONFIRMATION") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
m_config.set_autoreply_confirmation(checked);
|
|
sendNetworkMessage("STATION.AUTOREPLY_CONFIRMATION", "",
|
|
{{"_ID", id},
|
|
{"AUTOREPLY_CONFIRMATION", QVariant(checked)}});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.AUTOREPLY_CONFIRM_RESPONSE: Accept/reject a pending autoreply confirmation.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.AUTOREPLY_CONFIRM_RESPONSE") {
|
|
auto confirmId = message.params().value("CONFIRM_ID").toInt();
|
|
auto accepted = QVariant(message.value()).toBool();
|
|
// CRITICAL: m_pendingConfirmations lives in the GUI thread
|
|
QMetaObject::invokeMethod(this, [this, confirmId, accepted]() {
|
|
if (m_pendingConfirmations.contains(confirmId)) {
|
|
auto pc = m_pendingConfirmations.take(confirmId);
|
|
pc.timer->stop();
|
|
delete pc.timer;
|
|
if (accepted) {
|
|
enqueueMessage(pc.priority, pc.message, pc.offset, pc.callback);
|
|
}
|
|
sendNetworkMessage("STATION.AUTOREPLY_CONFIRMED", "",
|
|
{{"_ID", QVariant(-1)},
|
|
{"CONFIRM_ID", QVariant(confirmId)},
|
|
{"ACCEPTED", QVariant(accepted)},
|
|
{"MESSAGE", QVariant(pc.message)}});
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_GROUPS: Replace the subscribed groups list via TCP.
|
|
* Validates each group (isGroupAllowed + isCompoundCallsign) and returns
|
|
* a TCP error instead of opening a MessageBox (headless-safe).
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_GROUPS") {
|
|
QStringList newGroups;
|
|
auto groupsVar = message.params().value("GROUPS");
|
|
if (groupsVar.canConvert<QVariantList>()) {
|
|
for (auto const &v : groupsVar.toList()) {
|
|
auto g = v.toString().trimmed();
|
|
if (g.isEmpty() || !g.startsWith("@")) continue;
|
|
if (!Varicode::isGroupAllowed(g)) {
|
|
sendNetworkMessage("STATION.SET_GROUPS", "", {
|
|
{"_ID", id},
|
|
{"ERROR", QString("Group not allowed: %1").arg(g)},
|
|
});
|
|
return;
|
|
}
|
|
if (!Varicode::isCompoundCallsign(g)) {
|
|
sendNetworkMessage("STATION.SET_GROUPS", "", {
|
|
{"_ID", id},
|
|
{"ERROR", QString("Invalid group name: %1").arg(g)},
|
|
});
|
|
return;
|
|
}
|
|
newGroups.append(g);
|
|
}
|
|
}
|
|
m_config.setMyGroups(newGroups);
|
|
|
|
QVariantList result;
|
|
for (auto const &g : m_config.my_groups()) {
|
|
result.append(g);
|
|
}
|
|
sendNetworkMessage("STATION.SET_GROUPS", "", {
|
|
{"_ID", id},
|
|
{"GROUPS", QVariant(result)},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @brief STATION.SET_AVOID_ALLCALL: Toggle @ALLCALL opt-out via TCP.
|
|
* @note API 2.6+ */
|
|
if (type == "STATION.SET_AVOID_ALLCALL") {
|
|
auto checked = QVariant(message.value()).toBool();
|
|
m_config.set_avoid_allcall(checked);
|
|
sendNetworkMessage("STATION.SET_AVOID_ALLCALL", "", {
|
|
{"_ID", id},
|
|
{"AVOID_ALLCALL", QVariant(m_config.avoid_allcall())},
|
|
});
|
|
return;
|
|
}
|
|
/** @} */ // End STATION Commands
|
|
|
|
// RX.GET_CALL_ACTIVITY
|
|
// RX.GET_CALL_SELECTED
|
|
// RX.GET_BAND_ACTIVITY
|
|
// RX.GET_TEXT
|
|
/**
|
|
* @name RX Commands
|
|
* RX related API calls
|
|
* Refers to received data and activity
|
|
*/
|
|
/** @{ */
|
|
|
|
/**
|
|
* @brief RX.GET_CALL_ACTIVITY: Returns a list of active callsigns.
|
|
* Filters results based on the `callsign_aging` configuration.
|
|
*/
|
|
if (type == "RX.GET_CALL_ACTIVITY") {
|
|
auto now = DriftingDateTime::currentDateTimeUtc();
|
|
int callsignAging = m_config.callsign_aging();
|
|
QVariantMap calls = {
|
|
{"_ID", id},
|
|
};
|
|
|
|
foreach (auto cd, m_callActivity.values()) {
|
|
if (callsignAging &&
|
|
cd.utcTimestamp.secsTo(now) / 60 >= callsignAging) {
|
|
continue;
|
|
}
|
|
QVariantMap detail;
|
|
detail["SNR"] = QVariant(cd.snr);
|
|
detail["GRID"] = QVariant(cd.grid);
|
|
detail["UTC"] = QVariant(cd.utcTimestamp.toMSecsSinceEpoch());
|
|
calls[cd.call] = QVariant(detail);
|
|
}
|
|
|
|
sendNetworkMessage("RX.CALL_ACTIVITY", "", calls);
|
|
return;
|
|
}
|
|
/** @brief RX.GET_CALL_SELECTED: Returns the currently selected callsign. */
|
|
if (type == "RX.GET_CALL_SELECTED") {
|
|
sendNetworkMessage("RX.CALL_SELECTED", callsignSelected(),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/**
|
|
* @brief RX.GET_BAND_ACTIVITY: Returns recent band activity details.
|
|
* Includes frequency, offset, text, SNR, and UTC timestamp for each entry.
|
|
*/
|
|
if (type == "RX.GET_BAND_ACTIVITY") {
|
|
QVariantMap offsets = {
|
|
{"_ID", id},
|
|
};
|
|
for (auto const [offset, activity] : m_bandActivity.asKeyValueRange()) {
|
|
if (activity.isEmpty())
|
|
continue;
|
|
|
|
auto const d = activity.last();
|
|
|
|
offsets[QString("%1").arg(offset)] = QVariant(QVariantMap{
|
|
{"FREQ", QVariant(d.dial + d.offset)},
|
|
{"DIAL", QVariant(d.dial)},
|
|
{"OFFSET", QVariant(d.offset)},
|
|
{"TEXT", QVariant(d.text)},
|
|
{"SNR", QVariant(d.snr)},
|
|
{"UTC", QVariant(d.utcTimestamp.toMSecsSinceEpoch())}});
|
|
}
|
|
|
|
sendNetworkMessage("RX.BAND_ACTIVITY", "", offsets);
|
|
return;
|
|
}
|
|
/** @brief RX.GET_TEXT: Retrieves the current RX text buffer. */
|
|
if (type == "RX.GET_TEXT") { /** RX.GET_TEXT */
|
|
sendNetworkMessage("RX.TEXT", ui->textEditRX->toPlainText().right(1024),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @} */ // End RX Commands
|
|
|
|
// TX.GET_TEXT - Retrieves the current TX text buffer.
|
|
// TX.SET_TEXT - Updates the TX text buffer with new content.
|
|
// TX.SEND_MESSAGE - Enqueues a message for transmission.
|
|
// TX.GET_QUEUE_DEPTH - Return the number of items in the transmit queue.
|
|
/**
|
|
* @name TX Commands
|
|
* TX related API calls
|
|
* Refers to transmitted data and activity
|
|
*/
|
|
/** @{ */
|
|
|
|
/** @brief TX.GET_TEXT: Retrieves the current TX text buffer. */
|
|
if (type == "TX.GET_TEXT") {
|
|
sendNetworkMessage("TX.TEXT",
|
|
ui->extFreeTextMsgEdit->toPlainText().right(1024),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief TX.SET_TEXT: Updates the TX text buffer with new content. */
|
|
if (type == "TX.SET_TEXT") {
|
|
addMessageText(message.value(), true);
|
|
sendNetworkMessage("TX.TEXT",
|
|
ui->extFreeTextMsgEdit->toPlainText().right(1024),
|
|
{
|
|
{"_ID", id},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief TX.SEND_MESSAGE: Enqueues a message for transmission. */
|
|
if (type == "TX.SEND_MESSAGE") {
|
|
auto text = message.value();
|
|
if (!text.isEmpty()) {
|
|
enqueueMessage(PriorityNormal, text, -1, nullptr);
|
|
processTxQueue();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Return the number of items in the transmit queue.
|
|
* @note API 2.6+
|
|
*
|
|
* Thanks to N0GQ Jeff Francis
|
|
*/
|
|
if(type == "TX.GET_QUEUE_DEPTH"){
|
|
int depth = m_txMessageQueue.size();
|
|
if(m_transmitting && depth==0) depth=1;
|
|
sendNetworkMessage("TX.QUEUE_DEPTH", "", {
|
|
{"_ID", id},
|
|
{"DEPTH", QVariant(depth)}
|
|
});
|
|
return;
|
|
}
|
|
/** @} */ // End TX Commands
|
|
|
|
// MODE.GET_SPEED
|
|
// MODE.SET_SPEED
|
|
/**
|
|
* @name MODE Commands
|
|
* MODE related API calls
|
|
*/
|
|
/** @{ */
|
|
/** @brief MODE.GET_SPEED: Retrieves the current transmission speed mode. */
|
|
if (type == "MODE.GET_SPEED") {
|
|
sendNetworkMessage("MODE.SPEED", "",
|
|
{
|
|
{"_ID", id},
|
|
{"SPEED", m_nSubMode},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief MODE.SET_SPEED: Updates the transmission speed mode. */
|
|
if (type == "MODE.SET_SPEED") {
|
|
auto ok = false;
|
|
auto const speed =
|
|
message.params().value("SPEED", QVariant(m_nSubMode)).toInt(&ok);
|
|
if (ok) {
|
|
if (speed == Varicode::JS8CallNormal)
|
|
ui->actionModeJS8Normal->setChecked(true);
|
|
else if (speed == Varicode::JS8CallFast)
|
|
ui->actionModeJS8Fast->setChecked(true);
|
|
else if (speed == Varicode::JS8CallTurbo)
|
|
ui->actionModeJS8Turbo->setChecked(true);
|
|
else if (speed == Varicode::JS8CallSlow)
|
|
ui->actionModeJS8Slow->setChecked(true);
|
|
else if (speed == Varicode::JS8CallUltra)
|
|
ui->actionModeJS8Ultra->setChecked(true);
|
|
setupJS8();
|
|
}
|
|
sendNetworkMessage("MODE.SET_SPEED", "",
|
|
{
|
|
{"_ID", id},
|
|
{"SPEED", m_nSubMode},
|
|
});
|
|
return;
|
|
}
|
|
|
|
/** @} */ // End MODE Commands
|
|
|
|
// INBOX.GET_MESSAGES
|
|
// INBOX.STORE_MESSAGE
|
|
/**
|
|
* @name INBOX Commands
|
|
* INBOX related API calls
|
|
*/
|
|
/** @{ */
|
|
/** @brief INBOX.GET_MESSAGES: Retrieves messages for a specified callsign. */
|
|
if (type == "INBOX.GET_MESSAGES") {
|
|
QString selectedCall =
|
|
message.params().value("CALLSIGN", "").toString();
|
|
if (selectedCall.isEmpty()) {
|
|
selectedCall = "%";
|
|
}
|
|
|
|
Inbox inbox(inboxPath());
|
|
if (!inbox.open()) {
|
|
return;
|
|
}
|
|
|
|
QList<QPair<int, Message>> msgs;
|
|
msgs.append(
|
|
inbox.values("STORE", "$.params.TO", selectedCall, 0, 1000));
|
|
msgs.append(
|
|
inbox.values("READ", "$.params.FROM", selectedCall, 0, 1000));
|
|
foreach (auto pair, inbox.values("UNREAD", "$.params.FROM",
|
|
selectedCall, 0, 1000)) {
|
|
msgs.append(pair);
|
|
}
|
|
std::stable_sort(
|
|
msgs.begin(), msgs.end(),
|
|
[](QPair<int, Message> const &a, QPair<int, Message> const &b) {
|
|
return QVariant::compare(a.second.params().value("UTC"),
|
|
b.second.params().value("UTC")) ==
|
|
QPartialOrdering::Greater;
|
|
});
|
|
|
|
QVariantList l;
|
|
foreach (auto pair, msgs) {
|
|
l << pair.second.toVariantMap();
|
|
}
|
|
|
|
sendNetworkMessage("INBOX.MESSAGES", "",
|
|
{
|
|
{"_ID", id},
|
|
{"MESSAGES", l},
|
|
});
|
|
return;
|
|
}
|
|
/** @brief INBOX.STORE_MESSAGE: Stores a message in the inbox for a
|
|
* callsign. */
|
|
if (type == "INBOX.STORE_MESSAGE") {
|
|
QString selectedCall =
|
|
message.params().value("CALLSIGN", "").toString();
|
|
if (selectedCall.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QString text = message.params().value("TEXT", "").toString();
|
|
if (text.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
CommandDetail d = {};
|
|
d.cmd = " MSG ";
|
|
d.to = selectedCall;
|
|
d.from = m_config.my_callsign();
|
|
d.relayPath = d.from;
|
|
d.text = text;
|
|
d.utcTimestamp = DriftingDateTime::currentDateTimeUtc();
|
|
d.submode = m_nSubMode;
|
|
|
|
auto mid = addCommandToStorage("STORE", d);
|
|
|
|
sendNetworkMessage("INBOX.MESSAGE", "",
|
|
{
|
|
{"_ID", id},
|
|
{"ID", mid},
|
|
});
|
|
return;
|
|
}
|
|
/** @} */ // End INBOX Commands
|
|
|
|
// WINDOW.RAISE
|
|
/**
|
|
* @name WINDOW Commands
|
|
* WINDOW related API calls
|
|
*/
|
|
/** @{ */
|
|
/** @brief WINDOW.RAISE: Brings the main application window to the
|
|
* foreground.
|
|
* NOTE: Some OSes block this from happening
|
|
*/
|
|
if (type == "WINDOW.RAISE") {
|
|
setWindowState(Qt::WindowActive);
|
|
activateWindow();
|
|
raise();
|
|
return;
|
|
}
|
|
|
|
qCDebug(mainwindow_js8) << "Unable to process networkMessage:" << type;
|
|
}
|
|
/** @} */ // end of WINDOW Commands
|
|
/** @} */ // end of API
|