From 1f0bc3db322b3700db3f5e5fe3402922bb62e994 Mon Sep 17 00:00:00 2001 From: Chipster Date: Sun, 8 Nov 2026 09:08:17 -0600 Subject: [PATCH] Publish the modem's details over MQTT Once the modem is open, send a "Modem" message on the json topic with what its version reply said: protocol, description, CPU and UDID (when sent), the modes it can do, and how it is connected (UART port and speed, I2C port, or UDP address and port). The new remote command "modem" answers the same on the response topic, for a dashboard that starts after MMDVM-Host. schema.json describes the new message. Protocol 1 firmware built with ENABLE_UDID (MMDVM_HS) sends its chip ID as text after the NUL that ends the description; it is now read and logged as "UDID:" too. --- MMDVM-Host.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++++ MMDVM-Host.h | 5 ++++ Modem.cpp | 54 +++++++++++++++++++++++++++++++++++++-- Modem.h | 10 ++++++++ RemoteControl.cpp | 10 ++++++++ RemoteControl.h | 3 ++- schema.json | 20 ++++++++++++++- 7 files changed, 163 insertions(+), 4 deletions(-) diff --git a/MMDVM-Host.cpp b/MMDVM-Host.cpp index f0684129..de46522e 100644 --- a/MMDVM-Host.cpp +++ b/MMDVM-Host.cpp @@ -1795,6 +1795,8 @@ bool CMMDVMHost::createModem() return false; } + writeJSONModem(); + return true; } @@ -3175,6 +3177,69 @@ void CMMDVMHost::writeJSONMode(const std::string& mode) WriteJSON("MMDVM", json); } +void CMMDVMHost::buildModemInfo(nlohmann::json& json) +{ + json["timestamp"] = CUtils::createTimestamp(); + + if (m_modem == nullptr) { + json["open"] = false; + return; + } + + json["open"] = true; + json["protocol"] = m_modem->getVersion(); + json["description"] = m_modem->getDescription(); + if (!m_modem->getCPU().empty()) + json["cpu"] = m_modem->getCPU(); + if (!m_modem->getUDID().empty()) + json["udid"] = m_modem->getUDID(); + + nlohmann::json modes = nlohmann::json::array(); + if (m_modem->hasDStar()) + modes.push_back("D-Star"); + if (m_modem->hasDMR()) + modes.push_back("DMR"); + if (m_modem->hasYSF()) + modes.push_back("YSF"); + if (m_modem->hasP25()) + modes.push_back("P25"); + if (m_modem->hasNXDN()) + modes.push_back("NXDN"); + if (m_modem->hasFM()) + modes.push_back("FM"); + if (m_modem->hasPOCSAG()) + modes.push_back("POCSAG"); + json["modes"] = modes; + + std::string protocol = m_conf.getModemProtocol(); + json["connection"] = protocol; + if (protocol == "uart") { + json["port"] = m_conf.getModemUARTPort(); + json["speed"] = m_conf.getModemUARTSpeed(); + } else if (protocol == "i2c") { + json["port"] = m_conf.getModemI2CPort(); + } else if (protocol == "udp") { + json["address"] = m_conf.getModemModemAddress(); + json["port"] = m_conf.getModemModemPort(); + } +} + +void CMMDVMHost::buildModemInfoString(std::string& str) +{ + nlohmann::json json; + buildModemInfo(json); + + str = json.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore); +} + +void CMMDVMHost::writeJSONModem() +{ + nlohmann::json json; + buildModemInfo(json); + + WriteJSON("Modem", json); +} + void CMMDVMHost::writeJSONMessage(const std::string& message) { nlohmann::json json; diff --git a/MMDVM-Host.h b/MMDVM-Host.h index b4382f91..b4ecdf24 100644 --- a/MMDVM-Host.h +++ b/MMDVM-Host.h @@ -42,6 +42,8 @@ #include "Modem.h" #include "Conf.h" +#include + #include class CMMDVMHost @@ -54,6 +56,7 @@ class CMMDVMHost void buildNetworkStatusString(std::string &str); void buildNetworkHostsString(std::string &str); + void buildModemInfoString(std::string &str); private: void setListenOnly(bool on); @@ -211,6 +214,8 @@ class CMMDVMHost void writeJSONMode(const std::string& mode); void writeJSONMessage(const std::string& message); + void buildModemInfo(nlohmann::json& json); + void writeJSONModem(); static void onDisplay(const unsigned char* message, unsigned int length); static void onQSOInfo(const unsigned char* message, unsigned int length); diff --git a/Modem.cpp b/Modem.cpp index 63fb87d3..117d9cc9 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -318,6 +319,9 @@ m_fmExtEnable(false), #endif m_capabilities1(0x00U), m_capabilities2(0x00U), +m_description(), +m_cpu(), +m_udid(), m_serialDataLen(0U) { m_buffer = new unsigned char[BUFFER_LENGTH]; @@ -2057,30 +2061,61 @@ bool CModem::readVersion() if ((resp == RESP_TYPE_MMDVM::OK) && (m_buffer[2U] == MMDVM_GET_VERSION)) { m_protocolVersion = m_buffer[3U]; switch (m_protocolVersion) { - case 1U: + case 1U: { LogInfo("MMDVM protocol version: 1, description: %.*s", m_length - 4U, m_buffer + 4U); + // Firmware built with its chip ID sends it as text after + // a NUL that ends the description. + unsigned int descLen = 0U; + while ((4U + descLen) < m_length && m_buffer[4U + descLen] != 0x00U) + descLen++; + m_description = std::string((char*)(m_buffer + 4U), descLen); + m_cpu.clear(); + m_udid.clear(); + for (unsigned int i = 4U + descLen + 1U; i < m_length && m_buffer[i] != 0x00U; i++) { + if (::isxdigit(m_buffer[i])) + m_udid += char(::toupper(m_buffer[i])); + } + if (!m_udid.empty()) + LogInfo("UDID: %s", m_udid.c_str()); m_capabilities1 = CAP1_DSTAR | CAP1_DMR | CAP1_YSF | CAP1_P25 | CAP1_NXDN; m_capabilities2 = CAP2_POCSAG; + } break; - case 2U: + case 2U: { LogInfo("MMDVM protocol version: 2, description: %.*s", m_length - 23U, m_buffer + 23U); + m_description = std::string((char*)(m_buffer + 23U), m_length - 23U); + m_cpu.clear(); + m_udid.clear(); + unsigned int udidLen = 0U; switch (m_buffer[6U]) { case 0U: LogInfo("CPU: Atmel ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U], m_buffer[21U], m_buffer[22U]); + m_cpu = "Atmel ARM"; + udidLen = 16U; break; case 1U: LogInfo("CPU: NXP ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U], m_buffer[19U], m_buffer[20U], m_buffer[21U], m_buffer[22U]); + m_cpu = "NXP ARM"; + udidLen = 16U; break; case 2U: LogInfo("CPU: ST-Micro ARM, UDID: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", m_buffer[7U], m_buffer[8U], m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U], m_buffer[13U], m_buffer[14U], m_buffer[15U], m_buffer[16U], m_buffer[17U], m_buffer[18U]); + m_cpu = "ST-Micro ARM"; + udidLen = 12U; break; default: LogInfo("CPU: Unknown type: %u", m_buffer[6U]); break; } + for (unsigned int i = 0U; i < udidLen; i++) { + char hex[3U]; + ::snprintf(hex, 3U, "%02X", m_buffer[7U + i]); + m_udid += hex; + } m_capabilities1 = m_buffer[4U]; m_capabilities2 = m_buffer[5U]; + } break; default: @@ -2118,6 +2153,21 @@ bool CModem::readVersion() return false; } +std::string CModem::getDescription() const +{ + return m_description; +} + +std::string CModem::getCPU() const +{ + return m_cpu; +} + +std::string CModem::getUDID() const +{ + return m_udid; +} + bool CModem::readStatus() { assert(m_port != nullptr); diff --git a/Modem.h b/Modem.h index 0b882a84..d687ceda 100644 --- a/Modem.h +++ b/Modem.h @@ -87,6 +87,13 @@ class CModem { unsigned int getVersion() const; + // What the modem said in its version reply: its description, CPU + // (protocol 2 only) and chip ID (protocol 2, or protocol 1 firmware + // built to send one), each empty when it did not say. + std::string getDescription() const; + std::string getCPU() const; + std::string getUDID() const; + #if defined(USE_DSTAR) unsigned int readDStarData(unsigned char* data); #endif @@ -403,6 +410,9 @@ class CModem { #endif unsigned char m_capabilities1; unsigned char m_capabilities2; + std::string m_description; + std::string m_cpu; + std::string m_udid; bool readVersion(); bool readStatus(); diff --git a/RemoteControl.cpp b/RemoteControl.cpp index 7bcdd5d4..a4e6c0a5 100644 --- a/RemoteControl.cpp +++ b/RemoteControl.cpp @@ -198,6 +198,16 @@ REMOTE_COMMAND CRemoteControl::getCommand(const std::string& command) } m_command = REMOTE_COMMAND::CONFIG_HOSTS; + } else if (m_args.at(0U) == "modem") { + // The modem's details as JSON, the same as the "Modem" message + // sent when it was opened. + if (m_host != nullptr) { + m_host->buildModemInfoString(reply); + } else { + reply = "KO"; + } + + m_command = REMOTE_COMMAND::MODEM_INFO; } else { reply = "KO"; } diff --git a/RemoteControl.h b/RemoteControl.h index 96c6718a..34802265 100644 --- a/RemoteControl.h +++ b/RemoteControl.h @@ -97,7 +97,8 @@ enum class REMOTE_COMMAND { LISTEN_OFF, RELOAD, CONNECTION_STATUS, - CONFIG_HOSTS + CONFIG_HOSTS, + MODEM_INFO }; class CRemoteControl { diff --git a/schema.json b/schema.json index 07bce4af..db4ed200 100644 --- a/schema.json +++ b/schema.json @@ -23,7 +23,9 @@ "loss": {"type": "number", "minimum": 0.0}, "ber": {"type": "number", "minimum": 0.0}, "rssi": {"type": "integer", "minimum": -200, "maximum": -10}, - "timestamp": {"type": "string"} + "timestamp": {"type": "string"}, + "modem_mode": {"type": "string", "enum": ["D-Star", "DMR", "YSF", "P25", "NXDN", "FM", "POCSAG"]}, + "modem_connection": {"type": "string", "enum": ["uart", "udp", "i2c", "null"]} }, "MMDVM": { @@ -178,5 +180,21 @@ "timestamp": {"$ref": "#/$defs/timestamp"}, "state": {"$ref": "#/$defs/fm_state"}, "required": ["timestamp", "state"] + }, + + "Modem": { + "type": "object", + "timestamp": {"$ref": "#/$defs/timestamp"}, + "open": {"type": "boolean"}, + "protocol": {"type": "integer", "enum": [1, 2]}, + "description": {"type": "string"}, + "cpu": {"type": "string"}, + "udid": {"type": "string"}, + "modes": {"type": "array", "items": {"$ref": "#/$defs/modem_mode"}}, + "connection": {"$ref": "#/$defs/modem_connection"}, + "port": {"type": ["string", "integer"]}, + "speed": {"type": "integer"}, + "address": {"type": "string"}, + "required": ["timestamp", "open"] } }