Refactor compound call structure for directed messaging by using a new frame type FrameCompoundDirected. This allows us to send only two frames when each station has a compound callsign. No aliasing, full calls are sent with each transmission.

This commit is contained in:
Jordan Sherer 2018-08-03 17:07:36 -04:00
parent 172e1df31d
commit 7a155a4820
5 changed files with 292 additions and 118 deletions

View File

@ -21,6 +21,9 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
, contest_mode_ {contest_mode} , contest_mode_ {contest_mode}
, message_ {string_.mid (column_qsoText + padding_).trimmed ()} , message_ {string_.mid (column_qsoText + padding_).trimmed ()}
, is_standard_ {false} , is_standard_ {false}
, frameType_(Varicode::FrameUnknown)
, isBeacon_(false)
, isAlt_(false)
{ {
if(message_.length() >= 1) { if(message_.length() >= 1) {
message_ = message_.left (21).remove (QRegularExpression {"[<>]"}); message_ = message_.left (21).remove (QRegularExpression {"[<>]"});
@ -58,8 +61,12 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
tryUnpack(); tryUnpack();
} }
DecodedText::DecodedText (QString const& ft8callmessage){ DecodedText::DecodedText (QString const& ft8callmessage):
message_ = ft8callmessage; frameType_(Varicode::FrameUnknown),
message_(ft8callmessage),
isBeacon_(false),
isAlt_(false)
{
is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch(); is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch();
tryUnpack(); tryUnpack();
@ -80,7 +87,6 @@ bool DecodedText::tryUnpack(){
unpacked = tryUnpackCompound(); unpacked = tryUnpackCompound();
} }
if(!unpacked){ if(!unpacked){
unpacked = tryUnpackDirected(); unpacked = tryUnpackDirected();
} }
@ -101,7 +107,8 @@ bool DecodedText::tryUnpackBeacon(){
} }
bool isAlt = false; bool isAlt = false;
QStringList parts = Varicode::unpackBeaconMessage(m, &isAlt); quint8 type = Varicode::FrameUnknown;
QStringList parts = Varicode::unpackBeaconMessage(m, &type, &isAlt);
if(parts.isEmpty() || parts.length() < 2){ if(parts.isEmpty() || parts.length() < 2){
return false; return false;
@ -113,7 +120,7 @@ bool DecodedText::tryUnpackBeacon(){
// 1 1 CQ // 1 1 CQ
isBeacon_ = true; isBeacon_ = true;
isAlt_ = isAlt; isAlt_ = isAlt;
extra_ = parts.at(2); extra_ = parts.length() < 3 ? "" : parts.at(2);
QStringList cmp; QStringList cmp;
if(!parts.at(0).isEmpty()){ if(!parts.at(0).isEmpty()){
@ -124,7 +131,7 @@ bool DecodedText::tryUnpackBeacon(){
} }
compound_ = cmp.join("/"); compound_ = cmp.join("/");
message_ = QString("%1: ALLCALL %2 %3 ").arg(compound_).arg(isAlt ? "CQ" : "BCN").arg(extra_); message_ = QString("%1: ALLCALL %2 %3 ").arg(compound_).arg(isAlt ? "CQ" : "BCN").arg(extra_);
frameType_ = type;
return true; return true;
} }
@ -135,15 +142,12 @@ bool DecodedText::tryUnpackCompound(){
return false; return false;
} }
auto parts = Varicode::unpackCompoundMessage(m); quint8 type = Varicode::FrameUnknown;
auto parts = Varicode::unpackCompoundMessage(m, &type);
if(parts.isEmpty() || parts.length() < 2){ if(parts.isEmpty() || parts.length() < 2){
return false; return false;
} }
isBeacon_ = false;
extra_ = parts.at(2);
QStringList cmp; QStringList cmp;
if(!parts.at(0).isEmpty()){ if(!parts.at(0).isEmpty()){
cmp.append(parts.at(0)); cmp.append(parts.at(0));
@ -152,13 +156,29 @@ bool DecodedText::tryUnpackCompound(){
cmp.append(parts.at(1)); cmp.append(parts.at(1));
} }
compound_ = cmp.join("/"); compound_ = cmp.join("/");
extra_ = parts.length() < 3 ? "" : parts.mid(2).join(" ");
if(extra_.isEmpty()){ bool hasPrefixSuffix = compound_.contains("/");
message_ = QString("<%1> ").arg(compound_);
} else { if(type == Varicode::FrameCompound){
message_ = QString("<%1 %2> ").arg(compound_).arg(extra_); #if COMPOUND_SHOW_GRID
message_ = QString("<%1%2>:").arg(compound_).arg(extra_);
#endif
if(hasPrefixSuffix){
message_ = QString("<%1>: ").arg(compound_);
} else {
message_ = QString("%1: ").arg(compound_);
}
} else if(type == Varicode::FrameCompoundDirected){
if(hasPrefixSuffix){
message_ = QString("<%1>%2").arg(compound_).arg(extra_);
} else {
message_ = QString("%1%2").arg(compound_).arg(extra_);
}
directed_ = QStringList{ "<....>", compound_ } + parts.mid(2);
} }
frameType_ = type;
return true; return true;
} }
@ -170,7 +190,8 @@ bool DecodedText::tryUnpackDirected(){
return false; return false;
} }
QStringList parts = Varicode::unpackDirectedMessage(m); quint8 type = Varicode::FrameUnknown;
QStringList parts = Varicode::unpackDirectedMessage(m, &type);
if(parts.isEmpty()){ if(parts.isEmpty()){
return false; return false;
@ -188,6 +209,7 @@ bool DecodedText::tryUnpackDirected(){
} }
directed_ = parts; directed_ = parts;
frameType_ = type;
return true; return true;
} }
@ -199,13 +221,15 @@ bool DecodedText::tryUnpackData(){
return false; return false;
} }
QString data = Varicode::unpackDataMessage(m); quint8 type = Varicode::FrameUnknown;
QString data = Varicode::unpackDataMessage(m, &type);
if(data.isEmpty()){ if(data.isEmpty()){
return false; return false;
} }
message_ = data; message_ = data;
frameType_ = type;
return true; return true;
} }

