2018-07-12 09:54:56 -04:00
|
|
|
/**
|
2018-10-04 13:52:52 -04:00
|
|
|
* This file is part of JS8Call.
|
2018-07-13 21:59:54 -04:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*
|
2018-07-12 09:54:56 -04:00
|
|
|
* (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
|
2018-07-13 21:59:54 -04:00
|
|
|
*
|
2018-07-12 09:54:56 -04:00
|
|
|
**/
|
2018-07-13 21:59:54 -04:00
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
#include <QDebug>
|
|
|
|
#include <QMap>
|
2018-07-15 13:03:16 -04:00
|
|
|
#include <QSet>
|
2018-07-12 09:54:56 -04:00
|
|
|
|
2018-07-14 22:05:08 -04:00
|
|
|
#define CRCPP_INCLUDE_ESOTERIC_CRC_DEFINITIONS
|
|
|
|
#include "crc.h"
|
|
|
|
|
2018-07-11 23:09:22 -04:00
|
|
|
#include "varicode.h"
|
2018-10-01 09:57:37 -04:00
|
|
|
#include "jsc.h"
|
2018-11-01 02:19:48 -04:00
|
|
|
#include "decodedtext.h"
|
2018-07-11 23:09:22 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
#include <cmath>
|
|
|
|
|
2018-07-12 09:54:56 -04:00
|
|
|
const int nalphabet = 41;
|
2018-08-01 11:25:45 -04:00
|
|
|
QString alphabet = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"}; // alphabet to encode _into_ for FT8 freetext transmission
|
2018-08-06 09:37:43 -04:00
|
|
|
QString alphabet72 = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+/?."};
|
2018-08-27 21:19:38 -04:00
|
|
|
QString grid_pattern = {R"((?<grid>[A-X]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*)+)"};
|
2018-07-25 20:24:22 -04:00
|
|
|
QString orig_compound_callsign_pattern = {R"((?<callsign>(\d|[A-Z])+\/?((\d|[A-Z]){2,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?))"};
|
2019-02-21 12:42:46 -05:00
|
|
|
QString base_callsign_pattern = {R"((?<callsign>\b(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<portable>[/][P])?\b))"};
|
2018-10-25 22:55:54 -04:00
|
|
|
//QString compound_callsign_pattern = {R"((?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
|
2018-10-26 16:57:07 -04:00
|
|
|
QString compound_callsign_pattern = {R"((?<callsign>(?:[@]?|\b)(?<extended>[A-Z0-9\/@][A-Z0-9\/]{0,2}[\/]?[A-Z0-9\/]{0,3}[\/]?[A-Z0-9\/]{0,3})\b))"};
|
2018-07-24 02:47:14 -04:00
|
|
|
QString pack_callsign_pattern = {R"(([0-9A-Z ])([0-9A-Z])([0-9])([A-Z ])([A-Z ])([A-Z ]))"};
|
2018-10-25 22:55:54 -04:00
|
|
|
QString alphanumeric = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ /@"}; // callsign and grid alphabet
|
2018-07-15 13:03:16 -04:00
|
|
|
|
|
|
|
QMap<QString, int> directed_cmds = {
|
|
|
|
// any changes here need to be made also in the directed regular xpression for parsing
|
2018-10-30 21:00:04 -04:00
|
|
|
// ?*^&@
|
2019-02-01 01:46:01 -05:00
|
|
|
{" HB", -1 }, // this is my heartbeat (unused except for faux processing of HBs as directed commands)
|
2018-07-20 23:17:49 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" SNR?", 0 }, // query snr
|
|
|
|
{"?", 0 }, // compat
|
2018-10-30 21:00:04 -04:00
|
|
|
|
2019-03-04 23:25:17 -05:00
|
|
|
{" DIT DIT", 1 }, // unused
|
2019-03-03 23:33:30 -05:00
|
|
|
|
|
|
|
{" NACK", 2 }, // negative acknowledge
|
2018-10-30 21:00:04 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" HEARING?", 3 }, // query station calls heard
|
2018-10-30 21:00:04 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" GRID?", 4 }, // query grid
|
2018-11-20 10:09:07 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{">", 5 }, // relay message
|
2018-10-30 21:00:04 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" STATUS?", 6 }, // query idle message
|
2018-11-20 10:09:07 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" STATUS", 7 }, // this is my status
|
2018-10-28 12:37:47 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" HEARING", 8 }, // these are the stations i'm hearing
|
2018-12-31 20:03:51 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" MSG", 9 }, // this is a complete message
|
2018-11-03 22:38:27 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" MSG TO:", 10 }, // store message at a station
|
2018-10-03 14:50:25 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" QUERY", 11 }, // generic query
|
2018-12-17 00:54:54 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" QUERY MSGS", 12 }, // do you have any stored messages?
|
2019-02-11 18:44:35 -05:00
|
|
|
{" QUERY MSGS?", 12 }, // do you have any stored messages?
|
2019-01-23 20:31:26 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" QUERY CALL", 13 }, // can you transmit a ping to callsign?
|
2019-01-23 20:31:26 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" APRS:", 14 }, // send an aprs packet
|
2018-11-25 22:12:54 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" GRID", 15 }, // this is my current grid locator
|
2018-09-21 01:47:21 -04:00
|
|
|
|
2019-02-08 16:57:42 -05:00
|
|
|
{" INFO?", 16 }, // what is your info message?
|
|
|
|
{" INFO", 17 }, // this is my info message
|
2018-09-21 01:47:21 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" FB", 18 }, // fine business
|
|
|
|
{" HW CPY?", 19 }, // how do you copy?
|
|
|
|
{" SK", 20 }, // end of contact
|
|
|
|
{" RR", 21 }, // roger roger
|
2019-01-31 10:53:17 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" QSL?", 22 }, // do you copy?
|
|
|
|
{" QSL", 23 }, // i copy
|
2019-01-31 10:53:17 -05:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" CMD", 24 }, // command
|
2018-09-21 01:47:21 -04:00
|
|
|
|
2019-02-01 01:46:01 -05:00
|
|
|
{" SNR", 25 }, // seen a station at the provided snr
|
|
|
|
{" NO", 26 }, // negative confirm
|
|
|
|
{" YES", 27 }, // confirm
|
|
|
|
{" 73", 28 }, // best regards, end of contact
|
|
|
|
{" ACK", 29 }, // acknowledge
|
|
|
|
{" AGN?", 30 }, // repeat message
|
|
|
|
{" ", 31 }, // send freetext (weird artifact)
|
|
|
|
{" ", 31 }, // send freetext
|
2018-07-15 13:03:16 -04:00
|
|
|
};
|
|
|
|
|
2019-01-20 23:08:57 -05:00
|
|
|
// commands allowed to be processed
|
2019-03-04 23:25:17 -05:00
|
|
|
QSet<int> allowed_cmds = {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
|
2018-09-21 01:47:21 -04:00
|
|
|
|
2019-01-31 14:04:44 -05:00
|
|
|
// commands that result in an autoreply (which can be relayed)
|
2019-02-01 03:08:47 -05:00
|
|
|
QSet<int> autoreply_cmds = {0, 3, 4, 6, 9, 10, 11, 12, 13, 16, 30};
|
2019-01-20 23:08:57 -05:00
|
|
|
|
|
|
|
// commands that should be buffered
|
2019-02-01 01:47:36 -05:00
|
|
|
QSet<int> buffered_cmds = {5, 9, 10, 11, 12, 13, 14, 15, 24};
|
2018-07-15 13:03:16 -04:00
|
|
|
|
2019-01-20 23:08:57 -05:00
|
|
|
// commands that may include an SNR value
|
2018-11-25 22:12:54 -05:00
|
|
|
QSet<int> snr_cmds = {25, 29};
|
2018-07-25 22:49:19 -04:00
|
|
|
|
2019-01-20 23:08:57 -05:00
|
|
|
// commands that are checksummed and their crc size
|
2018-08-27 22:04:17 -04:00
|
|
|
QMap<int, int> checksum_cmds = {
|
2018-09-08 23:36:12 -04:00
|
|
|
{ 5, 16 },
|
2019-02-01 01:46:01 -05:00
|
|
|
{ 9, 16 },
|
2019-01-23 20:31:26 -05:00
|
|
|
{ 10, 16 },
|
|
|
|
{ 11, 16 },
|
2018-12-31 20:03:51 -05:00
|
|
|
{ 12, 16 },
|
2018-08-29 12:08:22 -04:00
|
|
|
{ 13, 16 },
|
2018-08-28 15:13:38 -04:00
|
|
|
{ 14, 16 },
|
2019-01-31 14:04:44 -05:00
|
|
|
{ 15, 0 },
|
|
|
|
{ 24, 16 }
|
2018-08-27 22:04:17 -04:00
|
|
|
};
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
QString callsign_pattern = QString("(?<callsign>[@]?[A-Z0-9/]+)");
|
2019-03-04 23:25:17 -05:00
|
|
|
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|INFO[?]|GRID[?]|STATUS[?]|QUERY MSGS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|NACK|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|INFO|GRID|DIT DIT)(?=[ ]|$))|[?> ]))?");
|
2018-08-03 17:07:36 -04:00
|
|
|
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
|
2018-08-11 10:06:50 -04:00
|
|
|
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
|
2019-03-03 23:33:30 -05:00
|
|
|
QString optional_num_pattern = QString("(?<num>(?<=SNR|\\bACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
|
2018-08-03 17:07:36 -04:00
|
|
|
|
|
|
|
QRegularExpression directed_re("^" +
|
|
|
|
callsign_pattern +
|
|
|
|
optional_cmd_pattern +
|
|
|
|
optional_num_pattern);
|
2018-07-12 15:14:41 -04:00
|
|
|
|
2019-06-06 14:34:38 -04:00
|
|
|
QRegularExpression heartbeat_re(R"(^\s*(?<type>CQ CQ CQ|CQ DX|CQ QRP|CQ CONTEST|CQ FIELD|CQ FD|CQ CQ|CQ|HB( AUTO)?( RELAY)?( SPOT)?)(?:\s(?<grid>[A-R]{2}[0-9]{2}))?\b)");
|
2018-08-02 01:38:47 -04:00
|
|
|
|
2018-10-16 16:11:28 -04:00
|
|
|
QRegularExpression compound_re("^\\s*[`]" +
|
2018-08-04 11:19:07 -04:00
|
|
|
callsign_pattern +
|
|
|
|
"(?<extra>" +
|
2018-10-16 16:11:28 -04:00
|
|
|
optional_grid_pattern + // there's a reason this is first (see: buildMessageFrames)
|
2018-08-04 11:19:07 -04:00
|
|
|
optional_cmd_pattern +
|
|
|
|
optional_num_pattern +
|
2018-10-16 16:11:28 -04:00
|
|
|
")");
|
2018-08-02 01:38:47 -04:00
|
|
|
|
2018-07-30 21:26:36 -04:00
|
|
|
QMap<QString, QString> hufftable = {
|
2018-07-12 18:02:54 -04:00
|
|
|
// char code weight
|
2018-10-03 12:36:45 -04:00
|
|
|
{ " " , "01" }, // 1.0
|
|
|
|
{ "E" , "100" }, // 0.5
|
|
|
|
{ "T" , "1101" }, // 0.333333333333
|
|
|
|
{ "A" , "0011" }, // 0.25
|
|
|
|
{ "O" , "11111" }, // 0.2
|
|
|
|
{ "I" , "11100" }, // 0.166666666667
|
|
|
|
{ "N" , "10111" }, // 0.142857142857
|
|
|
|
{ "S" , "10100" }, // 0.125
|
|
|
|
{ "H" , "00011" }, // 0.111111111111
|
|
|
|
{ "R" , "00000" }, // 0.1
|
|
|
|
{ "D" , "111011" }, // 0.0909090909091
|
|
|
|
{ "L" , "110011" }, // 0.0833333333333
|
|
|
|
{ "C" , "110001" }, // 0.0769230769231
|
|
|
|
{ "U" , "101101" }, // 0.0714285714286
|
|
|
|
{ "M" , "101011" }, // 0.0666666666667
|
|
|
|
{ "W" , "001011" }, // 0.0625
|
|
|
|
{ "F" , "001001" }, // 0.0588235294118
|
|
|
|
{ "G" , "000101" }, // 0.0555555555556
|
|
|
|
{ "Y" , "000011" }, // 0.0526315789474
|
|
|
|
{ "P" , "1111011" }, // 0.05
|
|
|
|
{ "B" , "1111001" }, // 0.047619047619
|
|
|
|
{ "." , "1110100" }, // 0.0434782608696
|
|
|
|
{ "V" , "1100101" }, // 0.0416666666667
|
|
|
|
{ "K" , "1100100" }, // 0.04
|
|
|
|
{ "-" , "1100001" }, // 0.0384615384615
|
|
|
|
{ "+" , "1100000" }, // 0.037037037037
|
|
|
|
{ "?" , "1011001" }, // 0.0344827586207
|
|
|
|
{ "!" , "1011000" }, // 0.0333333333333
|
|
|
|
{"\"" , "1010101" }, // 0.0322580645161
|
|
|
|
{ "X" , "1010100" }, // 0.03125
|
|
|
|
{ "0" , "0010101" }, // 0.030303030303
|
|
|
|
{ "J" , "0010100" }, // 0.0294117647059
|
|
|
|
{ "1" , "0010001" }, // 0.0285714285714
|
|
|
|
{ "Q" , "0010000" }, // 0.0277777777778
|
|
|
|
{ "2" , "0001001" }, // 0.027027027027
|
|
|
|
{ "Z" , "0001000" }, // 0.0263157894737
|
|
|
|
{ "3" , "0000101" }, // 0.025641025641
|
|
|
|
{ "5" , "0000100" }, // 0.025
|
|
|
|
{ "4" , "11110101" }, // 0.0243902439024
|
|
|
|
{ "9" , "11110100" }, // 0.0238095238095
|
|
|
|
{ "8" , "11110001" }, // 0.0232558139535
|
|
|
|
{ "6" , "11110000" }, // 0.0227272727273
|
|
|
|
{ "7" , "11101011" }, // 0.0222222222222
|
|
|
|
{ "/" , "11101010" }, // 0.0217391304348
|
2018-07-26 12:47:03 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
QChar ESC = '\\'; // Escape char
|
|
|
|
QChar EOT = '\x04'; // EOT char
|
2018-07-12 20:31:45 -04:00
|
|
|
|
2018-07-13 00:55:48 -04:00
|
|
|
quint32 nbasecall = 37 * 36 * 10 * 27 * 27 * 27;
|
2018-08-01 11:25:45 -04:00
|
|
|
quint16 nbasegrid = 180 * 180;
|
2018-08-03 17:07:36 -04:00
|
|
|
quint16 nusergrid = nbasegrid + 10;
|
2018-08-01 11:25:45 -04:00
|
|
|
quint16 nmaxgrid = (1<<15)-1;
|
2018-07-13 00:55:48 -04:00
|
|
|
|
|
|
|
QMap<QString, quint32> basecalls = {
|
2019-02-12 19:37:41 -05:00
|
|
|
{ "<....>", nbasecall + 1 }, // incomplete callsign
|
|
|
|
{ "@ALLCALL", nbasecall + 2 }, // ALLCALL group
|
|
|
|
{ "@JS8NET", nbasecall + 3 }, // JS8NET group
|
2019-02-12 19:50:22 -05:00
|
|
|
|
|
|
|
// continental dx
|
|
|
|
{ "@DX/NA", nbasecall + 4 }, // North America DX group
|
|
|
|
{ "@DX/SA", nbasecall + 5 }, // South America DX group
|
|
|
|
{ "@DX/EU", nbasecall + 6 }, // Europe DX group
|
|
|
|
{ "@DX/AS", nbasecall + 7 }, // Asia DX group
|
|
|
|
{ "@DX/AF", nbasecall + 8 }, // Africa DX group
|
|
|
|
{ "@DX/OC", nbasecall + 9 }, // Oceania DX group
|
|
|
|
{ "@DX/AN", nbasecall + 10 }, // Antarctica DX group
|
|
|
|
|
|
|
|
// itu regions
|
|
|
|
{ "@REGION/1", nbasecall + 11 }, // ITU Region 1
|
|
|
|
{ "@REGION/2", nbasecall + 12 }, // ITU Region 2
|
|
|
|
{ "@REGION/3", nbasecall + 13 }, // ITU Region 3
|
|
|
|
|
|
|
|
// generic
|
|
|
|
{ "@GROUP/0", nbasecall + 14 }, // Generic group
|
|
|
|
{ "@GROUP/1", nbasecall + 15 }, // Generic group
|
|
|
|
{ "@GROUP/2", nbasecall + 16 }, // Generic group
|
|
|
|
{ "@GROUP/3", nbasecall + 17 }, // Generic group
|
|
|
|
{ "@GROUP/4", nbasecall + 18 }, // Generic group
|
|
|
|
{ "@GROUP/5", nbasecall + 19 }, // Generic group
|
|
|
|
{ "@GROUP/6", nbasecall + 20 }, // Generic group
|
|
|
|
{ "@GROUP/7", nbasecall + 21 }, // Generic group
|
|
|
|
{ "@GROUP/8", nbasecall + 22 }, // Generic group
|
|
|
|
{ "@GROUP/9", nbasecall + 23 }, // Generic group
|
|
|
|
|
|
|
|
// ops
|
|
|
|
{ "@COMMAND", nbasecall + 24 }, // Command group
|
|
|
|
{ "@CONTROL", nbasecall + 25 }, // Control group
|
|
|
|
{ "@NET", nbasecall + 26 }, // Net group
|
|
|
|
{ "@NTS", nbasecall + 27 }, // NTS group
|
2018-07-13 00:55:48 -04:00
|
|
|
};
|
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QMap<quint32, QString> cqs = {
|
2019-06-06 14:34:38 -04:00
|
|
|
{ 0, "CQ CQ CQ" },
|
2018-09-06 15:22:24 -04:00
|
|
|
{ 1, "CQ DX" },
|
|
|
|
{ 2, "CQ QRP" },
|
2019-01-20 13:15:23 -05:00
|
|
|
{ 3, "CQ CONTEST" },
|
|
|
|
{ 4, "CQ FIELD" },
|
2019-06-06 14:34:38 -04:00
|
|
|
{ 5, "CQ FD"},
|
2018-09-20 10:04:18 -04:00
|
|
|
{ 6, "CQ CQ"},
|
2019-06-06 14:34:38 -04:00
|
|
|
{ 7, "CQ"},
|
2018-09-06 15:22:24 -04:00
|
|
|
};
|
|
|
|
|
2018-11-25 22:12:54 -05:00
|
|
|
QMap<quint32, QString> hbs = {
|
2019-01-14 10:15:45 -05:00
|
|
|
{ 0, "HB" }, // HB
|
|
|
|
{ 1, "HB AUTO" }, // HB AUTO
|
|
|
|
{ 2, "HB AUTO RELAY" }, // HB AUTO RELAY
|
|
|
|
{ 3, "HB AUTO RELAY SPOT" }, // HB AUTO RELAY SPOT
|
2019-02-08 10:03:41 -05:00
|
|
|
{ 7, "HB AUTO SPOT"}, // HB AUTO SPOT
|
2019-01-14 10:15:45 -05:00
|
|
|
{ 4, "HB RELAY" }, // HB RELAY
|
|
|
|
{ 5, "HB RELAY SPOT" }, // HB RELAY SPOT
|
|
|
|
{ 6, "HB SPOT" }, // HB SPOT
|
2018-11-25 22:12:54 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-07-21 02:18:15 -04:00
|
|
|
QMap<int, int> dbm2mw = {
|
2018-07-30 21:26:36 -04:00
|
|
|
{0 , 1}, // 1mW
|
|
|
|
{3 , 2}, // 2mW
|
|
|
|
{7 , 5}, // 5mW
|
|
|
|
{10 , 10}, // 10mW
|
|
|
|
{13 , 20}, // 20mW
|
|
|
|
{17 , 50}, // 50mW
|
|
|
|
{20 , 100}, // 100mW
|
|
|
|
{23 , 200}, // 200mW
|
|
|
|
{27 , 500}, // 500mW
|
2018-07-21 02:18:15 -04:00
|
|
|
{30 , 1000}, // 1W
|
|
|
|
{33 , 2000}, // 2W
|
|
|
|
{37 , 5000}, // 5W
|
|
|
|
{40 , 10000}, // 10W
|
|
|
|
{43 , 20000}, // 20W
|
|
|
|
{47 , 50000}, // 50W
|
|
|
|
{50 , 100000}, // 100W
|
|
|
|
{53 , 200000}, // 200W
|
|
|
|
{57 , 500000}, // 500W
|
|
|
|
{60 , 1000000}, // 1000W
|
|
|
|
};
|
|
|
|
|
2018-07-26 12:47:03 -04:00
|
|
|
/*
|
|
|
|
* UTILITIES
|
|
|
|
*/
|
|
|
|
|
2018-07-21 02:18:15 -04:00
|
|
|
int mwattsToDbm(int mwatts){
|
|
|
|
int dbm = 0;
|
|
|
|
auto values = dbm2mw.values();
|
|
|
|
qSort(values);
|
|
|
|
foreach(auto mw, values){
|
|
|
|
if(mw < mwatts){ continue; }
|
|
|
|
dbm = dbm2mw.key(mw);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dbm;
|
|
|
|
}
|
|
|
|
|
|
|
|
int dbmTomwatts(int dbm){
|
|
|
|
if(dbm2mw.contains(dbm)){
|
|
|
|
return dbm2mw[dbm];
|
|
|
|
}
|
|
|
|
auto iter = dbm2mw.lowerBound(dbm);
|
|
|
|
if(iter == dbm2mw.end()){
|
|
|
|
return dbm2mw.last();
|
|
|
|
}
|
|
|
|
return iter.value();
|
|
|
|
}
|
|
|
|
|
2018-10-02 09:44:46 -04:00
|
|
|
QString Varicode::rstrip(const QString& str) {
|
|
|
|
int n = str.size() - 1;
|
|
|
|
for (; n >= 0; --n) {
|
|
|
|
if (str.at(n).isSpace()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return str.left(n + 1);
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Varicode::lstrip(const QString& str) {
|
|
|
|
int len = str.size();
|
|
|
|
for (int n = 0; n < len; n++) {
|
|
|
|
if(str.at(n).isSpace()){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
return str.mid(n);
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2018-07-26 12:47:03 -04:00
|
|
|
/*
|
|
|
|
* VARICODE
|
|
|
|
*/
|
2018-09-20 10:04:18 -04:00
|
|
|
QMap<QString, QString> Varicode::defaultHuffTable(){
|
|
|
|
return hufftable;
|
|
|
|
}
|
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QString Varicode::cqString(int number){
|
|
|
|
if(!cqs.contains(number)){
|
|
|
|
return QString{};
|
|
|
|
}
|
|
|
|
return cqs[number];
|
|
|
|
}
|
|
|
|
|
2018-11-25 22:12:54 -05:00
|
|
|
QString Varicode::hbString(int number){
|
|
|
|
if(!hbs.contains(number)){
|
|
|
|
return QString{};
|
|
|
|
}
|
|
|
|
return hbs[number];
|
|
|
|
}
|
|
|
|
|
2018-09-18 10:53:43 -04:00
|
|
|
bool Varicode::startsWithCQ(QString text){
|
|
|
|
foreach(auto cq, cqs.values()){
|
|
|
|
if(text.startsWith(cq)){
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-11-25 22:12:54 -05:00
|
|
|
bool Varicode::startsWithHB(QString text){
|
2019-01-09 11:03:32 -05:00
|
|
|
foreach(auto hb, hbs.values()){
|
|
|
|
if(text.startsWith(hb)){
|
2018-11-25 22:12:54 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-20 11:40:55 -04:00
|
|
|
QString Varicode::formatSNR(int snr){
|
|
|
|
if(snr < -60 || snr > 60){
|
|
|
|
return QString();
|
|
|
|
}
|
2018-07-20 16:04:14 -04:00
|
|
|
|
|
|
|
return QString("%1%2").arg(snr >= 0 ? "+" : "").arg(snr, snr < 0 ? 3 : 2, 10, QChar('0'));
|
2018-07-20 11:40:55 -04:00
|
|
|
}
|
|
|
|
|
2018-07-24 23:10:47 -04:00
|
|
|
QString Varicode::checksum16(QString const &input){
|
2018-07-23 08:51:29 -04:00
|
|
|
auto fromBytes = input.toLocal8Bit();
|
|
|
|
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
|
|
|
|
auto checksum = Varicode::pack16bits(crc);
|
|
|
|
if(checksum.length() < 3){
|
|
|
|
checksum += QString(" ").repeated(3-checksum.length());
|
|
|
|
}
|
|
|
|
return checksum;
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:10:47 -04:00
|
|
|
bool Varicode::checksum16Valid(QString const &checksum, QString const &input){
|
2018-07-23 08:51:29 -04:00
|
|
|
auto fromBytes = input.toLocal8Bit();
|
|
|
|
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
|
|
|
|
return Varicode::pack16bits(crc) == checksum;
|
|
|
|
}
|
|
|
|
|
2018-07-24 23:10:47 -04:00
|
|
|
QString Varicode::checksum32(QString const &input){
|
|
|
|
auto fromBytes = input.toLocal8Bit();
|
|
|
|
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
|
|
|
|
auto checksum = Varicode::pack32bits(crc);
|
|
|
|
if(checksum.length() < 6){
|
|
|
|
checksum += QString(" ").repeated(6-checksum.length());
|
|
|
|
}
|
|
|
|
return checksum;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Varicode::checksum32Valid(QString const &checksum, QString const &input){
|
|
|
|
auto fromBytes = input.toLocal8Bit();
|
|
|
|
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
|
|
|
|
return Varicode::pack32bits(crc) == checksum;
|
|
|
|
}
|
|
|
|
|
2018-07-12 15:14:41 -04:00
|
|
|
QStringList Varicode::parseCallsigns(QString const &input){
|
|
|
|
QStringList callsigns;
|
2018-07-24 02:47:14 -04:00
|
|
|
QRegularExpression re(compound_callsign_pattern);
|
2018-07-12 15:14:41 -04:00
|
|
|
QRegularExpressionMatchIterator iter = re.globalMatch(input);
|
|
|
|
while(iter.hasNext()){
|
|
|
|
QRegularExpressionMatch match = iter.next();
|
|
|
|
if(!match.hasMatch()){
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-16 16:11:28 -04:00
|
|
|
QString callsign = match.captured("callsign").trimmed();
|
2018-10-26 16:57:07 -04:00
|
|
|
if(!Varicode::isValidCallsign(callsign, nullptr)){
|
2018-10-25 22:55:54 -04:00
|
|
|
continue;
|
|
|
|
}
|
2018-07-12 15:14:41 -04:00
|
|
|
QRegularExpression m(grid_pattern);
|
|
|
|
if(m.match(callsign).hasMatch()){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
callsigns.append(callsign);
|
|
|
|
}
|
|
|
|
return callsigns;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList Varicode::parseGrids(const QString &input){
|
|
|
|
QStringList grids;
|
|
|
|
QRegularExpression re(grid_pattern);
|
|
|
|
QRegularExpressionMatchIterator iter = re.globalMatch(input);
|
|
|
|
while(iter.hasNext()){
|
|
|
|
QRegularExpressionMatch match = iter.next();
|
|
|
|
if(!match.hasMatch()){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
auto grid = match.captured("grid");
|
|
|
|
if(grid == "RR73"){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
grids.append(grid);
|
|
|
|
}
|
|
|
|
return grids;
|
|
|
|
}
|
2018-07-11 23:09:22 -04:00
|
|
|
|
2018-07-30 21:26:36 -04:00
|
|
|
QList<QPair<int, QVector<bool>>> Varicode::huffEncode(const QMap<QString, QString> &huff, QString const& text){
|
|
|
|
QList<QPair<int, QVector<bool>>> out;
|
2018-07-12 18:02:54 -04:00
|
|
|
|
2018-07-30 21:26:36 -04:00
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
auto keys = huff.keys();
|
|
|
|
qSort(keys.begin(), keys.end(), [](QString const &a, QString const &b){
|
|
|
|
auto alen = a.length();
|
|
|
|
auto blen = b.length();
|
|
|
|
if(blen < alen){
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if(alen < blen){
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b < a;
|
|
|
|
});
|
|
|
|
|
|
|
|
while(i < text.length()){
|
|
|
|
bool found = false;
|
|
|
|
foreach(auto ch, keys){
|
|
|
|
if(text.midRef(i).startsWith(ch)){
|
|
|
|
out.append({ ch.length(), Varicode::strToBits(huff[ch])});
|
|
|
|
i += ch.length();
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!found){
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2018-07-30 21:26:36 -04:00
|
|
|
QString Varicode::huffDecode(QMap<QString, QString> const &huff, QVector<bool> const& bitvec){
|
2018-07-26 12:47:03 -04:00
|
|
|
QString text;
|
2018-07-12 18:02:54 -04:00
|
|
|
|
2018-07-29 22:40:34 -04:00
|
|
|
QString bits = Varicode::bitsToStr(bitvec); //.mid(0, bitvec.length()-pad);
|
2018-07-12 18:02:54 -04:00
|
|
|
|
|
|
|
// TODO: jsherer - this is naive...
|
|
|
|
while(bits.length() > 0){
|
|
|
|
bool found = false;
|
|
|
|
foreach(auto key, huff.keys()){
|
|
|
|
if(bits.startsWith(huff[key])){
|
2018-07-26 12:47:03 -04:00
|
|
|
if(key == EOT){
|
|
|
|
text.append(" ");
|
2018-07-20 09:38:34 -04:00
|
|
|
found = false;
|
2018-07-12 20:31:45 -04:00
|
|
|
break;
|
|
|
|
}
|
2018-07-26 12:47:03 -04:00
|
|
|
text.append(key);
|
2018-07-12 18:02:54 -04:00
|
|
|
bits = bits.mid(huff[key].length());
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!found){
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-26 12:47:03 -04:00
|
|
|
return text;
|
2018-07-12 18:02:54 -04:00
|
|
|
}
|
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
QSet<QString> Varicode::huffValidChars(const QMap<QString, QString> &huff){
|
|
|
|
return QSet<QString>::fromList(huff.keys());
|
2018-07-26 12:47:03 -04:00
|
|
|
}
|
|
|
|
|
2018-07-15 15:43:29 -04:00
|
|
|
// convert char* array of 0 bytes and 1 bytes to bool vector
|
|
|
|
QVector<bool> Varicode::bytesToBits(char *bitvec, int n){
|
|
|
|
QVector<bool> bits;
|
|
|
|
for(int i = 0; i < n; i++){
|
|
|
|
bits.append(bitvec[i] == 0x01);
|
|
|
|
}
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert string of 0s and 1s to bool vector
|
2018-07-12 18:02:54 -04:00
|
|
|
QVector<bool> Varicode::strToBits(QString const& bitvec){
|
|
|
|
QVector<bool> bits;
|
|
|
|
foreach(auto ch, bitvec){
|
|
|
|
bits.append(ch == '1');
|
|
|
|
}
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Varicode::bitsToStr(QVector<bool> const& bitvec){
|
|
|
|
QString bits;
|
|
|
|
foreach(auto bit, bitvec){
|
|
|
|
bits.append(bit ? "1" : "0");
|
|
|
|
}
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVector<bool> Varicode::intToBits(quint64 value, int expected){
|
2018-07-12 09:54:56 -04:00
|
|
|
QVector<bool> bits;
|
|
|
|
|
|
|
|
while(value){
|
2018-07-12 18:02:54 -04:00
|
|
|
bits.prepend((bool)(value & 1));
|
2018-07-12 09:54:56 -04:00
|
|
|
value = value >> 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(expected){
|
|
|
|
while(bits.count() < expected){
|
|
|
|
bits.prepend((bool) 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return bits;
|
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
quint64 Varicode::bitsToInt(QVector<bool> const value){
|
|
|
|
quint64 v = 0;
|
2018-07-12 09:54:56 -04:00
|
|
|
foreach(bool bit, value){
|
|
|
|
v = (v << 1) + (int)(bit);
|
|
|
|
}
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2018-07-15 15:43:29 -04:00
|
|
|
quint64 Varicode::bitsToInt(QVector<bool>::ConstIterator start, int n){
|
|
|
|
quint64 v = 0;
|
|
|
|
for(int i = 0; i < n; i++){
|
|
|
|
int bit = (int)(*start);
|
|
|
|
v = (v << 1) + (int)(bit);
|
|
|
|
start++;
|
|
|
|
}
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2018-07-26 12:47:03 -04:00
|
|
|
QVector<bool> Varicode::bitsListToBits(QList<QVector<bool>> &list){
|
|
|
|
QVector<bool> out;
|
|
|
|
foreach(auto vec, list){
|
|
|
|
out += vec;
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
quint8 Varicode::unpack5bits(QString const& value){
|
2018-07-12 09:54:56 -04:00
|
|
|
return alphabet.indexOf(value.at(0));
|
|
|
|
}
|
|
|
|
|
2018-07-23 15:28:36 -04:00
|
|
|
// pack a 5-bit value from 0 to 31 into a single character
|
2018-07-12 18:02:54 -04:00
|
|
|
QString Varicode::pack5bits(quint8 packed){
|
2018-07-23 15:28:36 -04:00
|
|
|
return alphabet.at(packed % 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
quint8 Varicode::unpack6bits(QString const& value){
|
|
|
|
return alphabet.indexOf(value.at(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
// pack a 6-bit value from 0 to 40 into a single character
|
|
|
|
QString Varicode::pack6bits(quint8 packed){
|
|
|
|
return alphabet.at(packed % 41);
|
2018-07-12 09:54:56 -04:00
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
quint16 Varicode::unpack16bits(QString const& value){
|
2018-07-12 09:54:56 -04:00
|
|
|
int a = alphabet.indexOf(value.at(0));
|
|
|
|
int b = alphabet.indexOf(value.at(1));
|
|
|
|
int c = alphabet.indexOf(value.at(2));
|
2018-07-23 15:28:36 -04:00
|
|
|
|
|
|
|
int unpacked = (nalphabet * nalphabet) * a + nalphabet * b + c;
|
|
|
|
if(unpacked > (1<<16)-1){
|
|
|
|
// BASE-41 can produce a value larger than 16 bits... ala "???" == 70643
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return unpacked & ((1<<16)-1);
|
2018-07-11 23:09:22 -04:00
|
|
|
}
|
|
|
|
|
2018-07-23 15:28:36 -04:00
|
|
|
// pack a 16-bit value into a three character sequence
|
2018-07-12 18:02:54 -04:00
|
|
|
QString Varicode::pack16bits(quint16 packed){
|
2018-07-11 23:09:22 -04:00
|
|
|
QString out;
|
2018-07-23 15:28:36 -04:00
|
|
|
quint16 tmp = packed / (nalphabet * nalphabet);
|
2018-07-12 18:02:54 -04:00
|
|
|
|
2018-07-11 23:09:22 -04:00
|
|
|
out.append(alphabet.at(tmp));
|
|
|
|
|
2018-07-23 15:28:36 -04:00
|
|
|
tmp = (packed - (tmp * (nalphabet * nalphabet))) / nalphabet;
|
2018-07-11 23:09:22 -04:00
|
|
|
out.append(alphabet.at(tmp));
|
|
|
|
|
2018-07-12 09:54:56 -04:00
|
|
|
tmp = packed % nalphabet;
|
2018-07-11 23:09:22 -04:00
|
|
|
out.append(alphabet.at(tmp));
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
2018-07-12 09:54:56 -04:00
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
quint32 Varicode::unpack32bits(QString const& value){
|
|
|
|
return (quint32)(unpack16bits(value.left(3))) << 16 | unpack16bits(value.right(3));
|
2018-07-12 09:54:56 -04:00
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
QString Varicode::pack32bits(quint32 packed){
|
|
|
|
quint16 a = (packed & 0xFFFF0000) >> 16;
|
|
|
|
quint16 b = packed & 0xFFFF;
|
2018-07-12 09:54:56 -04:00
|
|
|
return pack16bits(a) + pack16bits(b);
|
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
quint64 Varicode::unpack64bits(QString const& value){
|
|
|
|
return (quint64)(unpack32bits(value.left(6))) << 32 | unpack32bits(value.right(6));
|
2018-07-12 09:54:56 -04:00
|
|
|
}
|
|
|
|
|
2018-07-12 18:02:54 -04:00
|
|
|
QString Varicode::pack64bits(quint64 packed){
|
|
|
|
quint32 a = (packed & 0xFFFFFFFF00000000) >> 32;
|
|
|
|
quint32 b = packed & 0xFFFFFFFF;
|
2018-07-12 15:14:41 -04:00
|
|
|
return pack32bits(a) + pack32bits(b);
|
2018-07-12 09:54:56 -04:00
|
|
|
}
|
2018-07-13 00:55:48 -04:00
|
|
|
|
2018-08-06 10:16:20 -04:00
|
|
|
// returns the first 64 bits and sets the last 8 bits in pRem
|
|
|
|
quint64 Varicode::unpack72bits(QString const& text, quint8 *pRem){
|
2018-08-06 09:37:43 -04:00
|
|
|
quint64 value = 0;
|
|
|
|
quint8 rem = 0;
|
|
|
|
quint8 mask2 = ((1<<2)-1);
|
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++){
|
|
|
|
value |= (quint64)(alphabet72.indexOf(text.at(i))) << (58-6*i);
|
|
|
|
}
|
|
|
|
|
|
|
|
quint8 remHigh = alphabet72.indexOf(text.at(10));
|
|
|
|
value |= remHigh >> 2;
|
|
|
|
|
|
|
|
quint8 remLow = alphabet72.indexOf(text.at(11));
|
|
|
|
rem = ((remHigh & mask2) << 6) | remLow;
|
|
|
|
|
|
|
|
if(pRem) *pRem = rem;
|
2018-08-06 10:16:20 -04:00
|
|
|
return value;
|
2018-08-06 09:37:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
QString Varicode::pack72bits(quint64 value, quint8 rem){
|
|
|
|
QChar packed[12]; // 12 x 6bit characters
|
|
|
|
|
|
|
|
quint8 mask4 = ((1<<4)-1);
|
|
|
|
quint8 mask6 = ((1<<6)-1);
|
|
|
|
|
|
|
|
quint8 remHigh = ((value & mask4) << 2) | (rem >> 6);
|
|
|
|
quint8 remLow = rem & mask6;
|
|
|
|
value = value >> 4;
|
|
|
|
|
|
|
|
packed[11] = alphabet72.at(remLow);
|
|
|
|
packed[10] = alphabet72.at(remHigh);
|
|
|
|
|
|
|
|
for(int i = 0; i < 10; i++){
|
|
|
|
packed[9-i] = alphabet72.at(value & mask6);
|
|
|
|
value = value >> 6;
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString(packed, 12);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-07-23 15:28:36 -04:00
|
|
|
|
|
|
|
// //
|
|
|
|
// --- //
|
|
|
|
// //
|
|
|
|
|
|
|
|
|
2018-08-01 11:25:45 -04:00
|
|
|
// pack a 4-digit alpha-numeric + space into a 22 bit value
|
|
|
|
// 21 bits for the data + 1 bit for a flag indicator
|
|
|
|
// giving us a total of 5.5 bits per character
|
|
|
|
quint32 Varicode::packAlphaNumeric22(QString const& value, bool isFlag){
|
2018-10-24 16:15:12 -04:00
|
|
|
QString word = QString(value).replace(QRegExp("[^A-Z0-9/ ]"), "");
|
2018-08-01 11:25:45 -04:00
|
|
|
if(word.length() < 4){
|
|
|
|
word = word + QString(" ").repeated(4-word.length());
|
2018-07-23 15:28:36 -04:00
|
|
|
}
|
|
|
|
|
2018-10-24 16:15:12 -04:00
|
|
|
quint32 a = 38 * 38 * 38 * alphanumeric.indexOf(word.at(0));
|
|
|
|
quint32 b = 38 * 38 * alphanumeric.indexOf(word.at(1));
|
|
|
|
quint32 c = 38 * alphanumeric.indexOf(word.at(2));
|
2018-08-01 11:25:45 -04:00
|
|
|
quint32 d = alphanumeric.indexOf(word.at(3));
|
|
|
|
|
|
|
|
quint32 packed = a + b + c + d;
|
|
|
|
packed = (packed << 1) + (int)isFlag;
|
2018-07-23 15:28:36 -04:00
|
|
|
|
2018-08-01 11:25:45 -04:00
|
|
|
return packed;
|
2018-07-23 15:28:36 -04:00
|
|
|
}
|
|
|
|
|
2018-08-01 11:25:45 -04:00
|
|
|
QString Varicode::unpackAlphaNumeric22(quint32 packed, bool *isFlag){
|
|
|
|
QChar word[4];
|
|
|
|
|
|
|
|
if(isFlag) *isFlag = packed & 1;
|
|
|
|
packed = packed >> 1;
|
|
|
|
|
2018-10-24 16:15:12 -04:00
|
|
|
quint32 tmp = packed % 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[3] = alphanumeric.at(tmp);
|
2018-10-24 16:15:12 -04:00
|
|
|
packed = packed / 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
|
2018-10-24 16:15:12 -04:00
|
|
|
tmp = packed % 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[2] = alphanumeric.at(tmp);
|
2018-10-24 16:15:12 -04:00
|
|
|
packed = packed / 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
|
2018-10-24 16:15:12 -04:00
|
|
|
tmp = packed % 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[1] = alphanumeric.at(tmp);
|
2018-10-24 16:15:12 -04:00
|
|
|
packed = packed / 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
|
2018-10-24 16:15:12 -04:00
|
|
|
tmp = packed % 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[0] = alphanumeric.at(tmp);
|
2018-10-24 16:15:12 -04:00
|
|
|
packed = packed / 38;
|
2018-08-01 11:25:45 -04:00
|
|
|
|
|
|
|
return QString(word, 4);
|
2018-07-23 15:28:36 -04:00
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
// pack a 10-digit alpha-numeric + space + forward-slash into a 50 bit value
|
|
|
|
// optionally start with an @
|
|
|
|
//
|
|
|
|
// [39][38][38][02][38][38][38][02][38][38][38]
|
|
|
|
// [K] [N] [4] [ ] [C] [R] [D] [/] [Q] [R] [P]
|
|
|
|
// [V] [E] [3] [/] [L] [B] [9] [ ] [Y] [H] [X]
|
|
|
|
// [@] [R] [A] [ ] [C] [E] [S] [ ] [ ] [ ] [ ]
|
|
|
|
//
|
|
|
|
// giving us a total of 4.5-5.55 bits per character
|
|
|
|
quint64 Varicode::packAlphaNumeric50(QString const& value){
|
|
|
|
QString word = QString(value).replace(QRegExp("[^A-Z0-9 /@]"), "");
|
|
|
|
if(word.length() > 3 && word.at(3) != '/'){
|
|
|
|
word.insert(3, ' ');
|
|
|
|
}
|
|
|
|
if(word.length() > 7 && word.at(7) != '/'){
|
|
|
|
word.insert(7, ' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
if(word.length() < 11){
|
|
|
|
word = word + QString(" ").repeated(11-word.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 a = (quint64)38 * 38 * 38 * 2 * 38 * 38 * 38 * 2 * 38 * 38 * alphanumeric.indexOf(word.at(0));
|
|
|
|
quint64 b = (quint64)38 * 38 * 38 * 2 * 38 * 38 * 38 * 2 * 38 * alphanumeric.indexOf(word.at(1));
|
|
|
|
quint64 c = (quint64)38 * 38 * 38 * 2 * 38 * 38 * 38 * 2 * alphanumeric.indexOf(word.at(2));
|
|
|
|
quint64 d = (quint64)38 * 38 * 38 * 2 * 38 * 38 * 38 * (int)(word.at(3) == '/');
|
|
|
|
quint64 e = (quint64)38 * 38 * 38 * 2 * 38 * 38 * alphanumeric.indexOf(word.at(4));
|
|
|
|
quint64 f = (quint64)38 * 38 * 38 * 2 * 38 * alphanumeric.indexOf(word.at(5));
|
|
|
|
quint64 g = (quint64)38 * 38 * 38 * 2 * alphanumeric.indexOf(word.at(6));
|
|
|
|
quint64 h = (quint64)38 * 38 * 38 * (int)(word.at(7) == '/');
|
|
|
|
quint64 i = (quint64)38 * 38 * alphanumeric.indexOf(word.at(8));
|
|
|
|
quint64 j = (quint64)38 * alphanumeric.indexOf(word.at(9));
|
|
|
|
quint64 k = (quint64)alphanumeric.indexOf(word.at(10));
|
|
|
|
|
|
|
|
quint64 packed = a + b + c + d + e + f + g + h + i + j + k;
|
|
|
|
|
|
|
|
return packed;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Varicode::unpackAlphaNumeric50(quint64 packed){
|
|
|
|
QChar word[11];
|
|
|
|
|
|
|
|
quint64 tmp = packed % 38;
|
|
|
|
word[10] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[9] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[8] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 2;
|
|
|
|
word[7] = tmp ? '/' : ' ';
|
|
|
|
packed = packed / 2;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[6] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[5] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[4] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 2;
|
|
|
|
word[3] = tmp ? '/' : ' ';
|
|
|
|
packed = packed / 2;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[2] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 38;
|
|
|
|
word[1] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 38;
|
|
|
|
|
|
|
|
tmp = packed % 39;
|
|
|
|
word[0] = alphanumeric.at(tmp);
|
|
|
|
packed = packed / 39;
|
|
|
|
|
|
|
|
auto value = QString(word, 11);
|
|
|
|
|
|
|
|
return value.replace(" ", "");
|
|
|
|
}
|
|
|
|
|
2019-02-21 12:42:46 -05:00
|
|
|
// pack a callsign into a 28-bit value and a boolean portable flag
|
|
|
|
quint32 Varicode::packCallsign(QString const& value, bool *pPortable){
|
2018-07-13 00:55:48 -04:00
|
|
|
quint32 packed = 0;
|
|
|
|
|
|
|
|
QString callsign = value.toUpper().trimmed();
|
|
|
|
|
|
|
|
if(basecalls.contains(callsign)){
|
|
|
|
return basecalls[callsign];
|
|
|
|
}
|
|
|
|
|
2019-02-21 12:42:46 -05:00
|
|
|
// strip /P
|
|
|
|
if(callsign.endsWith("/P")){
|
|
|
|
callsign = callsign.left(callsign.length()-2);
|
|
|
|
|
|
|
|
if(pPortable) *pPortable = true;
|
|
|
|
}
|
|
|
|
|
2018-07-13 00:55:48 -04:00
|
|
|
// workaround for swaziland
|
|
|
|
if(callsign.startsWith("3DA0")){
|
|
|
|
callsign = "3D0" + callsign.mid(4);
|
|
|
|
}
|
|
|
|
|
|
|
|
// workaround for guinea
|
|
|
|
if(callsign.startsWith("3X") && 'A' <= callsign.at(2) && callsign.at(2) <= 'Z'){
|
|
|
|
callsign = "Q" + callsign.mid(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
int slen = callsign.length();
|
|
|
|
if(slen < 2){
|
|
|
|
return packed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(slen > 6){
|
|
|
|
return packed;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList permutations = { callsign };
|
|
|
|
if(slen == 2){
|
|
|
|
permutations.append(" " + callsign + " ");
|
|
|
|
}
|
|
|
|
if(slen == 3){
|
|
|
|
permutations.append(" " + callsign + " ");
|
|
|
|
permutations.append(callsign + " ");
|
|
|
|
}
|
|
|
|
if(slen == 4){
|
|
|
|
permutations.append(" " + callsign + " ");
|
|
|
|
permutations.append(callsign + " ");
|
|
|
|
}
|
|
|
|
if(slen == 5){
|
|
|
|
permutations.append(" " + callsign);
|
|
|
|
permutations.append(callsign + " ");
|
|
|
|
}
|
|
|
|
|
|
|
|
QString matched;
|
2018-07-24 02:47:14 -04:00
|
|
|
QRegularExpression m(pack_callsign_pattern);
|
2018-07-13 00:55:48 -04:00
|
|
|
foreach(auto permutation, permutations){
|
|
|
|
auto match = m.match(permutation);
|
|
|
|
if(match.hasMatch()){
|
|
|
|
matched = match.captured(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(matched.isEmpty()){
|
|
|
|
return packed;
|
|
|
|
}
|
2018-07-13 15:44:48 -04:00
|
|
|
if(matched.length() < 6){
|
|
|
|
return packed;
|
|
|
|
}
|
2018-07-13 00:55:48 -04:00
|
|
|
|
2018-08-01 11:25:45 -04:00
|
|
|
packed = alphanumeric.indexOf(matched.at(0));
|
|
|
|
packed = 36*packed + alphanumeric.indexOf(matched.at(1));
|
|
|
|
packed = 10*packed + alphanumeric.indexOf(matched.at(2));
|
|
|
|
packed = 27*packed + alphanumeric.indexOf(matched.at(3)) - 10;
|
|
|
|
packed = 27*packed + alphanumeric.indexOf(matched.at(4)) - 10;
|
|
|
|
packed = 27*packed + alphanumeric.indexOf(matched.at(5)) - 10;
|
2018-07-13 00:55:48 -04:00
|
|
|
|
|
|
|
return packed;
|
|
|
|
}
|
|
|
|
|
2019-02-21 12:42:46 -05:00
|
|
|
QString Varicode::unpackCallsign(quint32 value, bool portable){
|
2018-07-13 00:55:48 -04:00
|
|
|
foreach(auto key, basecalls.keys()){
|
|
|
|
if(basecalls[key] == value){
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QChar word[6];
|
|
|
|
quint32 tmp = value % 27 + 10;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[5] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
value = value/27;
|
|
|
|
|
|
|
|
tmp = value % 27 + 10;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[4] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
value = value/27;
|
|
|
|
|
|
|
|
tmp = value % 27 + 10;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[3] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
value = value/27;
|
|
|
|
|
|
|
|
tmp = value % 10;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[2] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
value = value/10;
|
|
|
|
|
|
|
|
tmp = value % 36;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[1] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
value = value/36;
|
|
|
|
|
|
|
|
tmp = value;
|
2018-08-01 11:25:45 -04:00
|
|
|
word[0] = alphanumeric.at(tmp);
|
2018-07-13 00:55:48 -04:00
|
|
|
|
|
|
|
QString callsign(word, 6);
|
|
|
|
if(callsign.startsWith("3D0")){
|
|
|
|
callsign = "3DA0" + callsign.mid(3);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(callsign.startsWith("Q") and 'A' <= callsign.at(1) && callsign.at(1) <= 'Z'){
|
|
|
|
callsign = "3X" + callsign.mid(1);
|
|
|
|
}
|
|
|
|
|
2019-02-21 12:42:46 -05:00
|
|
|
if(portable){
|
|
|
|
callsign = callsign.trimmed() + "/P";
|
|
|
|
}
|
|
|
|
|
|
|
|
return callsign.trimmed();
|
2018-07-13 00:55:48 -04:00
|
|
|
}
|
2018-07-13 04:42:23 -04:00
|
|
|
|
2018-08-27 21:19:38 -04:00
|
|
|
QString Varicode::deg2grid(float dlong, float dlat){
|
2018-07-13 21:59:54 -04:00
|
|
|
QChar grid[6];
|
|
|
|
|
|
|
|
if(dlong < -180){
|
|
|
|
dlong += 360;
|
|
|
|
}
|
|
|
|
if(dlong > 180){
|
|
|
|
dlong -= 360;
|
|
|
|
}
|
|
|
|
|
|
|
|
int nlong = int(60.0*(180.0-dlong)/5);
|
|
|
|
|
|
|
|
int n1 = nlong/240;
|
|
|
|
int n2 = (nlong-240*n1)/24;
|
|
|
|
int n3 = (nlong-240*n1-24*n2);
|
|
|
|
|
|
|
|
grid[0] = QChar('A' + n1);
|
|
|
|
grid[2] = QChar('0' + n2);
|
|
|
|
grid[4] = QChar('a' + n3);
|
|
|
|
|
|
|
|
int nlat=int(60.0*(dlat+90)/2.5);
|
|
|
|
|
|
|
|
n1 = nlat/240;
|
|
|
|
n2 = (nlat-240*n1)/24;
|
|
|
|
n3 = (nlat-240*n1-24*n2);
|
|
|
|
|
|
|
|
grid[1] = QChar('A' + n1);
|
|
|
|
grid[3] = QChar('0' + n2);
|
|
|
|
grid[5] = QChar('a' + n3);
|
|
|
|
|
|
|
|
return QString(grid, 6);
|
|
|
|
}
|
|
|
|
|
2018-08-27 21:19:38 -04:00
|
|
|
QPair<float, float> Varicode::grid2deg(QString const &grid){
|
2018-07-13 21:59:54 -04:00
|
|
|
QPair<float, float> longLat;
|
|
|
|
|
|
|
|
QString g = grid;
|
|
|
|
if(g.length() < 6){
|
|
|
|
g = grid.left(4) + "mm";
|
|
|
|
}
|
|
|
|
|
|
|
|
g = g.left(4).toUpper() + g.right(2).toLower();
|
|
|
|
|
|
|
|
int nlong = 180 - 20 * (g.at(0).toLatin1() - 'A');
|
|
|
|
int n20d = 2 * (g.at(2).toLatin1() - '0');
|
|
|
|
float xminlong = 5 * (g.at(4).toLatin1() - 'a' + 0.5);
|
|
|
|
float dlong = nlong - n20d - xminlong/60.0;
|
|
|
|
|
|
|
|
int nlat = -90 + 10*(g.at(1).toLatin1() - 'A') + g.at(3).toLatin1() - '0';
|
|
|
|
float xminlat = 2.5 * (g.at(5).toLatin1() - 'a' + 0.5);
|
|
|
|
float dlat = nlat + xminlat/60.0;
|
|
|
|
|
|
|
|
longLat.first = dlong;
|
|
|
|
longLat.second = dlat;
|
|
|
|
|
|
|
|
return longLat;
|
|
|
|
}
|
|
|
|
|
2018-07-23 15:28:36 -04:00
|
|
|
// pack a 4-digit maidenhead grid locator into a 15-bit value
|
2018-08-03 17:07:36 -04:00
|
|
|
quint16 Varicode::packGrid(QString const& value){
|
|
|
|
QString grid = QString(value).trimmed();
|
2018-08-01 11:25:45 -04:00
|
|
|
if(grid.length() < 4){
|
2018-07-27 16:11:11 -04:00
|
|
|
return (1<<15)-1;
|
|
|
|
}
|
2018-07-13 21:59:54 -04:00
|
|
|
|
2018-08-27 21:19:38 -04:00
|
|
|
auto pair = Varicode::grid2deg(grid.left(4));
|
2018-07-13 21:59:54 -04:00
|
|
|
int ilong = pair.first;
|
|
|
|
int ilat = pair.second + 90;
|
|
|
|
|
|
|
|
return ((ilong + 180)/2) * 180 + ilat;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Varicode::unpackGrid(quint16 value){
|
2018-08-03 17:07:36 -04:00
|
|
|
if(value > nbasegrid){
|
2018-07-13 21:59:54 -04:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
float dlat = value % 180 - 90;
|
|
|
|
float dlong = value / 180 * 2 - 180 + 2;
|
|
|
|
|
2018-08-27 21:19:38 -04:00
|
|
|
return Varicode::deg2grid(dlong, dlat).left(4);
|
2018-07-13 21:59:54 -04:00
|
|
|
}
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
// pack a number or snr into an integer between 0 & 62
|
|
|
|
quint8 Varicode::packNum(QString const &num, bool *ok){
|
|
|
|
int inum = 0;
|
|
|
|
if(num.isEmpty()){
|
|
|
|
if(ok) *ok = false;
|
|
|
|
return inum;
|
|
|
|
}
|
|
|
|
|
|
|
|
inum = qMax(-30, qMin(num.toInt(ok, 10), 31));
|
|
|
|
return inum + 30 + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// pack a reduced fidelity command and a number into the extra bits provided between nbasegrid and nmaxgrid
|
2018-08-04 11:19:07 -04:00
|
|
|
quint8 Varicode::packCmd(quint8 cmd, quint8 num, bool *pPackedNum){
|
2018-08-03 17:07:36 -04:00
|
|
|
//quint8 allowed = nmaxgrid - nbasegrid - 1;
|
|
|
|
|
2018-09-01 10:34:12 -04:00
|
|
|
// if cmd == snr
|
2018-08-03 17:07:36 -04:00
|
|
|
quint8 value = 0;
|
2018-09-21 01:47:21 -04:00
|
|
|
auto cmdStr = directed_cmds.key(cmd);
|
|
|
|
if(Varicode::isSNRCommand(cmdStr)){
|
2018-08-03 17:07:36 -04:00
|
|
|
// 8 bits - 1 bit flag + 1 bit type + 6 bit number
|
|
|
|
// [1][X][6]
|
|
|
|
// X = 0 == SNR
|
2018-10-28 09:47:30 -04:00
|
|
|
// X = 1 == ACK
|
2018-11-25 22:12:54 -05:00
|
|
|
value = ((1 << 1) | (int)(cmdStr == " ACK")) << 6;
|
2018-09-01 10:34:12 -04:00
|
|
|
value = value + (num & ((1<<6)-1));
|
2018-08-04 11:19:07 -04:00
|
|
|
if(pPackedNum) *pPackedNum = true;
|
2018-08-03 17:07:36 -04:00
|
|
|
} else {
|
|
|
|
value = cmd & ((1<<7)-1);
|
2018-08-04 11:19:07 -04:00
|
|
|
if(pPackedNum) *pPackedNum = false;
|
2018-08-03 17:07:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint8 Varicode::unpackCmd(quint8 value, quint8 *pNum){
|
2018-09-01 10:34:12 -04:00
|
|
|
// if the first bit is 1, this is an SNR with a number encoded in the lower 6 bits
|
2018-08-03 17:07:36 -04:00
|
|
|
if(value & (1<<7)){
|
|
|
|
if(pNum) *pNum = value & ((1<<6)-1);
|
2018-09-21 01:47:21 -04:00
|
|
|
|
|
|
|
auto cmd = directed_cmds[" SNR"];
|
|
|
|
if(value & (1<<6)){
|
2018-11-25 22:12:54 -05:00
|
|
|
cmd = directed_cmds[" ACK"];
|
2018-09-21 01:47:21 -04:00
|
|
|
}
|
|
|
|
return cmd;
|
2018-08-03 17:07:36 -04:00
|
|
|
} else {
|
|
|
|
if(pNum) *pNum = 0;
|
|
|
|
return value & ((1<<7)-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-21 01:47:21 -04:00
|
|
|
bool Varicode::isSNRCommand(const QString &cmd){
|
|
|
|
return directed_cmds.contains(cmd) && snr_cmds.contains(directed_cmds[cmd]);
|
|
|
|
}
|
|
|
|
|
2018-07-19 02:09:19 -04:00
|
|
|
bool Varicode::isCommandAllowed(const QString &cmd){
|
|
|
|
return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
|
|
|
|
}
|
|
|
|
|
2018-07-25 22:49:19 -04:00
|
|
|
bool Varicode::isCommandBuffered(const QString &cmd){
|
2018-10-03 14:37:34 -04:00
|
|
|
return directed_cmds.contains(cmd) && (cmd.contains(" ") || buffered_cmds.contains(directed_cmds[cmd]));
|
2018-07-25 22:49:19 -04:00
|
|
|
}
|
|
|
|
|
2018-08-27 22:04:17 -04:00
|
|
|
int Varicode::isCommandChecksumed(const QString &cmd){
|
|
|
|
if(!directed_cmds.contains(cmd) || !checksum_cmds.contains(directed_cmds[cmd])){
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return checksum_cmds[directed_cmds[cmd]];
|
|
|
|
}
|
|
|
|
|
2019-01-20 23:08:57 -05:00
|
|
|
bool Varicode::isCommandAutoreply(const QString &cmd){
|
|
|
|
return directed_cmds.contains(cmd) && (autoreply_cmds.contains(directed_cmds[cmd]));
|
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
bool isValidCompoundCallsign(QStringRef callsign){
|
2018-10-27 09:53:57 -04:00
|
|
|
// compound calls cannot be > 9 characters after removing the /
|
|
|
|
if(callsign.toString().replace("/", "").length() > 9){
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
// compound is valid when it is:
|
|
|
|
// 1) a group call (starting with @)
|
2018-10-26 16:57:07 -04:00
|
|
|
// 2) an actual compound call (containing /) that is not a base call
|
2018-10-27 15:14:54 -04:00
|
|
|
// 3) is greater than two characters and has an alphanumeric character sequence
|
2018-10-25 22:55:54 -04:00
|
|
|
//
|
|
|
|
// this is so arbitrary words < 10 characters in length don't end up coded as callsigns
|
2018-10-26 16:57:07 -04:00
|
|
|
if(callsign.contains("/")){
|
|
|
|
auto base = callsign.toString().left(callsign.indexOf("/"));
|
|
|
|
return !basecalls.contains(base);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(callsign.startsWith("@")){
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-10-27 15:14:54 -04:00
|
|
|
if(callsign.length() > 2 && QRegularExpression("[0-9][A-Z]|[A-Z][0-9]").match(callsign).hasMatch()){
|
2018-10-26 16:57:07 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2018-10-25 22:55:54 -04:00
|
|
|
}
|
2018-10-24 16:15:12 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
bool Varicode::isValidCallsign(const QString &callsign, bool *pIsCompound){
|
2018-10-25 22:55:54 -04:00
|
|
|
if(basecalls.contains(callsign)){
|
2018-10-26 16:57:07 -04:00
|
|
|
if(pIsCompound) *pIsCompound = false;
|
2018-10-25 22:55:54 -04:00
|
|
|
return true;
|
2018-10-24 16:15:12 -04:00
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
auto match = QRegularExpression(base_callsign_pattern).match(callsign);
|
2018-10-26 16:57:07 -04:00
|
|
|
if(match.hasMatch() && (match.capturedLength() == callsign.length())){
|
|
|
|
if(pIsCompound) *pIsCompound = false;
|
2018-10-27 15:14:54 -04:00
|
|
|
return callsign.length() > 2 && QRegularExpression("[0-9][A-Z]|[A-Z][0-9]").match(callsign).hasMatch();
|
2018-10-24 16:15:12 -04:00
|
|
|
}
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
match = QRegularExpression("^" + compound_callsign_pattern).match(callsign);
|
|
|
|
|
|
|
|
if(match.hasMatch() && (match.capturedLength() == callsign.length())){
|
|
|
|
bool isValid = isValidCompoundCallsign(match.capturedRef(0));
|
|
|
|
|
|
|
|
if(pIsCompound) *pIsCompound = isValid;
|
|
|
|
return isValid;
|
2018-10-25 22:55:54 -04:00
|
|
|
}
|
2018-10-24 16:15:12 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
if(pIsCompound) *pIsCompound = false;
|
2018-10-25 22:55:54 -04:00
|
|
|
return false;
|
2018-10-24 16:15:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool Varicode::isCompoundCallsign(const QString &callsign){
|
2019-02-12 19:37:41 -05:00
|
|
|
if(basecalls.contains(callsign) && !callsign.startsWith("@")){
|
2018-10-25 22:55:54 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto match = QRegularExpression(base_callsign_pattern).match(callsign);
|
2018-10-26 16:57:07 -04:00
|
|
|
if(match.hasMatch() && (match.capturedLength() == callsign.length())){
|
2018-10-25 22:55:54 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
match = QRegularExpression("^" + compound_callsign_pattern).match(callsign);
|
|
|
|
if(!match.hasMatch() || (match.capturedLength() != callsign.length())){
|
2018-10-25 22:55:54 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
bool isValid = isValidCompoundCallsign(match.capturedRef(0));
|
|
|
|
|
|
|
|
qDebug() << "is valid compound?" << match.capturedRef(0) << isValid;
|
|
|
|
|
|
|
|
return isValid;
|
2018-10-24 16:15:12 -04:00
|
|
|
}
|
|
|
|
|
2018-08-04 11:19:07 -04:00
|
|
|
// CQCQCQ EM73
|
2018-09-06 15:22:24 -04:00
|
|
|
// CQ DX EM73
|
|
|
|
// CQ QRP EM73
|
Removed selcall and active flag
In effort to simplify the behavior of automatic responses as well as make the software easier to use, I have removed the SELCALL button and the ACTIVE flag. Now, the response to STATUS is one that contains actual status (AUTO ON/OFF, VERSION NUMBER, etc). HBs used this in their transmissions, but it was never really accurate because it relied on the user to toggle the switch. Hazardous really. So, I approached this by simplifying the behavior. If AUTO is on, you will reply to direct queries. If AUTO is off, you wont. Simple. If HB is on, you will heartbeat. If it is off, you wont. Simple. If both AUTO and HB is on, you will automatically reply to heartbeats with ACKs. If not, you wont. Simple. You can remove yourself from the ALLCALL group. This is the same behavior as the previous SELCALL function and now that we have simplified it I can build an actual SELCALL function (to selectively allow stations to call you) instead of a 1/2 SELCALL that it used to be. Bingo.
2018-12-26 14:05:44 -05:00
|
|
|
// HB EM73
|
2018-10-28 09:47:30 -04:00
|
|
|
QString Varicode::packHeartbeatMessage(QString const &text, const QString &callsign, int *n){
|
2018-07-27 16:11:11 -04:00
|
|
|
QString frame;
|
|
|
|
|
2018-10-28 09:47:30 -04:00
|
|
|
auto parsedText = heartbeat_re.match(text);
|
2018-07-29 10:39:58 -04:00
|
|
|
if(!parsedText.hasMatch()){
|
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto extra = parsedText.captured("grid");
|
2018-07-30 17:16:45 -04:00
|
|
|
|
2018-10-28 09:47:30 -04:00
|
|
|
// Heartbeat Alt Type
|
2018-07-30 17:16:45 -04:00
|
|
|
// ---------------
|
2018-11-26 22:40:34 -05:00
|
|
|
// 1 0 HB
|
|
|
|
// 1 1 CQ
|
2018-07-30 17:16:45 -04:00
|
|
|
|
|
|
|
auto type = parsedText.captured("type");
|
2018-08-04 11:19:07 -04:00
|
|
|
auto isAlt = type.startsWith("CQ");
|
2018-07-29 10:39:58 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
if(callsign.isEmpty()){
|
2018-10-24 16:15:12 -04:00
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
2018-07-27 16:11:11 -04:00
|
|
|
}
|
|
|
|
|
2018-08-01 11:25:45 -04:00
|
|
|
quint16 packed_extra = nmaxgrid; // which will display an empty string
|
2018-07-27 16:11:11 -04:00
|
|
|
if(extra.length() == 4 && QRegularExpression(grid_pattern).match(extra).hasMatch()){
|
|
|
|
packed_extra = Varicode::packGrid(extra);
|
|
|
|
}
|
|
|
|
|
2018-11-26 22:40:34 -05:00
|
|
|
quint8 cqNumber = hbs.key(type, 0);
|
2018-11-25 22:12:54 -05:00
|
|
|
|
2018-07-29 22:40:34 -04:00
|
|
|
if(isAlt){
|
2018-07-27 16:11:11 -04:00
|
|
|
packed_extra |= (1<<15);
|
2018-11-26 22:40:34 -05:00
|
|
|
cqNumber = cqs.key(type, 0);
|
2018-07-27 16:11:11 -04:00
|
|
|
}
|
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
frame = packCompoundFrame(callsign, Varicode::FrameHeartbeat, packed_extra, cqNumber);
|
2018-07-29 10:39:58 -04:00
|
|
|
if(frame.isEmpty()){
|
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(n) *n = parsedText.captured(0).length();
|
|
|
|
return frame;
|
2018-07-27 16:11:11 -04:00
|
|
|
}
|
|
|
|
|
2018-10-28 09:47:30 -04:00
|
|
|
QStringList Varicode::unpackHeartbeatMessage(const QString &text, quint8 *pType, bool * isAlt, quint8 * pBits3){
|
2018-11-08 16:53:42 -05:00
|
|
|
quint8 type = Varicode::FrameHeartbeat;
|
2018-08-02 01:38:47 -04:00
|
|
|
quint16 num = nmaxgrid;
|
2018-09-06 15:22:24 -04:00
|
|
|
quint8 bits3 = 0;
|
2018-07-27 16:11:11 -04:00
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QStringList unpacked = unpackCompoundFrame(text, &type, &num, &bits3);
|
2018-11-08 16:53:42 -05:00
|
|
|
if(unpacked.isEmpty() || type != Varicode::FrameHeartbeat){
|
2018-08-02 01:38:47 -04:00
|
|
|
return QStringList{};
|
2018-08-02 00:32:06 -04:00
|
|
|
}
|
2018-07-27 16:11:11 -04:00
|
|
|
|
|
|
|
unpacked.append(Varicode::unpackGrid(num & ((1<<15)-1)));
|
|
|
|
|
2018-08-02 00:32:06 -04:00
|
|
|
if(isAlt) *isAlt = (num & (1<<15));
|
2018-08-03 17:07:36 -04:00
|
|
|
if(pType) *pType = type;
|
2018-09-06 15:22:24 -04:00
|
|
|
if(pBits3) *pBits3 = bits3;
|
2018-08-02 00:32:06 -04:00
|
|
|
|
2018-07-27 16:11:11 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
2018-08-02 01:38:47 -04:00
|
|
|
// KN4CRD/XXXX EM73
|
|
|
|
// XXXX/KN4CRD EM73
|
|
|
|
// KN4CRD/P
|
|
|
|
QString Varicode::packCompoundMessage(QString const &text, int *n){
|
|
|
|
QString frame;
|
|
|
|
|
2018-08-11 10:06:50 -04:00
|
|
|
qDebug() << "trying to pack compound message" << text;
|
2018-08-02 01:38:47 -04:00
|
|
|
auto parsedText = compound_re.match(text);
|
|
|
|
if(!parsedText.hasMatch()){
|
2018-10-26 16:57:07 -04:00
|
|
|
qDebug() << "no match for compound message" << text;
|
2018-08-02 01:38:47 -04:00
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2018-08-11 10:06:50 -04:00
|
|
|
qDebug() << parsedText.capturedTexts();
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
QString callsign = parsedText.captured("callsign");
|
|
|
|
QString grid = parsedText.captured("grid");
|
|
|
|
QString cmd = parsedText.captured("cmd");
|
|
|
|
QString num = parsedText.captured("num").trimmed();
|
2018-08-02 01:38:47 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
if(callsign.isEmpty()){
|
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
2018-08-02 01:38:47 -04:00
|
|
|
}
|
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
quint8 type = Varicode::FrameCompound;
|
2018-08-03 17:07:36 -04:00
|
|
|
quint16 extra = nmaxgrid;
|
|
|
|
|
2018-10-16 16:11:28 -04:00
|
|
|
qDebug() << "try pack cmd" << cmd << directed_cmds.contains(cmd) << Varicode::isCommandAllowed(cmd);
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
if (!cmd.isEmpty() && directed_cmds.contains(cmd) && Varicode::isCommandAllowed(cmd)){
|
2018-08-04 11:19:07 -04:00
|
|
|
bool packedNum = false;
|
2018-08-03 17:07:36 -04:00
|
|
|
qint8 inum = Varicode::packNum(num, nullptr);
|
2018-08-04 11:19:07 -04:00
|
|
|
extra = nusergrid + Varicode::packCmd(directed_cmds[cmd], inum, &packedNum);
|
2018-08-03 17:07:36 -04:00
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
type = Varicode::FrameCompoundDirected;
|
2018-08-03 17:07:36 -04:00
|
|
|
} else if(!grid.isEmpty()){
|
|
|
|
extra = Varicode::packGrid(grid);
|
|
|
|
}
|
2018-08-02 01:38:47 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
frame = Varicode::packCompoundFrame(callsign, type, extra, 0);
|
2018-08-02 01:38:47 -04:00
|
|
|
|
|
|
|
if(n) *n = parsedText.captured(0).length();
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QStringList Varicode::unpackCompoundMessage(const QString &text, quint8 *pType, quint8 *pBits3){
|
2018-11-08 16:53:42 -05:00
|
|
|
quint8 type = Varicode::FrameCompound;
|
2018-08-03 17:07:36 -04:00
|
|
|
quint16 extra = nmaxgrid;
|
2018-09-06 15:22:24 -04:00
|
|
|
quint8 bits3 = 0;
|
2018-08-02 01:38:47 -04:00
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QStringList unpacked = unpackCompoundFrame(text, &type, &extra, &bits3);
|
2018-11-08 16:53:42 -05:00
|
|
|
if(unpacked.isEmpty() || (type != Varicode::FrameCompound && type != Varicode::FrameCompoundDirected)){
|
2018-08-02 01:38:47 -04:00
|
|
|
return QStringList {};
|
|
|
|
}
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
if(extra <= nbasegrid){
|
|
|
|
unpacked.append(" " + Varicode::unpackGrid(extra));
|
|
|
|
} else if (nusergrid <= extra && extra < nmaxgrid) {
|
|
|
|
quint8 num = 0;
|
|
|
|
auto cmd = Varicode::unpackCmd(extra - nusergrid, &num);
|
2018-09-21 01:47:21 -04:00
|
|
|
auto cmdStr = directed_cmds.key(cmd);
|
2018-08-03 17:07:36 -04:00
|
|
|
|
2018-09-21 01:47:21 -04:00
|
|
|
unpacked.append(cmdStr);
|
2018-08-03 17:07:36 -04:00
|
|
|
|
2018-09-21 01:47:21 -04:00
|
|
|
if(Varicode::isSNRCommand(cmdStr)){
|
2018-08-03 17:07:36 -04:00
|
|
|
unpacked.append(Varicode::formatSNR(num - 31));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(pType) *pType = type;
|
2018-09-06 15:22:24 -04:00
|
|
|
if(pBits3) *pBits3 = bits3;
|
2018-08-02 01:38:47 -04:00
|
|
|
|
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
QString Varicode::packCompoundFrame(const QString &callsign, quint8 type, quint16 num, quint8 bits3){
|
2018-07-24 02:47:14 -04:00
|
|
|
QString frame;
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
// needs to be a compound type...
|
2018-11-08 16:53:42 -05:00
|
|
|
if(type == Varicode::FrameData || type == Varicode::FrameDirected){
|
2018-08-02 00:32:06 -04:00
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint8 packed_flag = type;
|
2018-10-25 22:55:54 -04:00
|
|
|
quint64 packed_callsign = Varicode::packAlphaNumeric50(callsign);
|
|
|
|
if(packed_callsign == 0){
|
2018-07-24 02:47:14 -04:00
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint16 mask11 = ((1<<11)-1)<<5;
|
|
|
|
quint8 mask5 = (1<<5)-1;
|
|
|
|
|
|
|
|
quint16 packed_11 = (num & mask11) >> 5;
|
|
|
|
quint8 packed_5 = num & mask5;
|
2018-09-06 15:22:24 -04:00
|
|
|
quint8 packed_8 = (packed_5 << 3) | bits3;
|
2018-07-24 02:47:14 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
// [3][50][11],[5][3] = 72
|
2018-07-24 02:47:14 -04:00
|
|
|
auto bits = (
|
2018-07-29 22:40:34 -04:00
|
|
|
Varicode::intToBits(packed_flag, 3) +
|
2018-10-25 22:55:54 -04:00
|
|
|
Varicode::intToBits(packed_callsign, 50) +
|
2018-07-29 22:40:34 -04:00
|
|
|
Varicode::intToBits(packed_11, 11)
|
2018-07-24 02:47:14 -04:00
|
|
|
);
|
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
return Varicode::pack72bits(Varicode::bitsToInt(bits), packed_8);
|
2018-07-24 02:47:14 -04:00
|
|
|
}
|
|
|
|
|
2018-09-06 15:22:24 -04:00
|
|
|
QStringList Varicode::unpackCompoundFrame(const QString &text, quint8 *pType, quint16 *pNum, quint8 *pBits3){
|
2018-07-24 02:47:14 -04:00
|
|
|
QStringList unpacked;
|
|
|
|
|
2018-08-06 10:16:20 -04:00
|
|
|
if(text.length() < 12 || text.contains(" ")){
|
|
|
|
return unpacked;
|
2018-07-24 02:47:14 -04:00
|
|
|
}
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
// [3][50][11],[5][3] = 72
|
2018-09-06 15:22:24 -04:00
|
|
|
quint8 packed_8 = 0;
|
|
|
|
auto bits = Varicode::intToBits(Varicode::unpack72bits(text, &packed_8), 64);
|
|
|
|
|
|
|
|
quint8 packed_5 = packed_8 >> 3;
|
|
|
|
quint8 packed_3 = packed_8 & ((1<<3)-1);
|
2018-07-24 02:47:14 -04:00
|
|
|
|
2018-08-03 20:50:56 -04:00
|
|
|
quint8 packed_flag = Varicode::bitsToInt(bits.mid(0, 3));
|
2018-08-02 00:32:06 -04:00
|
|
|
|
2018-10-28 09:47:30 -04:00
|
|
|
// needs to be a ping type...
|
2018-11-08 16:53:42 -05:00
|
|
|
if(packed_flag == Varicode::FrameData || packed_flag == Varicode::FrameDirected){
|
2018-07-24 02:47:14 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
2018-07-29 22:40:34 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
quint64 packed_callsign = Varicode::bitsToInt(bits.mid(3, 50));
|
2018-08-03 20:50:56 -04:00
|
|
|
quint16 packed_11 = Varicode::bitsToInt(bits.mid(53, 11));
|
2018-07-24 02:47:14 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
QString callsign = Varicode::unpackAlphaNumeric50(packed_callsign);
|
2018-08-01 11:25:45 -04:00
|
|
|
|
2018-07-24 02:47:14 -04:00
|
|
|
quint16 num = (packed_11 << 5) | packed_5;
|
2018-07-23 15:28:36 -04:00
|
|
|
|
2018-08-02 00:32:06 -04:00
|
|
|
if(pType) *pType = packed_flag;
|
2018-07-27 16:11:11 -04:00
|
|
|
if(pNum) *pNum = num;
|
2018-09-06 15:22:24 -04:00
|
|
|
if(pBits3) *pBits3 = packed_3;
|
2018-07-27 16:11:11 -04:00
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
unpacked.append(callsign);
|
|
|
|
unpacked.append("");
|
2018-07-24 02:47:14 -04:00
|
|
|
|
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
2018-08-02 01:38:47 -04:00
|
|
|
// J1Y ACK
|
|
|
|
// J1Y?
|
|
|
|
// KN4CRD: J1Y$
|
|
|
|
// KN4CRD: J1Y! HELLO WORLD
|
2018-10-26 16:57:07 -04:00
|
|
|
QString Varicode::packDirectedMessage(const QString &text, const QString &mycall, QString *pTo, bool *pToCompound, QString * pCmd, QString *pNum, int *n){
|
2018-07-13 04:42:23 -04:00
|
|
|
QString frame;
|
2018-07-14 22:05:08 -04:00
|
|
|
|
2018-07-14 18:10:07 -04:00
|
|
|
auto match = directed_re.match(text);
|
2018-07-23 17:20:03 -04:00
|
|
|
if(!match.hasMatch()){
|
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
2018-07-15 13:03:16 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
QString from = mycall;
|
|
|
|
bool isFromCompound = Varicode::isCompoundCallsign(from);
|
|
|
|
if(isFromCompound){
|
|
|
|
from = "<....>";
|
|
|
|
}
|
2018-08-03 17:07:36 -04:00
|
|
|
QString to = match.captured("callsign");
|
2018-07-23 17:20:03 -04:00
|
|
|
QString cmd = match.captured("cmd");
|
2018-10-16 16:11:28 -04:00
|
|
|
QString num = match.captured("num");
|
2018-07-16 09:14:28 -04:00
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
// ensure we have a directed command
|
|
|
|
if(cmd.isEmpty()){
|
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
// ensure we have a valid callsign
|
|
|
|
bool isToCompound = false;
|
|
|
|
bool validToCallsign = (to != mycall) && Varicode::isValidCallsign(to, &isToCompound);
|
2018-07-23 17:20:03 -04:00
|
|
|
if(!validToCallsign){
|
2018-10-26 16:57:07 -04:00
|
|
|
qDebug() << "to" << to << "is not a valid callsign";
|
2018-07-23 17:20:03 -04:00
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
2018-07-13 04:42:23 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
// return back the parsed "to" field
|
|
|
|
if(pTo) *pTo = to;
|
|
|
|
if(pToCompound) *pToCompound = isToCompound;
|
2018-07-30 17:16:45 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
// then replace the current processing with a placeholder that we _can_ pack into a directed command,
|
|
|
|
// because later we'll send the "to" field in a compound frame using the results of this directed pack
|
|
|
|
if(isToCompound){
|
|
|
|
to = "<....>";
|
2018-07-27 11:28:31 -04:00
|
|
|
}
|
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
qDebug() << "directed" << validToCallsign << isToCompound << to;
|
|
|
|
|
2018-07-23 17:20:03 -04:00
|
|
|
// validate command
|
2018-08-31 21:30:34 -04:00
|
|
|
if(!Varicode::isCommandAllowed(cmd) && !Varicode::isCommandAllowed(cmd.trimmed())){
|
2018-07-23 17:20:03 -04:00
|
|
|
if(n) *n = 0;
|
|
|
|
return frame;
|
|
|
|
}
|
2018-07-14 22:05:08 -04:00
|
|
|
|
2018-07-23 17:20:03 -04:00
|
|
|
// packing general number...
|
2018-10-16 16:11:28 -04:00
|
|
|
bool numOK = false;
|
|
|
|
quint8 inum = Varicode::packNum(num.trimmed(), &numOK);
|
|
|
|
if(numOK){
|
|
|
|
if(pNum) *pNum = num;
|
|
|
|
}
|
2018-07-15 13:03:16 -04:00
|
|
|
|
2019-02-21 12:42:46 -05:00
|
|
|
bool portable_from = false;
|
|
|
|
quint32 packed_from = Varicode::packCallsign(from, &portable_from);
|
|
|
|
|
|
|
|
bool portable_to = false;
|
|
|
|
quint32 packed_to = Varicode::packCallsign(to, &portable_to);
|
2018-07-23 17:20:03 -04:00
|
|
|
|
|
|
|
if(packed_from == 0 || packed_to == 0){
|
2018-07-24 02:47:14 -04:00
|
|
|
if(n) *n = 0;
|
2018-07-23 17:20:03 -04:00
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2018-10-16 16:11:28 -04:00
|
|
|
QString cmdOut;
|
2018-08-31 21:30:34 -04:00
|
|
|
quint8 packed_cmd = 0;
|
|
|
|
if(directed_cmds.contains(cmd)){
|
2018-10-16 16:11:28 -04:00
|
|
|
cmdOut = cmd;
|
|
|
|
packed_cmd = directed_cmds[cmdOut];
|
2018-08-31 21:30:34 -04:00
|
|
|
}
|
|
|
|
if(directed_cmds.contains(cmd.trimmed())){
|
2018-10-16 16:11:28 -04:00
|
|
|
cmdOut = cmd.trimmed();
|
|
|
|
packed_cmd = directed_cmds[cmdOut];
|
2018-08-31 21:30:34 -04:00
|
|
|
}
|
2018-11-08 16:53:42 -05:00
|
|
|
quint8 packed_flag = Varicode::FrameDirected;
|
2019-02-21 12:42:46 -05:00
|
|
|
quint8 packed_extra = (
|
|
|
|
(((int)portable_from) << 7) +
|
|
|
|
(((int)portable_to) << 6) +
|
|
|
|
inum
|
|
|
|
);
|
2018-07-23 17:20:03 -04:00
|
|
|
|
2018-08-06 10:29:57 -04:00
|
|
|
// [3][28][28][5],[2][6] = 72
|
2018-07-23 17:20:03 -04:00
|
|
|
auto bits = (
|
2018-07-29 22:40:34 -04:00
|
|
|
Varicode::intToBits(packed_flag, 3) +
|
|
|
|
Varicode::intToBits(packed_from, 28) +
|
|
|
|
Varicode::intToBits(packed_to, 28) +
|
|
|
|
Varicode::intToBits(packed_cmd % 32, 5)
|
2018-07-23 17:20:03 -04:00
|
|
|
);
|
|
|
|
|
2018-10-16 16:11:28 -04:00
|
|
|
if(pCmd) *pCmd = cmdOut;
|
2018-07-24 02:47:14 -04:00
|
|
|
if(n) *n = match.captured(0).length();
|
2019-02-21 12:42:46 -05:00
|
|
|
return Varicode::pack72bits(Varicode::bitsToInt(bits), packed_extra);
|
2018-07-23 17:20:03 -04:00
|
|
|
}
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
|
2018-07-13 04:42:23 -04:00
|
|
|
QStringList unpacked;
|
2018-07-14 22:05:08 -04:00
|
|
|
|
2018-08-06 10:16:20 -04:00
|
|
|
if(text.length() < 12 || text.contains(" ")){
|
2018-07-14 22:05:08 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
2018-08-06 10:29:57 -04:00
|
|
|
// [3][28][22][11],[2][6] = 72
|
2018-08-06 10:16:20 -04:00
|
|
|
quint8 extra = 0;
|
|
|
|
auto bits = Varicode::intToBits(Varicode::unpack72bits(text, &extra), 64);
|
2018-07-13 04:42:23 -04:00
|
|
|
|
2018-08-03 20:50:56 -04:00
|
|
|
quint8 packed_flag = Varicode::bitsToInt(bits.mid(0, 3));
|
2018-11-08 16:53:42 -05:00
|
|
|
if(packed_flag != Varicode::FrameDirected){
|
2018-07-24 02:47:14 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
2018-07-29 22:40:34 -04:00
|
|
|
|
2018-08-03 20:50:56 -04:00
|
|
|
quint32 packed_from = Varicode::bitsToInt(bits.mid(3, 28));
|
|
|
|
quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28));
|
|
|
|
quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5));
|
2018-07-13 04:42:23 -04:00
|
|
|
|
2019-03-03 23:33:30 -05:00
|
|
|
bool portable_from = ((extra >> 7) & 1) == 1;
|
|
|
|
bool portable_to = ((extra >> 6) & 1) == 1;
|
2019-02-21 12:42:46 -05:00
|
|
|
extra = extra % 64;
|
|
|
|
|
|
|
|
QString from = Varicode::unpackCallsign(packed_from, portable_from);
|
|
|
|
QString to = Varicode::unpackCallsign(packed_to, portable_to);
|
2018-07-25 22:49:19 -04:00
|
|
|
QString cmd = directed_cmds.key(packed_cmd % 32);
|
2018-07-14 22:05:08 -04:00
|
|
|
|
|
|
|
unpacked.append(from);
|
2018-07-25 22:49:19 -04:00
|
|
|
unpacked.append(to);
|
|
|
|
unpacked.append(cmd);
|
2018-07-13 04:42:23 -04:00
|
|
|
|
2018-08-06 10:29:57 -04:00
|
|
|
if(extra != 0){
|
2018-09-21 01:47:21 -04:00
|
|
|
if(Varicode::isSNRCommand(cmd)){
|
2018-08-06 10:29:57 -04:00
|
|
|
unpacked.append(Varicode::formatSNR((int)extra-31));
|
2018-07-21 02:18:15 -04:00
|
|
|
} else {
|
2018-08-06 10:29:57 -04:00
|
|
|
unpacked.append(QString("%1").arg(extra-31));
|
2018-07-21 02:18:15 -04:00
|
|
|
}
|
2018-07-19 23:14:11 -04:00
|
|
|
}
|
|
|
|
|
2018-08-03 17:07:36 -04:00
|
|
|
if(pType) *pType = packed_flag;
|
2018-07-13 04:42:23 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
2018-07-19 03:44:02 -04:00
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
QString packHuffMessage(const QString &input, int *n){
|
2018-08-06 10:16:20 -04:00
|
|
|
static const int frameSize = 72;
|
|
|
|
|
2018-10-29 13:39:15 -04:00
|
|
|
QString frame;
|
2018-07-29 22:40:34 -04:00
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
// [1][1][70] = 72
|
|
|
|
// The first bit is a flag that indicates this is a data frame, technically encoded as [100]
|
2018-11-21 17:06:58 -05:00
|
|
|
// but, since none of the other frame types start with a 0, we can drop the two zeros and use
|
2018-11-08 16:53:42 -05:00
|
|
|
// them for encoding the first two bits of the actuall data sent. boom!
|
|
|
|
// The second bit is a flag that indicates this is not compressed frame (huffman coding)
|
|
|
|
QVector<bool> frameBits = {true, false};
|
2018-07-19 03:44:02 -04:00
|
|
|
|
|
|
|
int i = 0;
|
2018-07-26 12:47:03 -04:00
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
// only pack huff messages that only contain valid chars
|
|
|
|
QString::const_iterator it;
|
|
|
|
QSet<QString> validChars = Varicode::huffValidChars(Varicode::defaultHuffTable());
|
|
|
|
for(it = input.constBegin(); it != input.constEnd(); it++){
|
|
|
|
auto ch = (*it).toUpper();
|
|
|
|
if(!validChars.contains(ch)){
|
2018-10-29 03:26:10 -04:00
|
|
|
if(n) *n = 0;
|
2018-10-03 12:36:45 -04:00
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// pack using the default huff table
|
|
|
|
foreach(auto pair, Varicode::huffEncode(Varicode::defaultHuffTable(), input)){
|
2018-07-30 21:26:36 -04:00
|
|
|
auto charN = pair.first;
|
|
|
|
auto charBits = pair.second;
|
2018-10-29 03:26:10 -04:00
|
|
|
if(frameBits.length() + charBits.length() < frameSize){
|
|
|
|
frameBits += charBits;
|
2018-07-30 21:26:36 -04:00
|
|
|
i += charN;
|
2018-07-19 03:44:02 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
qDebug() << "Huff bits" << frameBits.length() << "chars" << i;
|
2018-07-29 22:40:34 -04:00
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
int pad = frameSize - frameBits.length();
|
2018-07-19 03:44:02 -04:00
|
|
|
if(pad){
|
2018-07-29 22:40:34 -04:00
|
|
|
// the way we will pad is this...
|
|
|
|
// set the bit after the frame to 0 and every bit after that a 1
|
|
|
|
// to unpad, seek from the end of the bits until you hit a zero... the rest is the actual frame.
|
|
|
|
for(int i = 0; i < pad; i++){
|
2018-10-29 03:26:10 -04:00
|
|
|
frameBits.append(i == 0 ? (bool)0 : (bool)1);
|
2018-07-29 22:40:34 -04:00
|
|
|
}
|
2018-07-19 03:44:02 -04:00
|
|
|
}
|
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
quint64 value = Varicode::bitsToInt(frameBits.constBegin(), 64);
|
|
|
|
quint8 rem = (quint8)Varicode::bitsToInt(frameBits.constBegin() + 64, 8);
|
2018-08-06 10:16:20 -04:00
|
|
|
frame = Varicode::pack72bits(value, rem);
|
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
if(n) *n = i;
|
|
|
|
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString packCompressedMessage(const QString &input, int *n){
|
|
|
|
static const int frameSize = 72;
|
|
|
|
|
|
|
|
QString frame;
|
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
// [1][1][70] = 72
|
|
|
|
// The first bit is a flag that indicates this is a data frame, technically encoded as [100]
|
|
|
|
// but, since none of the other frame types start with a 1, we can drop the two zeros and use
|
|
|
|
// them for encoding the first two bits of the actuall data sent. boom!
|
|
|
|
// The second bit is a flag that indicates this is a compressed frame (dense coding)
|
|
|
|
QVector<bool> frameBits = {true, true};
|
2018-10-02 18:03:15 -04:00
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
foreach(auto pair, JSC::compress(input)){
|
|
|
|
auto bits = pair.first;
|
|
|
|
auto chars = pair.second;
|
|
|
|
|
|
|
|
if(frameBits.length() + bits.length() < frameSize){
|
|
|
|
frameBits.append(bits);
|
|
|
|
i += chars;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
qDebug() << "Compressed bits" << frameBits.length() << "chars" << i;
|
2018-10-03 12:36:45 -04:00
|
|
|
|
2018-10-02 18:03:15 -04:00
|
|
|
int pad = frameSize - frameBits.length();
|
|
|
|
if(pad){
|
|
|
|
// the way we will pad is this...
|
|
|
|
// set the bit after the frame to 0 and every bit after that a 1
|
|
|
|
// to unpad, seek from the end of the bits until you hit a zero... the rest is the actual frame.
|
|
|
|
for(int i = 0; i < pad; i++){
|
|
|
|
frameBits.append(i == 0 ? (bool)0 : (bool)1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 value = Varicode::bitsToInt(frameBits.constBegin(), 64);
|
|
|
|
quint8 rem = (quint8)Varicode::bitsToInt(frameBits.constBegin() + 64, 8);
|
|
|
|
frame = Varicode::pack72bits(value, rem);
|
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
if(n) *n = i;
|
2018-07-19 03:44:02 -04:00
|
|
|
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
QString Varicode::packDataMessage(const QString &input, int *n){
|
2018-11-08 16:53:42 -05:00
|
|
|
QString huffFrame;
|
|
|
|
int huffChars = 0;
|
|
|
|
huffFrame = packHuffMessage(input, &huffChars);
|
2018-10-03 12:36:45 -04:00
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
QString compressedFrame;
|
|
|
|
int compressedChars = 0;
|
|
|
|
compressedFrame = packCompressedMessage(input, &compressedChars);
|
2018-10-03 12:36:45 -04:00
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
if(huffChars > compressedChars){
|
|
|
|
if(n) *n = huffChars;
|
|
|
|
return huffFrame;
|
|
|
|
} else {
|
|
|
|
if(n) *n = compressedChars;
|
|
|
|
return compressedFrame;
|
|
|
|
}
|
2018-10-03 12:36:45 -04:00
|
|
|
}
|
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
|
|
|
|
QString Varicode::unpackDataMessage(const QString &text){
|
2018-07-19 03:44:02 -04:00
|
|
|
QString unpacked;
|
|
|
|
|
2018-08-06 10:16:20 -04:00
|
|
|
if(text.length() < 12 || text.contains(" ")){
|
2018-07-19 03:44:02 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
2018-08-06 10:16:20 -04:00
|
|
|
quint8 rem = 0;
|
|
|
|
quint64 value = Varicode::unpack72bits(text, &rem);
|
|
|
|
auto bits = Varicode::intToBits(value, 64) + Varicode::intToBits(rem, 8);
|
2018-07-19 03:44:02 -04:00
|
|
|
|
2018-11-08 16:53:42 -05:00
|
|
|
bool isData = bits.at(0);
|
|
|
|
if(!isData){
|
|
|
|
return unpacked;
|
|
|
|
}
|
|
|
|
|
|
|
|
bits = bits.mid(1);
|
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
bool compressed = bits.at(0);
|
2018-07-19 03:44:02 -04:00
|
|
|
|
2018-10-03 12:36:45 -04:00
|
|
|
int n = bits.lastIndexOf(0);
|
2018-10-29 03:26:10 -04:00
|
|
|
bits = bits.mid(1, n-1);
|
2018-07-26 12:47:03 -04:00
|
|
|
|
2018-10-29 03:26:10 -04:00
|
|
|
if(compressed){
|
2018-11-08 16:53:42 -05:00
|
|
|
// partial word (s,c)-dense coding with code tables
|
2018-10-29 03:26:10 -04:00
|
|
|
unpacked = JSC::decompress(bits);
|
|
|
|
} else {
|
2018-10-03 12:36:45 -04:00
|
|
|
// huff decode the bits (without escapes)
|
|
|
|
unpacked = Varicode::huffDecode(Varicode::defaultHuffTable(), bits);
|
|
|
|
}
|
2018-08-03 17:07:36 -04:00
|
|
|
|
2018-07-19 03:44:02 -04:00
|
|
|
return unpacked;
|
|
|
|
}
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
// TODO: remove the dependence on providing all this data?
|
2018-10-29 03:26:10 -04:00
|
|
|
QList<QPair<QString, int>> Varicode::buildMessageFrames(
|
2018-10-02 09:44:46 -04:00
|
|
|
QString const& mycall,
|
|
|
|
QString const& mygrid,
|
2018-10-26 22:16:48 -04:00
|
|
|
QString const& selectedCall,
|
2019-05-27 11:30:30 -04:00
|
|
|
QString const& text,
|
|
|
|
bool forceIdentify
|
2018-10-02 09:44:46 -04:00
|
|
|
){
|
|
|
|
#define ALLOW_SEND_COMPOUND 1
|
2018-10-26 16:57:07 -04:00
|
|
|
#define ALLOW_SEND_COMPOUND_DIRECTED 1
|
2018-10-26 22:16:48 -04:00
|
|
|
#define AUTO_PREPEND_DIRECTED 1
|
|
|
|
#define AUTO_REMOVE_MYCALL 1
|
2018-11-01 00:06:09 -04:00
|
|
|
#define AUTO_PREPEND_DIRECTED_ALLOW_TEXT_CALLSIGNS 1
|
2019-05-27 11:30:30 -04:00
|
|
|
#define ALLOW_FORCE_IDENTIFY 1
|
2018-10-02 09:44:46 -04:00
|
|
|
|
2018-10-26 16:57:07 -04:00
|
|
|
bool mycallCompound = Varicode::isCompoundCallsign(mycall);
|
|
|
|
|
2019-05-27 11:30:30 -04:00
|
|
|
QList<QPair<QString, int>> allFrames;
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
foreach(QString line, text.split(QRegExp("[\\r\\n]"), QString::SkipEmptyParts)){
|
2019-05-27 11:30:30 -04:00
|
|
|
QList<QPair<QString, int>> lineFrames;
|
|
|
|
|
2018-10-02 09:44:46 -04:00
|
|
|
// once we find a directed call, data encode the rest of the line.
|
|
|
|
bool hasDirected = false;
|
|
|
|
|
|
|
|
// do the same for when we have sent data...
|
|
|
|
bool hasData = false;
|
|
|
|
|
2018-10-25 22:55:54 -04:00
|
|
|
#if AUTO_REMOVE_MYCALL
|
2018-10-02 09:44:46 -04:00
|
|
|
// remove our callsign from the start of the line...
|
|
|
|
if(line.startsWith(mycall + ":") || line.startsWith(mycall + " ")){
|
|
|
|
line = lstrip(line.mid(mycall.length() + 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove trailing whitespace as long as there are characters left afterwards
|
|
|
|
auto rline = rstrip(line);
|
|
|
|
if(!rline.isEmpty()){
|
|
|
|
line = rline;
|
|
|
|
}
|
2018-10-25 22:55:54 -04:00
|
|
|
#endif
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
#if AUTO_PREPEND_DIRECTED
|
|
|
|
// see if we need to prepend the directed call to the line...
|
|
|
|
// if we have a selected call and the text doesn't start with that call...
|
2018-10-16 16:11:28 -04:00
|
|
|
// and if this isn't a raw message (starting with "`")... then...
|
|
|
|
if(!selectedCall.isEmpty() && !line.startsWith(selectedCall) && !line.startsWith("`")){
|
2018-10-02 09:44:46 -04:00
|
|
|
bool lineStartsWithBaseCall = (
|
2018-11-25 22:12:54 -05:00
|
|
|
line.startsWith("@ALLCALL") ||
|
|
|
|
Varicode::startsWithCQ(line) ||
|
|
|
|
Varicode::startsWithHB(line)
|
2018-10-02 09:44:46 -04:00
|
|
|
);
|
|
|
|
|
2018-10-27 15:14:54 -04:00
|
|
|
#if AUTO_PREPEND_DIRECTED_ALLOW_TEXT_CALLSIGNS
|
|
|
|
auto calls = Varicode::parseCallsigns(line);
|
2019-01-06 22:48:44 -05:00
|
|
|
bool lineStartsWithStandardCall = !calls.isEmpty() && line.startsWith(calls.first()) && calls.first().length() > 3;
|
2018-10-27 15:14:54 -04:00
|
|
|
#else
|
|
|
|
bool lineStartsWithStandardCall = false;
|
|
|
|
#endif
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
if(lineStartsWithBaseCall || lineStartsWithStandardCall){
|
|
|
|
// pass
|
|
|
|
} else {
|
|
|
|
// if the message doesn't start with a base call
|
|
|
|
// and if there are no other callsigns in this message
|
|
|
|
// or if the first callsign in the message isn't at the beginning...
|
|
|
|
// then we should be auto-prefixing this line with the selected call
|
|
|
|
|
|
|
|
line = QString("%1 %2").arg(selectedCall).arg(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
while(line.size() > 0){
|
|
|
|
QString frame;
|
|
|
|
|
|
|
|
bool useBcn = false;
|
|
|
|
#if ALLOW_SEND_COMPOUND
|
|
|
|
bool useCmp = false;
|
|
|
|
#endif
|
|
|
|
bool useDir = false;
|
|
|
|
bool useDat = false;
|
|
|
|
|
|
|
|
int l = 0;
|
2018-10-28 09:47:30 -04:00
|
|
|
QString bcnFrame = Varicode::packHeartbeatMessage(line, mycall, &l);
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
#if ALLOW_SEND_COMPOUND
|
|
|
|
int o = 0;
|
|
|
|
QString cmpFrame = Varicode::packCompoundMessage(line, &o);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int n = 0;
|
|
|
|
QString dirCmd;
|
|
|
|
QString dirTo;
|
|
|
|
QString dirNum;
|
2018-10-26 16:57:07 -04:00
|
|
|
bool dirToCompound = false;
|
|
|
|
QString dirFrame = Varicode::packDirectedMessage(line, mycall, &dirTo, &dirToCompound, &dirCmd, &dirNum, &n);
|
2018-10-07 08:44:47 -04:00
|
|
|
if(dirToCompound){
|
|
|
|
qDebug() << "directed message to field is compound" << dirTo;
|
|
|
|
}
|
2018-10-02 09:44:46 -04:00
|
|
|
|
2019-05-27 11:30:30 -04:00
|
|
|
#if ALLOW_FORCE_IDENTIFY
|
|
|
|
// if we're sending a data message, then ensure our callsign is included automatically
|
|
|
|
bool isLikelyDataFrame = lineFrames.isEmpty() && selectedCall.isEmpty() && dirTo.isEmpty() && l == 0 && o == 0;
|
|
|
|
if(forceIdentify && isLikelyDataFrame && !line.contains(mycall)){
|
|
|
|
line = QString("%1: %2").arg(mycall).arg(line);
|
|
|
|
}
|
|
|
|
#endif
|
2018-10-02 09:44:46 -04:00
|
|
|
int m = 0;
|
2018-10-03 12:36:45 -04:00
|
|
|
QString datFrame = Varicode::packDataMessage(line, &m);
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
// if this parses to a standard FT8 free text message
|
|
|
|
// but it can be parsed as a directed message, then we
|
|
|
|
// should send the directed version. if we've already sent
|
|
|
|
// a directed message or a data frame, we will only follow it
|
|
|
|
// with more data frames.
|
|
|
|
|
|
|
|
if(!hasDirected && !hasData && l > 0){
|
|
|
|
useBcn = true;
|
|
|
|
hasDirected = false;
|
|
|
|
frame = bcnFrame;
|
|
|
|
}
|
|
|
|
#if ALLOW_SEND_COMPOUND
|
|
|
|
else if(!hasDirected && !hasData && o > 0){
|
|
|
|
useCmp = true;
|
|
|
|
hasDirected = false;
|
|
|
|
frame = cmpFrame;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else if(!hasDirected && !hasData && n > 0){
|
|
|
|
useDir = true;
|
|
|
|
hasDirected = true;
|
|
|
|
frame = dirFrame;
|
|
|
|
}
|
|
|
|
else if (m > 0) {
|
|
|
|
useDat = true;
|
|
|
|
hasData = true;
|
|
|
|
frame = datFrame;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(useBcn){
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ frame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
line = line.mid(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if ALLOW_SEND_COMPOUND
|
|
|
|
if(useCmp){
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ frame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
line = line.mid(o);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if(useDir){
|
2018-10-26 16:57:07 -04:00
|
|
|
bool shouldUseStandardFrame = true;
|
|
|
|
|
|
|
|
#if ALLOW_SEND_COMPOUND_DIRECTED
|
2018-10-02 09:44:46 -04:00
|
|
|
/**
|
|
|
|
* We have a few special cases when we are sending to a compound call, or our call is a compound call, or both.
|
|
|
|
* CASE 0: Non-compound: KN4CRD: J1Y ACK
|
|
|
|
* -> One standard directed message frame
|
|
|
|
*
|
|
|
|
* CASE 1: Compound From: KN4CRD/P: J1Y ACK
|
|
|
|
* -> One standard compound frame, followed by a standard directed message frame with placeholder
|
|
|
|
* -> The second standard directed frame _could_ be replaced with a compound directed frame
|
|
|
|
* -> <KN4CRD/P EM73> then <....>: J1Y ACK
|
|
|
|
* -> <KN4CRD/P EM73> then <J1Y ACK>
|
|
|
|
*
|
|
|
|
* CASE 2: Compound To: KN4CRD: J1Y/P ACK
|
|
|
|
* -> One standard compound frame, followed by a compound directed frame
|
|
|
|
* -> <KN4CRD EM73> then <J1Y/P ACK>
|
|
|
|
*
|
|
|
|
* CASE 3: Compound From & To: KN4CRD/P: J1Y/P ACK
|
|
|
|
* -> One standard compound frame, followed by a compound directed frame
|
|
|
|
* -> <KN4CRD/P EM73> then <J1Y/P ACK>
|
|
|
|
**/
|
2018-10-26 16:57:07 -04:00
|
|
|
if(mycallCompound || dirToCompound){
|
|
|
|
qDebug() << "compound?" << mycallCompound << dirToCompound;
|
2018-10-02 09:44:46 -04:00
|
|
|
// Cases 1, 2, 3 all send a standard compound frame first...
|
2018-10-16 16:11:28 -04:00
|
|
|
QString deCompoundMessage = QString("`%1 %2").arg(mycall).arg(mygrid);
|
2018-10-02 09:44:46 -04:00
|
|
|
QString deCompoundFrame = Varicode::packCompoundMessage(deCompoundMessage, nullptr);
|
|
|
|
if(!deCompoundFrame.isEmpty()){
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ deCompoundFrame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Followed, by a standard OR compound directed message...
|
2018-10-26 16:57:07 -04:00
|
|
|
QString dirCompoundMessage = QString("`%1%2%3").arg(dirTo).arg(dirCmd).arg(dirNum);
|
2018-10-02 09:44:46 -04:00
|
|
|
QString dirCompoundFrame = Varicode::packCompoundMessage(dirCompoundMessage, nullptr);
|
|
|
|
if(!dirCompoundFrame.isEmpty()){
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ dirCompoundFrame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|
|
|
|
shouldUseStandardFrame = false;
|
|
|
|
}
|
2018-10-26 16:57:07 -04:00
|
|
|
#endif
|
2018-10-02 09:44:46 -04:00
|
|
|
|
|
|
|
if(shouldUseStandardFrame) {
|
|
|
|
// otherwise, just send the standard directed frame
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ frame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
line = line.mid(n);
|
|
|
|
|
|
|
|
// generate a checksum for buffered commands with line data
|
|
|
|
if(Varicode::isCommandBuffered(dirCmd) && !line.isEmpty()){
|
|
|
|
qDebug() << "generating checksum for line" << line << line.mid(1);
|
|
|
|
|
|
|
|
// strip leading whitespace after a buffered directed command
|
|
|
|
line = lstrip(line);
|
|
|
|
|
|
|
|
qDebug() << "before:" << line;
|
|
|
|
int checksumSize = Varicode::isCommandChecksumed(dirCmd);
|
|
|
|
|
|
|
|
if(checksumSize == 32){
|
|
|
|
line = line + " " + Varicode::checksum32(line);
|
|
|
|
} else if (checksumSize == 16) {
|
|
|
|
line = line + " " + Varicode::checksum16(line);
|
|
|
|
} else if (checksumSize == 0) {
|
|
|
|
// pass
|
2018-10-03 14:37:34 -04:00
|
|
|
qDebug() << "no checksum required for cmd" << dirCmd;
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|
|
|
|
qDebug() << "after:" << line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(useDat){
|
2019-05-27 11:30:30 -04:00
|
|
|
lineFrames.append({ frame, Varicode::JS8Call });
|
2018-10-02 09:44:46 -04:00
|
|
|
line = line.mid(m);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-27 11:30:30 -04:00
|
|
|
if(!lineFrames.isEmpty()){
|
|
|
|
lineFrames.first().second |= Varicode::JS8CallFirst;
|
|
|
|
lineFrames.last().second |= Varicode::JS8CallLast;
|
|
|
|
}
|
|
|
|
|
|
|
|
allFrames.append(lineFrames);
|
2018-10-29 03:26:10 -04:00
|
|
|
}
|
|
|
|
|
2019-05-27 11:30:30 -04:00
|
|
|
return allFrames;
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|
|
|
|
|
2019-05-27 11:30:30 -04:00
|
|
|
BuildMessageFramesThread::BuildMessageFramesThread(const QString &mycall,
|
2018-10-26 16:57:07 -04:00
|
|
|
const QString &mygrid,
|
2018-10-26 22:16:48 -04:00
|
|
|
const QString &selectedCall,
|
2019-05-27 11:30:30 -04:00
|
|
|
const QString &text,
|
|
|
|
bool forceIdentify,
|
|
|
|
QObject *parent):
|
2018-10-02 09:44:46 -04:00
|
|
|
QThread(parent),
|
|
|
|
m_mycall{mycall},
|
|
|
|
m_mygrid{mygrid},
|
2018-10-26 22:16:48 -04:00
|
|
|
m_selectedCall{selectedCall},
|
2019-05-27 11:30:30 -04:00
|
|
|
m_text{text},
|
|
|
|
m_forceIdentify{forceIdentify}
|
2018-10-02 09:44:46 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void BuildMessageFramesThread::run(){
|
|
|
|
auto results = Varicode::buildMessageFrames(
|
|
|
|
m_mycall,
|
|
|
|
m_mygrid,
|
2018-10-26 22:16:48 -04:00
|
|
|
m_selectedCall,
|
2019-05-27 11:30:30 -04:00
|
|
|
m_text,
|
|
|
|
m_forceIdentify
|
2018-10-02 09:44:46 -04:00
|
|
|
);
|
|
|
|
|
2018-11-01 02:19:48 -04:00
|
|
|
// TODO: jsherer - we wouldn't normally use decodedtext.h here... but it's useful for computing the actual frames transmitted.
|
|
|
|
QStringList textList;
|
|
|
|
qDebug() << "frames:";
|
|
|
|
foreach(auto frame, results){
|
|
|
|
auto dt = DecodedText(frame.first, frame.second);
|
|
|
|
qDebug() << "->" << frame << dt.message() << Varicode::frameTypeString(dt.frameType());
|
|
|
|
textList.append(dt.message());
|
2018-10-29 03:26:10 -04:00
|
|
|
}
|
|
|
|
|
2018-11-01 02:19:48 -04:00
|
|
|
auto transmitText = textList.join("");
|
|
|
|
emit resultReady(transmitText, results.length());
|
2018-10-02 09:44:46 -04:00
|
|
|
}
|