Files
MeshCore/test/fixtures/morse_encoder.cpp
T
mikecarper 0be4c7488c Add bounded Morse receive and negotiated RS232 autosleep
Add native narrow-band CW capture, ACK-first remote sessions, retained LoRa-readable pages, checked restoration and transport-safe progress. Add stock-compatible negotiated heartbeat/autosleep and recovery. Include host regressions and identity-bound HIL runners; keep unverified faster tuning HIL-only and document outstanding hardware validation.
2026-10-13 01:24:39 -07:00

142 lines
6.2 KiB
C++

#include <helpers/MorseTx.h>
#include <cassert>
#include <cstring>
#include <initializer_list>
#include <string>
#include <vector>
using mesh::MorseTx;
using Result = mesh::MorseTxResult;
using Edge = MorseTx::Edge;
static std::string unpack(uint8_t code) {
std::string result;
const unsigned count = MorseTx::symbolCount(code);
for (unsigned bit = count; bit; --bit)
result += (code & (1U << (bit - 1))) ? '-' : '.';
return result;
}
static void alphabet() {
const char* letters[] = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....",
"..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.",
"...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."};
const char* digits[] = {"-----", ".----", "..---", "...--", "....-", ".....",
"-....", "--...", "---..", "----."};
for (int i = 0; i < 26; ++i) {
assert(unpack(MorseTx::codeFor('A' + i)) == letters[i]);
assert(MorseTx::codeFor('a' + i) == MorseTx::codeFor('A' + i));
}
for (int i = 0; i < 10; ++i) assert(unpack(MorseTx::codeFor('0' + i)) == digits[i]);
const char punctuation[] = ".,?'!/()&:;=+-_\"$@";
const char* patterns[] = {".-.-.-", "--..--", "..--..", ".----.", "-.-.--", "-..-.",
"-.--.", "-.--.-", ".-...", "---...", "-.-.-.", "-...-", ".-.-.", "-....-",
"..--.-", ".-..-.", "...-..-", ".--.-."};
static_assert(sizeof(punctuation) - 1 == sizeof(patterns) / sizeof(*patterns), "test table");
for (size_t i = 0; i < sizeof(punctuation) - 1; ++i)
assert(unpack(MorseTx::codeFor(punctuation[i])) == patterns[i]);
for (unsigned i = 0; i < 256; ++i) {
const char c = static_cast<char>(i);
const bool known = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9') || (c && std::strchr(punctuation, c));
assert(bool(MorseTx::codeFor(c)) == known);
}
}
static void timing(const char* message, const std::vector<uint32_t>& phases,
uint32_t start) {
MorseTx timer;
uint32_t duration = 0;
assert(MorseTx::validate(message, 25, &duration) == Result::Started);
uint32_t total = 0;
for (auto phase : phases) total += phase;
assert(duration == total);
assert(timer.start(message, 25, start) == Result::Started);
assert(timer.active() && timer.keyed() && timer.remainingMillis(start) == total);
uint32_t now = start;
for (size_t phase = 0; phase < phases.size(); ++phase) {
assert(timer.nextDeadline() == now + phases[phase]);
assert(timer.advance(now + phases[phase] - 1) == Edge::None);
assert(timer.keyed() == (phase % 2 == 0));
now += phases[phase];
const auto edge = timer.advance(now);
if (phase + 1 == phases.size()) {
assert(edge == Edge::Complete && !timer.active() && !timer.keyed());
} else {
assert(edge == (phase % 2 ? Edge::KeyDown : Edge::KeyUp));
assert(timer.remainingMillis(now) == total - uint32_t(now - start));
}
}
assert(timer.remainingMillis(now) == 0 && timer.advance(now) == Edge::None);
}
static void validation_and_limits() {
assert(MorseTx::DefaultWpm == 25 && MorseTx::DefaultFrequencyMHz == 902.100f);
assert(MorseTx::unitMillis(1) == 1200 && MorseTx::unitMillis(25) == 48);
assert(MorseTx::unitMillis(60) == 20);
for (unsigned speed = 1; speed <= MorseTx::MaxWpm; ++speed) {
uint32_t duration = 0;
assert(MorseTx::validate("T", speed, &duration) == Result::Started);
assert(duration == 3U * ((1200U + speed / 2U) / speed));
}
for (uint8_t speed : {uint8_t(0), uint8_t(MorseTx::MaxWpm + 1)})
assert(MorseTx::validate("E", speed) == Result::InvalidSpeed);
for (const char* invalid : {"", " ", "#", "line\nline", "tab\t", "\x80", "\xff"})
assert(MorseTx::validate(invalid, 25) == Result::InvalidText);
assert(MorseTx::validate(nullptr, 25) == Result::InvalidText);
assert(MorseTx::validate(std::string(80, 'E').c_str(), 25) == Result::Started);
assert(MorseTx::validate(std::string(81, 'E').c_str(), 25) == Result::TooLong);
uint32_t duration = 0;
assert(MorseTx::validate(std::string(38, 'E').c_str(), 1, &duration) == Result::Started);
assert(duration == 178800);
assert(MorseTx::validate(std::string(39, 'E').c_str(), 1) == Result::TooLong);
assert(MorseTx::validate(std::string(80, 'T').c_str(), 1) == Result::TooLong);
// A caller does not receive a duration for a rejected transmission.
duration = 0x12345678;
assert(MorseTx::validate("#", 25, &duration) == Result::InvalidText);
assert(duration == 0x12345678);
MorseTx timer;
for (const char* invalid : {"", "#", "\xff"}) {
assert(timer.start(invalid, 25, 0) == Result::InvalidText);
assert(!timer.active() && !timer.keyed());
}
}
static void slow_service_and_cancel() {
MorseTx timer;
assert(timer.start("SOS", 25, 100) == Result::Started);
assert(timer.advance(10000) == Edge::KeyUp); // no missed-edge catch-up burst
assert(!timer.keyed() && timer.nextDeadline() == 10048);
for (int i = 0; i < 100; ++i) assert(timer.advance(10000) == Edge::None);
assert(timer.advance(10048) == Edge::KeyDown);
timer.stop();
assert(!timer.active() && !timer.keyed() && !timer.remainingMillis(0));
assert(timer.advance(20000) == Edge::None);
assert(timer.start("T", 1, 0xfffffff0) == Result::Started);
assert(timer.hardDeadline() == uint32_t(0xfffffff0 + 180000));
assert(timer.advance(timer.hardDeadline() - 1) == Edge::Complete);
assert(timer.start("SOS", 25, 0xfffffff0) == Result::Started);
assert(timer.advance(timer.hardDeadline()) == Edge::Expired);
assert(!timer.active() && !timer.keyed() && timer.advance(0) == Edge::None);
}
static void frequency_guard() {
assert(!mesh::morseFrequencyOverlaps(902.100f, 910.525f, 500));
assert(mesh::morseFrequencyOverlaps(902.100f, 902.100f, 500));
assert(mesh::morseFrequencyOverlaps(902.100f, 902.350f, 500));
assert(!mesh::morseFrequencyOverlaps(902.100f, 902.375f, 500));
assert(!mesh::morseFrequencyOverlaps(902.100f, 0, 500));
assert(!mesh::morseFrequencyOverlaps(902.100f, 902.100f, 0));
assert(!mesh::morseFrequencyOverlaps(NAN, 902.100f, 500));
assert(!mesh::morseFrequencyOverlaps(INFINITY, 902.100f, 500));
}
int main() {
alphabet();
validation_and_limits();
timing("SOS", {48,48,48,48,48,144,144,48,144,48,144,144,48,48,48,48,48}, 100);
timing(" a B ", {48,48,144,336,144,48,48,48,48,48,48}, 0xfffffff0);
slow_service_and_cancel();
frequency_guard();
}