From d6fe3f0cac5a032747bea53125130f16970645ac Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 18 Jan 2023 20:56:37 +0000 Subject: [PATCH] Add AX.25 to JSON/MQTT. --- AX25Control.cpp | 375 +++++++++++++++++++++++++++++++++++------------- AX25Control.h | 5 +- DMRSlot.h | 2 + schema.json | 3 +- 4 files changed, 282 insertions(+), 103 deletions(-) diff --git a/AX25Control.cpp b/AX25Control.cpp index 3b108fc..53aec4d 100644 --- a/AX25Control.cpp +++ b/AX25Control.cpp @@ -21,6 +21,8 @@ #include #include +#include + // #define DUMP_AX25 const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U }; @@ -47,33 +49,37 @@ bool CAX25Control::writeModem(unsigned char *data, unsigned int len) if (!m_enabled) return false; - if (m_trace) - decode(data, len); + if (m_trace) + decode(data, len); - CUtils::dump(1U, "AX.25 received packet", data, len); + decodeJSON("rf", data, len); - if (m_network == NULL) - return true; + CUtils::dump(1U, "AX.25 received packet", data, len); - return m_network->write(data, len); + if (m_network == NULL) + return true; + + return m_network->write(data, len); } unsigned int CAX25Control::readModem(unsigned char* data) { - assert(data != NULL); + assert(data != NULL); - if (m_network == NULL) - return 0U; + if (m_network == NULL) + return 0U; - if (!m_enabled) - return 0U; + if (!m_enabled) + return 0U; - unsigned int length = m_network->read(data, 500U); + unsigned int length = m_network->read(data, 500U); - if (length > 0U) - CUtils::dump(1U, "AX.25 transmitted packet", data, length); + decodeJSON("network", data, length); - return length; + if (length > 0U) + CUtils::dump(1U, "AX.25 transmitted packet", data, length); + + return length; } bool CAX25Control::openFile() @@ -124,101 +130,240 @@ void CAX25Control::enable(bool enabled) void CAX25Control::decode(const unsigned char* data, unsigned int length) { - assert(data != NULL); - assert(length >= 15U); + assert(data != NULL); + assert(length >= 15U); - std::string text; + std::string text; - bool more = decodeAddress(data + 7U, text); + bool more = decodeAddress(data + 7U, text); - text += '>'; + text += '>'; - decodeAddress(data + 0U, text); + decodeAddress(data + 0U, text); - unsigned int n = 14U; - while (more && n < length) { - text += ','; - more = decodeAddress(data + n, text, true); - n += 7U; - } + unsigned int n = 14U; + while (more && n < length) { + text += ','; + more = decodeAddress(data + n, text, true); + n += 7U; + } - text += ' '; + text += ' '; - if ((data[n] & 0x01U) == 0x00U) { - // I frame - char t[20U]; - ::sprintf(t, "", (data[n] >> 1) & 0x07U, (data[n] >> 5) & 0x07U); - text += t; - } else { - if ((data[n] & 0x02U) == 0x00U) { - // S frame - char t[20U]; - switch (data[n] & 0x0FU) { - case 0x01U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x05U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x09U: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - case 0x0DU: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - default: - sprintf(t, "", (data[n] >> 5) & 0x07U); - break; - } + if ((data[n] & 0x01U) == 0x00U) { + // I frame + char t[20U]; + ::sprintf(t, "", (data[n] >> 1) & 0x07U, (data[n] >> 5) & 0x07U); + text += t; + } else { + if ((data[n] & 0x02U) == 0x00U) { + // S frame + char t[20U]; + switch (data[n] & 0x0FU) { + case 0x01U: + ::sprintf(t, "", (data[n] >> 5) & 0x07U); + break; + case 0x05U: + ::sprintf(t, "", (data[n] >> 5) & 0x07U); + break; + case 0x09U: + ::sprintf(t, "", (data[n] >> 5) & 0x07U); + break; + case 0x0DU: + ::sprintf(t, "", (data[n] >> 5) & 0x07U); + break; + default: + ::sprintf(t, "", (data[n] >> 5) & 0x07U); + break; + } - text += t; - LogMessage("AX.25, %s", text.c_str()); - return; - } else { - // U frame - switch (data[n] & 0xEFU) { - case 0x6FU: - text += ""; - break; - case 0x2FU: - text += ""; - break; - case 0x43U: - text += ""; - break; - case 0x0FU: - text += ""; - break; - case 0x63U: - text += ""; - break; - case 0x87U: - text += ""; - break; - case 0x03U: - text += ""; - break; - case 0xAFU: - text += ""; - break; - case 0xE3U: - text += ""; - break; - default: - text += ""; - break; - } + text += t; + LogMessage("AX.25, %s", text.c_str()); + return; + } else { + // U frame + switch (data[n] & 0xEFU) { + case 0x6FU: + text += ""; + break; + case 0x2FU: + text += ""; + break; + case 0x43U: + text += ""; + break; + case 0x0FU: + text += ""; + break; + case 0x63U: + text += ""; + break; + case 0x87U: + text += ""; + break; + case 0x03U: + text += ""; + break; + case 0xAFU: + text += ""; + break; + case 0xE3U: + text += ""; + break; + default: + text += ""; + break; + } - if ((data[n] & 0xEFU) != 0x03U) { - LogMessage("AX.25, %s", text.c_str()); - return; - } - } - } + if ((data[n] & 0xEFU) != 0x03U) { + LogMessage("AX.25, %s", text.c_str()); + return; + } + } + } - n += 2U; + n += 2U; - LogMessage("AX.25, %s %.*s", text.c_str(), length - n, data + n); + LogMessage("AX.25, %s %.*s", text.c_str(), length - n, data + n); +} + +void CAX25Control::decodeJSON(const char* source, const unsigned char* data, unsigned int length) +{ + assert(source != NULL); + assert(data != NULL); + assert(length >= 15U); + + nlohmann::json json; + + json["timestamp"] = CUtils::createTimestamp(); + + std::string text; + + bool isDigi; + bool more = decodeAddressJSON(data + 7U, text, isDigi); + json["source"] = text; + + decodeAddressJSON(data + 0U, text, isDigi); + json["destination"] = text; + + unsigned int n = 14U; + + if (more) { + while (more && n < length) { + nlohmann::json digi; + + more = decodeAddressJSON(data + n, text, isDigi); + n += 7U; + + digi["callsign"] = text; + digi["repeated"] = isDigi; + + json["digipeaters"] = digi; + } + } + + if ((data[n] & 0x01U) == 0x00U) { + // I frame + char t[20U]; + ::sprintf(t, "I S%u R%u", (data[n] >> 1) & 0x07U, (data[n] >> 5) & 0x07U); + json["type"] = t; + } else { + if ((data[n] & 0x02U) == 0x00U) { + // S frame + char t[20U]; + switch (data[n] & 0x0FU) { + case 0x01U: + ::sprintf(t, "RR R%u", (data[n] >> 5) & 0x07U); + break; + case 0x05U: + ::sprintf(t, "RNR R%u", (data[n] >> 5) & 0x07U); + break; + case 0x09U: + ::sprintf(t, "REJ R%u", (data[n] >> 5) & 0x07U); + break; + case 0x0DU: + ::sprintf(t, "SREJ R%u", (data[n] >> 5) & 0x07U); + break; + default: + ::sprintf(t, "Unknown R%u", (data[n] >> 5) & 0x07U); + break; + } + + json["type"] = t; + WriteJSON("AX.25", json); + return; + } else { + // U frame + switch (data[n] & 0xEFU) { + case 0x6FU: + text = "SABME"; + break; + case 0x2FU: + text = "SABM"; + break; + case 0x43U: + text = "DISC"; + break; + case 0x0FU: + text = "DM"; + break; + case 0x63U: + text = "UA"; + break; + case 0x87U: + text = "FRMR"; + break; + case 0x03U: + text = "UI"; + break; + case 0xAFU: + text = "XID"; + break; + case 0xE3U: + text = "TEST"; + break; + default: + text = "Unknown>"; + break; + } + + json["type"] = text; + + if ((data[n] & 0xEFU) != 0x03U) { + WriteJSON("AX.25", json); + return; + } + } + } + + n++; + + char buffer[5U]; + ::sprintf(buffer, "%02X", data[n]); + json["pid"] = buffer; + + n++; + + text.clear(); + + for (unsigned int i = 0U; i < (length - n); i++) { + char buffer[5U]; + ::sprintf(buffer, "%02X ", data[n + i]); + text += buffer; + } + + for (unsigned int i = 0U; i < (length - n); i++) { + char c = data[n + i]; + + if (::isprint(c) || c == ' ') + text += c; + else + text += '.'; + } + + json["data"] = text; + + WriteJSON("AX.25", json); } bool CAX25Control::decodeAddress(const unsigned char* data, std::string& text, bool isDigi) const @@ -237,8 +382,7 @@ bool CAX25Control::decodeAddress(const unsigned char* data, std::string& text, b if (ssid >= 10U) { text += '1'; text += '0' + ssid - 10U; - } - else { + } else { text += '0' + ssid; } } @@ -250,3 +394,32 @@ bool CAX25Control::decodeAddress(const unsigned char* data, std::string& text, b return (data[6U] & 0x01U) == 0x00U; } + +bool CAX25Control::decodeAddressJSON(const unsigned char* data, std::string& text, bool& isDigi) const +{ + assert(data != NULL); + + text.clear(); + + for (unsigned int i = 0U; i < 6U; i++) { + char c = data[i] >> 1; + if (c != ' ') + text += c; + } + + unsigned char ssid = (data[6U] >> 1) & 0x0FU; + if (ssid > 0U) { + text += '-'; + if (ssid >= 10U) { + text += '1'; + text += '0' + ssid - 10U; + } else { + text += '0' + ssid; + } + } + + isDigi = (data[6U] & 0x80U) == 0x80U; + + return (data[6U] & 0x01U) == 0x00U; +} + diff --git a/AX25Control.h b/AX25Control.h index b4fed2a..88ecf69 100644 --- a/AX25Control.h +++ b/AX25Control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 by Jonathan Naylor G4KLX + * Copyright (C) 2020,2023 by Jonathan Naylor G4KLX * * 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 @@ -41,7 +41,10 @@ private: FILE* m_fp; void decode(const unsigned char* data, unsigned int length); + void decodeJSON(const char* source, const unsigned char* data, unsigned int length); bool decodeAddress(const unsigned char* data, std::string& text, bool isDigi = false) const; + bool decodeAddressJSON(const unsigned char* data, std::string& text, bool& isDigi) const; + bool openFile(); bool writeFile(const unsigned char* data, unsigned int length); void closeFile(); diff --git a/DMRSlot.h b/DMRSlot.h index 6be9476..b35bc4d 100644 --- a/DMRSlot.h +++ b/DMRSlot.h @@ -37,6 +37,8 @@ #include +#include + enum ACTIVITY_TYPE { ACTIVITY_NONE, ACTIVITY_VOICE, diff --git a/schema.json b/schema.json index 0d5bbcd..8301060 100644 --- a/schema.json +++ b/schema.json @@ -11,9 +11,10 @@ "nxdn_id": {"type": "integer", "minimum": 1, "maximum": 65535}, "pocsag_ric": {"type": "integer"}, "destination_type": {"type": "string", "enum": ["group", "individual"]}, + "mode": {"type": "string", "enum": ["voice", "data"]}, "ysf_mode": {"type": "string", "enum": ["voice_vw", "voice_dn", "data_fr"]}, "dg-id": {"type": "integer", "minimum": 0, "maximum": 99}, - "ax25_type": {"type": "string", "enum": ["sabm", "disc", "ui", "ua", "rr", "rnr", "rej", "frmr", "i"]}, + "ax25_type": {"type": "string"}, "dmr_slot": {"type": "integer", "enum": [1, 2]}, "source": {"type": "string", "enum": ["rf", "network"]}, "fm_state": {"type": "string", "enum": ["listening", "kerchunk_rf", "relaying_rf", "relaying_wait_rf", "timeout_rf", "timeout_wait_rf", "kerchunk_ext", "relaying_ext", "relaying_wait_ext", "timeout_ext", "timeout_wait_ext", "hang", "unknown"]},