View File

@ -39,10 +39,12 @@ public:
bool tryUnpackDirected(); bool tryUnpackDirected();
bool tryUnpackData(); bool tryUnpackData();
quint8 frameType() const { return frameType_; }
QString extra() const { return extra_; }
QString compoundCall() const { return compound_; } QString compoundCall() const { return compound_; }
bool isCompound() const { return !compound_.isEmpty(); } bool isCompound() const { return !compound_.isEmpty(); }
QString extra() const { return extra_; }
bool isBeacon() const { return isBeacon_; } bool isBeacon() const { return isBeacon_; }
bool isAlt() const { return isAlt_; } bool isAlt() const { return isAlt_; }
@ -96,6 +98,7 @@ private:
column_mode = 19, column_mode = 19,
column_qsoText = 22 }; column_qsoText = 22 };
quint8 frameType_;
bool isBeacon_; bool isBeacon_;
bool isAlt_; bool isAlt_;
QString compound_; QString compound_;

View File

@ -3392,8 +3392,9 @@ void MainWindow::readFromStdout() //readFromStdout
// Process compound callsign commands (put them in cache)" // Process compound callsign commands (put them in cache)"
#if 1 #if 1
qDebug() << "decoded" << decodedtext.frameType() << decodedtext.isCompound() << decodedtext.isDirectedMessage() << decodedtext.isBeacon();
bool shouldProcessCompound = true; bool shouldProcessCompound = true;
if(shouldProcessCompound && decodedtext.isCompound()){ if(shouldProcessCompound && decodedtext.isCompound() && !decodedtext.isDirectedMessage()){
CallDetail cd; CallDetail cd;
cd.call = decodedtext.compoundCall(); cd.call = decodedtext.compoundCall();
cd.grid = decodedtext.extra(); // compound calls via beacons may contain grid... cd.grid = decodedtext.extra(); // compound calls via beacons may contain grid...
@ -5905,7 +5906,9 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
bool useStd = false; bool useStd = false;
bool useBcn = false; bool useBcn = false;
#if ALLOW_SEND_COMPOUND
bool useCmp = false; bool useCmp = false;
#endif
bool useDir = false; bool useDir = false;
bool useDat = false; bool useDat = false;
bool isFree = false; bool isFree = false;
@ -5914,18 +5917,20 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
int l = 0; int l = 0;
QString bcnFrame = Varicode::packBeaconMessage(line, mycall, &l); QString bcnFrame = Varicode::packBeaconMessage(line, mycall, &l);
#if ALLOW_SEND_COMPOUND
int o = 0; int o = 0;
QString cmpFrame = Varicode::packCompoundMessage(line, &o); QString cmpFrame = Varicode::packCompoundMessage(line, &o);
#endif
int n = 0; int n = 0;
QString dirCmd; QString dirCmd;
QString dirTo; QString dirTo;
QString dirFrame = Varicode::packDirectedMessage(line, basecall, &dirTo, &dirCmd, &n); QString dirNum;
QString dirFrame = Varicode::packDirectedMessage(line, basecall, &dirTo, &dirCmd, &dirNum, &n);
bool dirToCompound = dirTo.contains("/");
// packDataMessage can output a new line to datLineOut (huff escaping special characters)
int m = 0; int m = 0;
QString datLineOut; QString datFrame = Varicode::packDataMessage(line.left(24) + "\x04", &m); // 66 / 3 + 2 = 22 (maximum number of 3bit chars we could possibly stuff in here plus 2 for good measure :P)
QString datFrame = Varicode::packDataMessage(line.left(24) + "\x04", &datLineOut, &m); // 66 / 3 + 2 = 22 (maximum number of 3bit chars we could possibly stuff in here plus 2 for good measure :P)
// if this parses to a standard FT8 free text message // if this parses to a standard FT8 free text message
// but it can be parsed as a directed message, then we // but it can be parsed as a directed message, then we
@ -5938,11 +5943,13 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
hasDirected = false; hasDirected = false;
frame = bcnFrame; frame = bcnFrame;
} }
#if ALLOW_SEND_COMPOUND
else if(isFree && !hasDirected && !hasData && o > 0){ else if(isFree && !hasDirected && !hasData && o > 0){
useCmp = true; useCmp = true;
hasDirected = false; hasDirected = false;
frame = cmpFrame; frame = cmpFrame;
} }
#endif
else if(isFree && !hasDirected && !hasData && n > 0){ else if(isFree && !hasDirected && !hasData && n > 0){
useDir = true; useDir = true;
hasDirected = true; hasDirected = true;
@ -5952,9 +5959,6 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
useDat = true; useDat = true;
hasData = true; hasData = true;
frame = datFrame; frame = datFrame;
if(!datLineOut.isEmpty()){
line = datLineOut;
}
} else { } else {
useStd = true; useStd = true;
frame = stdFrame; frame = stdFrame;
@ -5981,32 +5985,52 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
line = line.mid(l); line = line.mid(l);
} }
#if ALLOW_SEND_COMPOUND
if(useCmp){ if(useCmp){
frames.append(frame); frames.append(frame);
line = line.mid(o); line = line.mid(o);
} }
#endif
if(useDir){ if(useDir){
// from compound callsign /**
if(compound){ * We have a few special cases when we are sending to a compound call, or our call is a compound call, or both.
QString compoundMessage = QString("<%1 %2>").arg(mycall).arg(mygrid); * CASE 0: Non-compound: KN4CRD: J1Y ACK
QString compoundFrame = Varicode::packCompoundMessage(compoundMessage, nullptr); * -> One standard directed message frame
if(!compoundFrame.isEmpty()){ *
frames.append(compoundFrame); * 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>
**/
if(compound || dirToCompound){
// Cases 1, 2, 3 all send a standard compound frame first...
QString deCompoundMessage = QString("<%1 %2>").arg(mycall).arg(mygrid);
QString deCompoundFrame = Varicode::packCompoundMessage(deCompoundMessage, nullptr);
if(!deCompoundFrame.isEmpty()){
frames.append(deCompoundFrame);
} }
}
// to compound callsign // Followed, by a standard OR compound directed message...
if(!dirTo.isEmpty() && dirTo.contains("/")){ QString dirCompoundMessage = QString("<%1%2%3>").arg(dirTo).arg(dirCmd).arg(dirNum);
QString compoundMessage = QString("<%1>").arg(dirTo); QString dirCompoundFrame = Varicode::packCompoundMessage(dirCompoundMessage, nullptr);
QString compoundFrame = Varicode::packCompoundMessage(compoundMessage, nullptr); if(!dirCompoundFrame.isEmpty()){
if(!compoundFrame.isEmpty()){ frames.append(dirCompoundFrame);
frames.append(compoundFrame);
} }
} else {
// otherwise, just send the standard directed frame
frames.append(frame);
} }
frames.append(frame);
line = line.mid(n); line = line.mid(n);
// generate a checksum for buffered commands with line data // generate a checksum for buffered commands with line data
@ -6037,7 +6061,7 @@ QStringList MainWindow::buildFT8MessageFrames(QString const& text){
#if 1 #if 1
qDebug() << "parsed frames:"; qDebug() << "parsed frames:";
foreach(auto frame, frames){ foreach(auto frame, frames){
qDebug() << "->" << frame << Varicode::unpackDataMessage(frame) << Varicode::unpackDirectedMessage(frame) << Varicode::unpackCompoundFrame(frame, nullptr, nullptr) << Varicode::unpackBeaconMessage(frame, nullptr); qDebug() << "->" << frame << DecodedText(frame).message();
} }
#endif #endif
@ -6283,10 +6307,11 @@ void MainWindow::prepareBacon(){
} }
QString MainWindow::calculateDistance(QString const& grid) QString MainWindow::calculateDistance(QString const& value)
{ {
if(grid.isEmpty()){ QString grid = value.trimmed();
return QString(); if(grid.isEmpty() || grid.length() < 4){
return QString{};
} }
qint64 nsec = (QDateTime::currentMSecsSinceEpoch()/1000) % 86400; qint64 nsec = (QDateTime::currentMSecsSinceEpoch()/1000) % 86400;

View File

@ -68,16 +68,27 @@ QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, 6, 7, 8, 23, 24, 25, 26, 27, 28, 29,
QSet<int> buffered_cmds = {6, 7, 8}; QSet<int> buffered_cmds = {6, 7, 8};
QRegularExpression directed_re("^" QString callsign_pattern = QString("(?<callsign>[A-Z0-9/]+)");
"(?<to>[A-Z0-9/]+)" QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|RR|73|YES|NO|SNR|PWR|ACK|[?@&$^%|!# ]))?");
"(?<cmd>\\s?(?:AGN[?]|RR|73|YES|NO|SNR|PWR|ACK|[?@&$^%|!# ]))" QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
"(?<pwr>\\s?\\d+\\s?[KM]?W)?" QString optional_pwr_pattern = QString("(?<pwr>\\s?\\d+\\s?[KM]?W)?");
"(?<num>\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?" QString optional_num_pattern = QString("(?<num>\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
);
QRegularExpression directed_re("^" +
callsign_pattern +
optional_cmd_pattern +
optional_pwr_pattern +
optional_num_pattern);
QRegularExpression beacon_re(R"(^ALLCALL\s(?<type>CQ|BCN)(?:\s(?<grid>[A-Z]{2}[0-9]{2}))?\b)"); QRegularExpression beacon_re(R"(^ALLCALL\s(?<type>CQ|BCN)(?:\s(?<grid>[A-Z]{2}[0-9]{2}))?\b)");
QRegularExpression compound_re(R"(^[<](?<callsign>[A-Z0-9/]+)(?:\s(?<grid>[A-Z]{2}[0-9]{2}))?[>])"); QRegularExpression compound_re("^[<]" +
callsign_pattern +
optional_grid_pattern +
optional_cmd_pattern +
optional_pwr_pattern +
optional_num_pattern +
"[>]");
QMap<QString, QString> hufftable = { QMap<QString, QString> hufftable = {
// char code weight // char code weight
@ -305,6 +316,7 @@ QChar EOT = '\x04'; // EOT char
quint32 nbasecall = 37 * 36 * 10 * 27 * 27 * 27; quint32 nbasecall = 37 * 36 * 10 * 27 * 27 * 27;
quint16 nbasegrid = 180 * 180; quint16 nbasegrid = 180 * 180;
quint16 nusergrid = nbasegrid + 10;
quint16 nmaxgrid = (1<<15)-1; quint16 nmaxgrid = (1<<15)-1;
QMap<QString, quint32> basecalls = { QMap<QString, quint32> basecalls = {
@ -955,14 +967,12 @@ QPair<float, float> grid2deg(QString const &grid){
} }
// pack a 4-digit maidenhead grid locator into a 15-bit value // pack a 4-digit maidenhead grid locator into a 15-bit value
quint16 Varicode::packGrid(QString const& grid){ quint16 Varicode::packGrid(QString const& value){
// TODO: validate grid... QString grid = QString(value).trimmed();
if(grid.length() < 4){ if(grid.length() < 4){
return (1<<15)-1; return (1<<15)-1;
} }
// TODO: encode non-grid data...
auto pair = grid2deg(grid.left(4)); auto pair = grid2deg(grid.left(4));
int ilong = pair.first; int ilong = pair.first;
int ilat = pair.second + 90; int ilat = pair.second + 90;
@ -971,8 +981,7 @@ quint16 Varicode::packGrid(QString const& grid){
} }
QString Varicode::unpackGrid(quint16 value){ QString Varicode::unpackGrid(quint16 value){
if(value > 180*180){ if(value > nbasegrid){
// TODO: decode non-grid data... for now just return an empty string...
return ""; return "";
} }
@ -982,6 +991,81 @@ QString Varicode::unpackGrid(quint16 value){
return deg2grid(dlong, dlat).left(4); return deg2grid(dlong, dlat).left(4);
} }
// 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 pwr string into a dbm between 0 and 62
quint8 Varicode::packPwr(QString const &pwr, bool *ok){
int ipwr = 0;
if(pwr.isEmpty()){
if(ok) *ok = false;
return ipwr;
}
int factor = 1000;
if(pwr.endsWith("KW")){
factor = 1000000;
}
else if(pwr.endsWith("MW")){
factor = 1;
}
ipwr = QString(pwr).replace(QRegExp("[KM]?W", Qt::CaseInsensitive), "").toInt() * factor;
ipwr = mwattsToDbm(ipwr);
if(ok) *ok = true;
return ipwr + 1;
}
// pack a reduced fidelity command and a number into the extra bits provided between nbasegrid and nmaxgrid
quint8 Varicode::packCmd(quint8 cmd, quint8 num){
//quint8 allowed = nmaxgrid - nbasegrid - 1;
// if cmd == pwr || cmd == snr
quint8 value = 0;
if(cmd == directed_cmds[" PWR"] || cmd == directed_cmds[" SNR"]){
// 8 bits - 1 bit flag + 1 bit type + 6 bit number
// [1][X][6]
// X = 0 == SNR
// X = 1 == PWR
value = ((1 << 1) + ((int)cmd == directed_cmds[" PWR"])) << 6;
value = value + num;
} else {
value = cmd & ((1<<7)-1);
}
return value;
}
quint8 Varicode::unpackCmd(quint8 value, quint8 *pNum){
// if the first bit is 1, the second bit will indicate if its pwr or snr...
if(value & (1<<7)){
// either pwr or snr...
bool pwr = value & (1<<6);
if(pNum) *pNum = value & ((1<<6)-1);
if(pwr){
return directed_cmds[" PWR"];
} else {
return directed_cmds[" SNR"];
}
} else {
if(pNum) *pNum = 0;
return value & ((1<<7)-1);
}
}
bool Varicode::isCommandAllowed(const QString &cmd){ bool Varicode::isCommandAllowed(const QString &cmd){
return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]); return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
} }
@ -1049,7 +1133,7 @@ QString Varicode::packBeaconMessage(QString const &text, const QString &callsign
return frame; return frame;
} }
QStringList Varicode::unpackBeaconMessage(const QString &text, bool * isAlt){ QStringList Varicode::unpackBeaconMessage(const QString &text, quint8 *pType, bool * isAlt){
quint8 type = FrameBeacon; quint8 type = FrameBeacon;
quint16 num = nmaxgrid; quint16 num = nmaxgrid;
@ -1061,6 +1145,7 @@ QStringList Varicode::unpackBeaconMessage(const QString &text, bool * isAlt){
unpacked.append(Varicode::unpackGrid(num & ((1<<15)-1))); unpacked.append(Varicode::unpackGrid(num & ((1<<15)-1)));
if(isAlt) *isAlt = (num & (1<<15)); if(isAlt) *isAlt = (num & (1<<15));
if(pType) *pType = type;
return unpacked; return unpacked;
} }
@ -1078,8 +1163,11 @@ QString Varicode::packCompoundMessage(QString const &text, int *n){
return frame; return frame;
} }
auto callsign = parsedText.captured("callsign"); QString callsign = parsedText.captured("callsign");
auto grid = parsedText.captured("grid"); QString grid = parsedText.captured("grid");
QString cmd = parsedText.captured("cmd");
QString num = parsedText.captured("num").trimmed();
QString pwr = parsedText.captured("pwr").trimmed().toUpper();
auto parsedCall = QRegularExpression(compound_callsign_pattern).match(callsign); auto parsedCall = QRegularExpression(compound_callsign_pattern).match(callsign);
if(!parsedCall.hasMatch()){ if(!parsedCall.hasMatch()){
@ -1099,24 +1187,54 @@ QString Varicode::packCompoundMessage(QString const &text, int *n){
fix = parsedCall.captured("suffix"); fix = parsedCall.captured("suffix");
} }
quint16 extra = Varicode::packGrid(grid); quint8 type = FrameCompound;
quint16 extra = nmaxgrid;
frame = Varicode::packCompoundFrame(base, fix, isPrefix, FrameCompound, extra); if (!cmd.isEmpty() && directed_cmds.contains(cmd) && Varicode::isCommandAllowed(cmd)){
qint8 inum = Varicode::packNum(num, nullptr);
if(cmd.trimmed() == "PWR"){
inum = Varicode::packPwr(pwr, nullptr);
}
extra = nusergrid + Varicode::packCmd(directed_cmds[cmd], inum);
type = FrameCompoundDirected;
} else if(!grid.isEmpty()){
extra = Varicode::packGrid(grid);
}
frame = Varicode::packCompoundFrame(base, fix, isPrefix, type, extra);
if(n) *n = parsedText.captured(0).length(); if(n) *n = parsedText.captured(0).length();
return frame; return frame;
} }
QStringList Varicode::unpackCompoundMessage(const QString &text){ QStringList Varicode::unpackCompoundMessage(const QString &text, quint8 *pType){
quint8 type = FrameCompound; quint8 type = FrameCompound;
quint16 num = nmaxgrid; quint16 extra = nmaxgrid;
QStringList unpacked = unpackCompoundFrame(text, &type, &num); QStringList unpacked = unpackCompoundFrame(text, &type, &extra);
if(unpacked.isEmpty() || type != FrameCompound){ if(unpacked.isEmpty() || (type != FrameCompound && type != FrameCompoundDirected)){
return QStringList {}; return QStringList {};
} }
unpacked.append(Varicode::unpackGrid(num & ((1<<15)-1))); if(extra <= nbasegrid){
unpacked.append(" " + Varicode::unpackGrid(extra));
} else if (nusergrid <= extra && extra < nmaxgrid) {
quint8 num = 0;
auto cmd = Varicode::unpackCmd(extra - nusergrid, &num);
unpacked.append(directed_cmds.key(cmd));
if(cmd == directed_cmds[" PWR"]){
unpacked.append(Varicode::formatPWR(num - 1));
} else if(cmd == directed_cmds[" SNR"]){
unpacked.append(Varicode::formatSNR(num - 31));
}
}
if(pType) *pType = type;
return unpacked; return unpacked;
} }
@ -1124,7 +1242,7 @@ QStringList Varicode::unpackCompoundMessage(const QString &text){
QString Varicode::packCompoundFrame(const QString &baseCallsign, const QString &fix, bool isPrefix, quint8 type, quint16 num){ QString Varicode::packCompoundFrame(const QString &baseCallsign, const QString &fix, bool isPrefix, quint8 type, quint16 num){
QString frame; QString frame;
// needs to be a beacon type... // needs to be a compound type...
if(type == FrameDataPadded || type == FrameDataUnpadded || type == FrameDirectedPositive || type == FrameDirectedNegative){ if(type == FrameDataPadded || type == FrameDataUnpadded || type == FrameDirectedPositive || type == FrameDirectedNegative){
return frame; return frame;
} }
@ -1203,7 +1321,7 @@ QStringList Varicode::unpackCompoundFrame(const QString &text, quint8 *pType, qu
// J1Y? // J1Y?
// KN4CRD: J1Y$ // KN4CRD: J1Y$
// KN4CRD: J1Y! HELLO WORLD // KN4CRD: J1Y! HELLO WORLD
QString Varicode::packDirectedMessage(const QString &text, const QString &callsign, QString *pTo, QString * pCmd, int *n){ QString Varicode::packDirectedMessage(const QString &text, const QString &callsign, QString *pTo, QString * pCmd, QString *pNum, int *n){
QString frame; QString frame;
auto match = directed_re.match(text); auto match = directed_re.match(text);
@ -1213,11 +1331,17 @@ QString Varicode::packDirectedMessage(const QString &text, const QString &callsi
} }
QString from = callsign; QString from = callsign;
QString to = match.captured("to"); QString to = match.captured("callsign");
QString cmd = match.captured("cmd"); QString cmd = match.captured("cmd");
QString num = match.captured("num").trimmed(); QString num = match.captured("num").trimmed();
QString pwr = match.captured("pwr").trimmed().toUpper(); QString pwr = match.captured("pwr").trimmed().toUpper();
// ensure we have a directed command
if(cmd.isEmpty()){
if(n) *n = 0;
return frame;
}
// validate "to" callsign // validate "to" callsign
auto parsedTo = QRegularExpression(compound_callsign_pattern).match(to); auto parsedTo = QRegularExpression(compound_callsign_pattern).match(to);
bool validToCallsign = (to != callsign) && (basecalls.contains(to) || parsedTo.hasMatch()); bool validToCallsign = (to != callsign) && (basecalls.contains(to) || parsedTo.hasMatch());
@ -1242,27 +1366,16 @@ QString Varicode::packDirectedMessage(const QString &text, const QString &callsi
} }
// packing general number... // packing general number...
int inum = -31; quint8 inum = 0;
bool hasnum = false;
if(!num.isEmpty()){ if(cmd.trimmed() == "PWR"){
inum = qMax(-30, qMin(num.toInt(&hasnum, 10), 30)); inum = Varicode::packPwr(pwr, nullptr);
if(pNum) *pNum = pwr;
} else {
inum = Varicode::packNum(num, nullptr);
if(pNum) *pNum = num;
} }
// if we are packing a PWR command, pack pwr as dbm
int ipwr = -31;
if(!pwr.isEmpty() && cmd.trimmed() == "PWR"){
int factor = 1000;
if(pwr.endsWith("KW")){
factor = 1000000;
}
else if(pwr.endsWith("MW")){
factor = 1;
}
ipwr = pwr.replace(QRegExp("[KM]?W", Qt::CaseInsensitive), "").toInt() * factor;
inum = mwattsToDbm(ipwr) - 30;
}
quint8 packed_flag = inum < 0 ? FrameDirectedNegative : FrameDirectedPositive;
quint32 packed_from = Varicode::packCallsign(from); quint32 packed_from = Varicode::packCallsign(from);
quint32 packed_to = Varicode::packCallsign(to); quint32 packed_to = Varicode::packCallsign(to);
@ -1272,7 +1385,8 @@ QString Varicode::packDirectedMessage(const QString &text, const QString &callsi
} }
quint8 packed_cmd = directed_cmds[cmd]; quint8 packed_cmd = directed_cmds[cmd];
quint8 packed_extra = qAbs(inum); quint8 packed_flag = inum < 31 ? FrameDirectedNegative : FrameDirectedPositive;
quint8 packed_extra = inum < 31 ? inum : inum - 31;
// [3][28][28][5],[5] = 69 // [3][28][28][5],[5] = 69
auto bits = ( auto bits = (
@ -1287,7 +1401,7 @@ QString Varicode::packDirectedMessage(const QString &text, const QString &callsi
return Varicode::pack64bits(Varicode::bitsToInt(bits)) + Varicode::pack5bits(packed_extra % 32); return Varicode::pack64bits(Varicode::bitsToInt(bits)) + Varicode::pack5bits(packed_extra % 32);
} }
QStringList Varicode::unpackDirectedMessage(const QString &text){ QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
QStringList unpacked; QStringList unpacked;
if(text.length() < 13 || text.contains(" ")){ if(text.length() < 13 || text.contains(" ")){
@ -1301,9 +1415,9 @@ QStringList Varicode::unpackDirectedMessage(const QString &text){
int numSign = 0; int numSign = 0;
quint8 packed_flag = Varicode::bitsToInt(Varicode::strToBits(bits.left(3))); quint8 packed_flag = Varicode::bitsToInt(Varicode::strToBits(bits.left(3)));
if(packed_flag == FrameDirectedPositive){ if(packed_flag == FrameDirectedPositive){
numSign = 1; numSign = 31;
} else if(packed_flag == FrameDirectedNegative){ } else if(packed_flag == FrameDirectedNegative){
numSign = -1; numSign = 0;
} else { } else {
return unpacked; return unpacked;
} }
@ -1320,22 +1434,23 @@ QStringList Varicode::unpackDirectedMessage(const QString &text){
unpacked.append(to); unpacked.append(to);
unpacked.append(cmd); unpacked.append(cmd);
int num = numSign * extra; quint8 num = extra + numSign;
if(num != -31){ if(num != 0){
// TODO: jsherer - should we decide which format to use on the command, or something else? // TODO: jsherer - should we decide which format to use on the command, or something else?
if(packed_cmd == directed_cmds[" PWR"]){ if(packed_cmd == directed_cmds[" PWR"]){
unpacked.append(Varicode::formatPWR(num + 30)); unpacked.append(Varicode::formatPWR(num-1));
} else if(packed_cmd == directed_cmds[" SNR"]) { } else if(packed_cmd == directed_cmds[" SNR"]) {
unpacked.append(Varicode::formatSNR(num)); unpacked.append(Varicode::formatSNR((int)num-31));
} else { } else {
unpacked.append(QString("%1").arg(num)); unpacked.append(QString("%1").arg(num));
} }
} }
if(pType) *pType = packed_flag;
return unpacked; return unpacked;
} }
QString Varicode::packDataMessage(const QString &input, QString * out, int *n){ QString Varicode::packDataMessage(const QString &input, int *n){
QString frame; QString frame;
// [3][66] = 69 // [3][66] = 69
@ -1380,7 +1495,7 @@ QString Varicode::packDataMessage(const QString &input, QString * out, int *n){
return frame; return frame;
} }
QString Varicode::unpackDataMessage(const QString &text){ QString Varicode::unpackDataMessage(const QString &text, quint8 *pType){
QString unpacked; QString unpacked;
if(text.length() < 13 || text.contains(" ")){ if(text.length() < 13 || text.contains(" ")){
@ -1389,11 +1504,10 @@ QString Varicode::unpackDataMessage(const QString &text){
auto bits = Varicode::intToBits(Varicode::unpack64bits(text.left(12)), 64) + Varicode::intToBits(Varicode::unpack5bits(text.right(1)), 5); auto bits = Varicode::intToBits(Varicode::unpack64bits(text.left(12)), 64) + Varicode::intToBits(Varicode::unpack5bits(text.right(1)), 5);
quint8 flag = Varicode::bitsToInt(bits.mid(0, 3)); quint8 type = Varicode::bitsToInt(bits.mid(0, 3));
if(type == FrameDataUnpadded){
if(flag == FrameDataUnpadded){
bits = bits.mid(3); bits = bits.mid(3);
} else if(flag == FrameDataPadded) { } else if(type == FrameDataPadded) {
int n = bits.lastIndexOf(0); int n = bits.lastIndexOf(0);
bits = bits.mid(3, n-3); bits = bits.mid(3, n-3);
} else { } else {
@ -1406,5 +1520,7 @@ QString Varicode::unpackDataMessage(const QString &text){
// then... unescape special characters // then... unescape special characters
unpacked = Varicode::huffUnescape(unpacked); unpacked = Varicode::huffUnescape(unpacked);
if(pType) *pType = type;
return unpacked; return unpacked;
} }

View File

@ -27,14 +27,15 @@ public:
}; };
enum FrameType { enum FrameType {
FrameBeacon = 0, // [000] FrameUnknown = 255, // [11111111] <- only used as a sentinel
FrameCompound = 1, // [001] FrameBeacon = 0, // [000]
FrameDirectedPositive = 2, // [010] FrameCompound = 1, // [001]
FrameDirectedNegative = 3, // [011] FrameCompoundDirected = 2, // [010]
FrameDataUnpadded = 4, // [100] FrameDirectedPositive = 3, // [011]
FrameDataPadded = 5, // [101] FrameDirectedNegative = 4, // [100]
FrameReservedA = 6, // [110] FrameDataUnpadded = 5, // [101]
FrameReservedB = 7, // [111] FrameDataPadded = 6, // [110]
FrameReservedX = 7, // [111]
}; };
//Varicode(); //Varicode();
@ -92,23 +93,28 @@ public:
static quint16 packGrid(QString const& value); static quint16 packGrid(QString const& value);
static QString unpackGrid(quint16 value); static QString unpackGrid(quint16 value);
static quint8 packNum(QString const &num, bool *ok);
static quint8 packPwr(QString const &pwr, bool *ok);
static quint8 packCmd(quint8 cmd, quint8 num);
static quint8 unpackCmd(quint8 value, quint8 *pNum);
static bool isCommandAllowed(const QString &cmd); static bool isCommandAllowed(const QString &cmd);
static bool isCommandBuffered(const QString &cmd); static bool isCommandBuffered(const QString &cmd);
static QString packBeaconMessage(QString const &text, QString const&callsign, int *n); static QString packBeaconMessage(QString const &text, QString const&callsign, int *n);
static QStringList unpackBeaconMessage(const QString &text, bool *isAlt); static QStringList unpackBeaconMessage(const QString &text, quint8 *pType, bool *isAlt);
static QString packCompoundMessage(QString const &text, int *n); static QString packCompoundMessage(QString const &text, int *n);
static QStringList unpackCompoundMessage(const QString &text); static QStringList unpackCompoundMessage(const QString &text, quint8 *pType);
static QString packCompoundFrame(const QString &baseCallsign, const QString &fix, bool isPrefix, quint8 type, quint16 num); static QString packCompoundFrame(const QString &baseCallsign, const QString &fix, bool isPrefix, quint8 type, quint16 num);
static QStringList unpackCompoundFrame(const QString &text, quint8 *pType, quint16 *pNum); static QStringList unpackCompoundFrame(const QString &text, quint8 *pType, quint16 *pNum);
static QString packDirectedMessage(QString const& text, QString const& callsign, QString *pTo, QString * pCmd, int *n); static QString packDirectedMessage(QString const& text, QString const& callsign, QString *pTo, QString * pCmd, QString *pNum, int *n);
static QStringList unpackDirectedMessage(QString const& text); static QStringList unpackDirectedMessage(QString const& text, quint8 *pType);
static QString packDataMessage(QString const& text, QString *out, int *n); static QString packDataMessage(QString const& text, int *n);
static QString unpackDataMessage(QString const& text); static QString unpackDataMessage(QString const& text, quint8 *pType);
}; };
#endif // VARICODE_H #endif // VARICODE_H