diff --git a/core/include/js8core/protocol/varicode.hpp b/core/include/js8core/protocol/varicode.hpp index 72a21519a..ba80976d7 100644 --- a/core/include/js8core/protocol/varicode.hpp +++ b/core/include/js8core/protocol/varicode.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/core/src/protocol/jsc.cpp b/core/src/protocol/jsc.cpp index c774ade01..aeb74b80a 100644 --- a/core/src/protocol/jsc.cpp +++ b/core/src/protocol/jsc.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/core/src/protocol/varicode.cpp b/core/src/protocol/varicode.cpp index 8263bf0c1..e1304c753 100644 --- a/core/src/protocol/varicode.cpp +++ b/core/src/protocol/varicode.cpp @@ -731,14 +731,26 @@ std::uint8_t pack_pwr(std::string const& pwr, bool* ok) { } } +// As desktop's Varicode::packCmd(): an SNR command is [1][type][6-bit +// number] (type 1 = HEARTBEAT SNR), any other command its 7-bit value. std::uint8_t pack_cmd(std::uint8_t cmd, std::uint8_t num, bool* p_packed_num) { - if (p_packed_num) *p_packed_num = kSnrCmds.count(cmd) > 0; - return (cmd & 0x1F) << 2 | (num & 0x03); + if (kSnrCmds.count(cmd) > 0) { + if (p_packed_num) *p_packed_num = true; + std::uint8_t value = static_cast(((1 << 1) | (cmd == 29 ? 1 : 0)) << 6); + return static_cast(value + (num & ((1 << 6) - 1))); + } + if (p_packed_num) *p_packed_num = false; + return static_cast(cmd & ((1 << 7) - 1)); } +// As desktop's Varicode::unpackCmd(). std::uint8_t unpack_cmd(std::uint8_t value, std::uint8_t* p_num) { - if (p_num) *p_num = value & 0x03; - return (value >> 2) & 0x1F; + if (value & (1 << 7)) { + if (p_num) *p_num = value & ((1 << 6) - 1); + return (value & (1 << 6)) ? 29 /* HEARTBEAT SNR */ : 25 /* SNR */; + } + if (p_num) *p_num = 0; + return value & ((1 << 7) - 1); } bool is_snr_command(std::string const& cmd) { diff --git a/core/tools/varicode_roundtrip.cpp b/core/tools/varicode_roundtrip.cpp index 31c0f3fc2..5753c0583 100644 --- a/core/tools/varicode_roundtrip.cpp +++ b/core/tools/varicode_roundtrip.cpp @@ -275,6 +275,48 @@ int main() { check(!saw_directed, "compound sender and recipient do not emit standard directed frame"); } + { + // Commands to and from a compound call (KG4UHM/6) packed as desktop's + // Varicode::packCmd(): frames from desktop's buildMessageFrames() (built + // with Qt), Normal speed. pack_cmd() kept 2 bits of the SNR and shifted + // every command: "KG4UHM/6 SNR -03" went out as SNR -31. + struct Case { char const* mycall; char const* text; char const* frames; }; + for (auto const& c : std::vector{ + {"VE7NHW", "KG4UHM/6 SNR -03", "Bu7hI9zNOrMu 1 IXXfezaRO+cm 2"}, + {"VE7NHW", "KG4UHM/6 SNR +05", "Bu7hI9zNOrMu 1 IXXfezaRO+dm 2"}, + {"VE7NHW", "KG4UHM/6 HEARTBEAT SNR -08", "Bu7hI9zNOrMu 1 IXXfezaRO+k8 2"}, + {"VE7NHW", "KG4UHM/6 HW CPY?", "Bu7hI9zNOrMu 1 IXXfezaRO+Le 2"}, + {"VE7NHW", "KG4UHM/6 RR", "Bu7hI9zNOrMu 1 IXXfezaRO+Lu 2"}, + {"VE7NHW", "KG4UHM/6 SNR?", "Bu7hI9zNOrMu 1 IXXfezaRO+JG 2"}, + {"KG4UHM/6", "KJ7PRS SNR -03", "AXXfezaROrMu 1 IYAexYQ7O+cm 2"}, + {"KG4UHM/6", "KJ7PRS HEARTBEAT SNR -08", "AXXfezaROrMu 1 IYAexYQ7O+k8 2"}, + {"KG4UHM/6", "VE7NHW FB", "AXXfezaROrMu 1 Ju7hI9zNO+LW 2"}, + }) { + std::string got; + for (auto const& [frame, bits] : js8core::protocol::varicode::build_message_frames( + c.mycall, "CN89", "", c.text, false, false, 0, nullptr)) + got += (got.empty() ? "" : " ") + frame + " " + std::to_string(bits); + check(got == c.frames, std::string("'") + c.text + "' from " + c.mycall + " matches desktop's frames"); + } + + // Desktop's frames read back with their command and SNR (KG4UHM/6 acking + // a heartbeat showed as "HW CPY?"). + struct Back { char const* frame; char const* call; char const* cmd; char const* num; }; + for (auto const& b : std::vector{ + {"Ju7hI9zNO+j0", "VE7NHW", " HEARTBEAT SNR", "-17"}, + {"IXXfezaRO+cm", "KG4UHM/6", " SNR", "-03"}, + {"IXXfezaRO+a8", "KG4UHM/6", " SNR", "-24"}, + {"IXXfezaRO+Le", "KG4UHM/6", " HW CPY?", ""}, + {"IXXfezaRO+Mm", "KG4UHM/6", " 73", ""}, + }) { + std::uint8_t type = 0; + auto parts = js8core::protocol::varicode::unpack_compound_message(b.frame, &type, nullptr, nullptr); + bool ok = parts.size() >= 3 && parts[0] == b.call && parts[2] == b.cmd && + (!*b.num || (parts.size() >= 4 && parts[3] == b.num)); + check(ok, std::string("desktop's compound frame ") + b.frame + " reads as" + b.cmd + " " + b.num); + } + } + if (failures != 0) { std::cerr << "varicode_roundtrip failures: " << failures << "\n"; return 1;