diff --git a/.clang-format b/.clang-format index ac7e5d71..2c48af9f 100644 --- a/.clang-format +++ b/.clang-format @@ -106,6 +106,7 @@ ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 4 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine # Taken from git's rules PenaltyBreakAssignment: 30 diff --git a/CMakeLists.txt b/CMakeLists.txt index f05b58f8..4337bc32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ target_sources(app openrtx/src/protocols/M17/M17DSP.cpp openrtx/src/protocols/M17/M17Golay.cpp openrtx/src/protocols/M17/M17Callsign.cpp + openrtx/src/protocols/M17/Callsign.cpp openrtx/src/protocols/M17/M17Modulator.cpp openrtx/src/protocols/M17/M17Demodulator.cpp openrtx/src/protocols/M17/M17FrameEncoder.cpp diff --git a/meson.build b/meson.build index 2e36fa30..2867d53f 100644 --- a/meson.build +++ b/meson.build @@ -62,6 +62,7 @@ openrtx_src = ['openrtx/src/core/state.c', 'openrtx/src/protocols/M17/M17DSP.cpp', 'openrtx/src/protocols/M17/M17Golay.cpp', 'openrtx/src/protocols/M17/M17Callsign.cpp', + 'openrtx/src/protocols/M17/Callsign.cpp', 'openrtx/src/protocols/M17/M17Modulator.cpp', 'openrtx/src/protocols/M17/M17Demodulator.cpp', 'openrtx/src/protocols/M17/M17FrameEncoder.cpp', diff --git a/openrtx/include/protocols/M17/Callsign.hpp b/openrtx/include/protocols/M17/Callsign.hpp new file mode 100644 index 00000000..6f875c22 --- /dev/null +++ b/openrtx/include/protocols/M17/Callsign.hpp @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 * + ***************************************************************************/ + +#ifndef CALLSIGN_H +#define CALLSIGN_H + +#ifndef __cplusplus +#error This header is C++ only! +#endif + +#include +#include "M17Datatypes.hpp" + +namespace M17 +{ + +/** + * Class representing an M17 callsign object. + */ +class Callsign +{ +public: + /** + * Default constructor. + * By default, an uninitialized callsign is set to invalid. + */ + Callsign(); + + /** + * Construct a callsign object from an std::string + * The callsign can have up to 9 characters + * + * @param callsign: callsign string + */ + Callsign(const std::string callsign); + + /** + * Construct a callsign object from a NULL-terminated string + * The callsign can have up to 9 characters + * + * @param callsign: callsign string + */ + Callsign(const char *callsign); + + /** + * Construct a callsign object from a base-40 encoded callsign + * + * @param encodedCall: encoded callsign value + */ + Callsign(const call_t &encodedCall); + + /** + * Test if callsign is empty. + * A callsign is considered empty when its first character is NULL. + * + * @return true if the callsign is empty + */ + inline bool isEmpty() const + { + return call[0] == '\0'; + } + + /** + * Test if callsign is a special one. + * + * @return true if the callsign is either ALL, INFO or ECHO + */ + bool isSpecial() const; + + /** + * Type-conversion operator to retrieve the callsign in encoded format + * + * @return the base-40 encoded version of the callsign + */ + operator call_t() const; + + /** + * Type-conversion operator to retrieve the callsign as a std::string + * + * @return a std::string containing the callsign + */ + operator std::string() const; + + /** + * Type-conversion operator to retrieve the callsign as a NULL-terminated + * string + * + * @return the callsign as a NULL-terminated string + */ + operator const char *() const; + + /** + * Comparison operator. + * + * @param other the incoming callsign to compare against + * @return true if callsigns are equivalent + */ + bool operator==(const Callsign &other) const; + +private: + static constexpr size_t MAX_CALLSIGN_CHARS = 9; + static constexpr size_t MAX_CALLSIGN_LEN = MAX_CALLSIGN_CHARS + 1; + char call[MAX_CALLSIGN_LEN]; +}; + +} // namespace M17 + +#endif // CALLSIGN_H diff --git a/openrtx/src/protocols/M17/Callsign.cpp b/openrtx/src/protocols/M17/Callsign.cpp new file mode 100644 index 00000000..ed662e44 --- /dev/null +++ b/openrtx/src/protocols/M17/Callsign.cpp @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * 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 * + ***************************************************************************/ + +#include +#include "protocols/M17/Callsign.hpp" + +using namespace M17; + +static const char BROADCAST_CALL[] = "ALL"; +static const char INVALID_CALL[] = "INVALID"; +static const char charMap[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."; + +Callsign::Callsign() : Callsign(INVALID_CALL) +{ +} + +Callsign::Callsign(const std::string callsign) : Callsign(callsign.c_str()) +{ +} + +Callsign::Callsign(const char *callsign) +{ + std::memset(call, 0, sizeof(call)); + std::strncpy(call, callsign, MAX_CALLSIGN_CHARS); +} + +Callsign::Callsign(const call_t &encodedCall) +{ + bool isBroadcast = true; + bool isInvalid = true; + + for (auto &elem : encodedCall) { + if (elem != 0xFF) + isBroadcast = false; + + if (elem != 0x00) + isInvalid = false; + } + + std::memset(call, 0, sizeof(call)); + + if (isBroadcast) { + std::strncpy(call, BROADCAST_CALL, sizeof(call)); + return; + } + + if (isInvalid) { + std::strncpy(call, INVALID_CALL, sizeof(call)); + return; + } + + // Convert to little endian format + uint64_t encoded = 0; + auto p = reinterpret_cast(&encoded); + std::copy(encodedCall.rbegin(), encodedCall.rend(), p); + + size_t pos = 0; + while (encoded != 0 && pos < MAX_CALLSIGN_CHARS) { + call[pos++] = charMap[encoded % 40]; + encoded /= 40; + } +} + +bool Callsign::isSpecial() const +{ + if ((std::strcmp(call, "INFO") == 0) || (std::strcmp(call, "ECHO") == 0) + || (std::strcmp(call, "ALL") == 0)) + return true; + + return false; +} + +Callsign::operator std::string() const +{ + return std::string(call); +} + +Callsign::operator const char *() const +{ + return call; +} + +Callsign::operator call_t() const +{ + call_t encoded; + uint64_t tmp = 0; + + if (strcmp(call, BROADCAST_CALL) == 0) { + encoded.fill(0xFF); + return encoded; + } + + if (strcmp(call, INVALID_CALL) == 0) { + encoded.fill(0x00); + return encoded; + } + + for (int i = strlen(call) - 1; i >= 0; i--) { + tmp *= 40; + + if (call[i] >= 'A' && call[i] <= 'Z') { + tmp += (call[i] - 'A') + 1; + } else if (call[i] >= '0' && call[i] <= '9') { + tmp += (call[i] - '0') + 27; + } else if (call[i] == '-') { + tmp += 37; + } else if (call[i] == '/') { + tmp += 38; + } else if (call[i] == '.') { + tmp += 39; + } + } + + // Return encoded callsign in big endian format + auto *ptr = reinterpret_cast(&tmp); + std::copy(ptr, ptr + 6, encoded.rbegin()); + + return encoded; +} + +bool Callsign::operator==(const Callsign &other) const +{ + // find slash and possibly truncate if slash is within first 3 chars + const char *truncatedLocal = call; + const char *truncatedIncoming = other.call; + + const char *slash = std::strchr(call, '/'); + if (slash && (slash - call) <= 2) + truncatedLocal = slash + 1; + + slash = std::strchr(other.call, '/'); + if (slash && (slash - other.call) <= 2) + truncatedIncoming = slash + 1; + + return std::strcmp(truncatedLocal, truncatedIncoming) == 0; +} diff --git a/scripts/clang_format.sh b/scripts/clang_format.sh index 8e5d1e1c..55f4e80d 100755 --- a/scripts/clang_format.sh +++ b/scripts/clang_format.sh @@ -69,9 +69,11 @@ openrtx/include/interfaces/radio.h openrtx/include/peripherals/gps.h openrtx/include/peripherals/rng.h openrtx/include/peripherals/rtc.h +openrtx/include/protocols/M17/Callsign.hpp openrtx/include/protocols/M17/M17FrameDecoder.hpp openrtx/src/core/dsp.cpp openrtx/src/core/memory_profiling.cpp +openrtx/src/protocols/M17/Callsign.cpp openrtx/src/protocols/M17/M17FrameDecoder.cpp platform/drivers/ADC/ADC0_GDx.h platform/drivers/audio/MAX9814.h