// SPDX-License-Identifier: GPL-2.0-only /** * Digital Voice Modem - Modem Host Software * GPLv2 Open Source. Use is subject to license terms. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * @package DVM / Modem Host Software * @derivedfrom MMDVMCal (https://github.com/g4klx/MMDVMCal) * @license GPLv2 License (https://opensource.org/licenses/GPL-2.0) * * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX * Copyright (C) 2017,2018 Andy Uribe, CA6JAU * Copyright (C) 2021-2023 Bryan Biedenkapp, N2PLL * */ #include "common/dmr/DMRDefines.h" #include "common/p25/P25Defines.h" #include "common/p25/data/DataHeader.h" #include "common/p25/lc/LC.h" #include "common/p25/lc/tsbk/TSBKFactory.h" #include "common/p25/NID.h" #include "common/p25/Sync.h" #include "common/p25/P25Utils.h" #include "common/nxdn/NXDNDefines.h" #include "common/nxdn/channel/LICH.h" #include "common/nxdn/NXDNUtils.h" #include "common/edac/CRC.h" #include "modem/port/ModemNullPort.h" #include "modem/port/UARTPort.h" #include "common/Log.h" #include "common/Utils.h" #include "setup/HostSetup.h" #include "HostMain.h" using namespace modem; using namespace lookups; #include #include #include #if !defined(CATCH2_TEST_COMPILATION) #if defined(ENABLE_SETUP_TUI) #include "setup/SetupApplication.h" #include "setup/SetupMainWnd.h" #include #endif // defined(ENABLE_SETUP_TUI) // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- // Voice LC MS Header, CC: 1, srcID: 1, dstID: TG9 const uint8_t VH_DMO1K[] = { 0x00U, 0x20U, 0x08U, 0x08U, 0x02U, 0x38U, 0x15U, 0x00U, 0x2CU, 0xA0U, 0x14U, 0x60U, 0x84U, 0x6DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x7EU, 0x30U, 0x30U, 0x01U, 0x10U, 0x01U, 0x40U, 0x03U, 0xC0U, 0x13U, 0xC1U, 0x1EU, 0x80U, 0x6FU }; // Voice Term MS with LC, CC: 1, srcID: 1, dstID: TG9 const uint8_t VT_DMO1K[] = { 0x00U, 0x4FU, 0x08U, 0xDCU, 0x02U, 0x88U, 0x15U, 0x78U, 0x2CU, 0xD0U, 0x14U, 0xC0U, 0x84U, 0xADU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x79U, 0x65U, 0x24U, 0x02U, 0x28U, 0x06U, 0x20U, 0x0FU, 0x80U, 0x1BU, 0xC1U, 0x07U, 0x80U, 0x5CU }; // Voice coding data + FEC, 1031 Hz Test Pattern const uint8_t VOICE_1K[] = { 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xA0U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x0CU, 0xC4U, 0x58U, 0x20U, 0x0AU, 0xCEU, 0xA8U, 0xFEU, 0x83U, 0xACU, 0xC4U, 0x58U, 0x20U, 0x0AU }; // Recommended 1011 Hz test pattern for P25 Phase 1 (ANSI/TIA-102.CAAA) // NAC: 0x293, srcID: 1, dstID: TG1 const uint8_t LDU1_1K[] = { 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x35, 0x54, 0x7B, 0xCB, 0x19, 0x4D, 0x0D, 0xCE, 0x24, 0xA1, 0x24, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4, 0xE2, 0x4A, 0x10, 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, 0x89, 0xD8, 0x39, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x70, 0xE2, 0x4A, 0x12, 0x40, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x6D, 0xE5, 0xD5, 0x48, 0xAD, 0xE3, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, 0xD8, 0x3B, 0xA1, 0x41, 0xC2, 0xD2, 0xBA, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x94, 0xC8, 0xFB, 0x02, 0x35, 0xA4, 0xE2, 0x4A, 0x12, 0x43, 0x50, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0xC0, 0x00, 0x00, 0x00, 0x0C, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xE4 }; const uint8_t LDU2_1K[] = { 0x55, 0x75, 0xF5, 0xFF, 0x77, 0xFF, 0x29, 0x3A, 0xB8, 0xA4, 0xEF, 0xB0, 0x9A, 0x8A, 0xCE, 0x24, 0xA1, 0x24, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB9, 0x18, 0x44, 0xFC, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC, 0xE2, 0x4A, 0x10, 0x90, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4C, 0xFC, 0x16, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x02, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x94, 0x89, 0xD8, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x24, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x18, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE2, 0x4A, 0x12, 0x40, 0xD4, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x16, 0x29, 0x62, 0x76, 0x0E, 0xE0, 0xE0, 0x00, 0x00, 0x00, 0x03, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x08, 0xF8, 0x6E, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x24, 0xD8, 0x39, 0xAE, 0x8B, 0x48, 0xB6, 0x49, 0x38, 0x90, 0xA1, 0x24, 0x35, 0x0C, 0xF0, 0x2F, 0x86, 0xE4, 0x60, 0x44, 0xFF, 0x05, 0x8A, 0x58, 0x9D, 0x83, 0xB9, 0xA8, 0xF4, 0xF1, 0xFD, 0x60, 0xE2, 0x4A, 0x12, 0x43, 0x50, 0x33, 0xC0, 0xBE, 0x1B, 0x91, 0x84, 0x4F, 0xF0, 0x58, 0x29, 0x62, 0x76, 0x0E, 0x40, 0x00, 0x00, 0x00, 0x0C, 0x89, 0x28, 0x49, 0x0D, 0x43, 0x3C, 0x0B, 0xE1, 0xB8, 0x46, 0x11, 0x3F, 0xC1, 0x62, 0x96, 0x27, 0x60, 0xEC }; // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the HostSetup class. /// /// Full-path to the configuration file. HostSetup::HostSetup(const std::string& confFile) : m_confFile(confFile), m_conf(), m_stopWatch(), m_modem(nullptr), m_queue(4096U, "Frame Buffer"), m_fec(), m_transmit(false), m_duplex(true), m_startupDuplex(true), m_dmrEnabled(false), m_dmrRx1K(false), m_p25Enabled(false), m_p25Rx1K(false), m_p25TduTest(false), m_nxdnEnabled(false), m_isHotspot(false), m_isConnected(false), m_debug(false), m_mode(STATE_DMR_CAL_1K), m_modeStr(DMR_CAL_1K_STR), m_rxFrequency(0U), m_rxAdjustedFreq(0U), m_txFrequency(0U), m_txAdjustedFreq(0U), m_channelId(0U), m_channelNo(0U), m_idenTable(nullptr), m_berFrames(0U), m_berBits(0U), m_berErrs(0U), m_berUndecodableLC(0U), m_berUncorrectable(0U), m_timeout(300U), m_timer(0U), m_updateConfigFromModem(false), m_hasFetchedStatus(false) { /* stub */ } /// /// Finalizes a instance of the HostSetup class. /// HostSetup::~HostSetup() { delete m_modem; } #if defined(ENABLE_SETUP_TUI) /// /// Executes the processing loop. /// /// /// /// Zero if successful, otherwise error occurred. int HostSetup::run(int argc, char** argv) { bool ret = false; try { ret = yaml::Parse(m_conf, m_confFile.c_str()); if (!ret) { ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); } } catch (yaml::OperationException const& e) { ::fatal("cannot read the configuration file - %s (%s)", m_confFile.c_str(), e.message()); } // initialize system logging ret = ::LogInitialise("", "", 0U, 1U); if (!ret) { ::fprintf(stderr, "unable to open the log file\n"); return 1; } ::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \ "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \ "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \ ">> Modem Setup\r\n"); // setup the finalcut tui SetupApplication app{this, argc, argv}; SetupMainWnd setupWnd{this, &app}; finalcut::FWidget::setMainWidget(&setupWnd); m_setupWnd = &setupWnd; yaml::Node logConf = m_conf["log"]; yaml::Node systemConf = m_conf["system"]; yaml::Node modemConfig = systemConf["modem"]; m_startupDuplex = m_duplex = systemConf["duplex"].as(true); // try to load bandplan identity table std::string idenLookupFile = systemConf["iden_table"]["file"].as(); uint32_t idenReloadTime = systemConf["iden_table"]["time"].as(0U); if (idenLookupFile.length() <= 0U) { ::LogError(LOG_HOST, "No bandplan identity table? This must be defined!"); return 1; } g_logDisplayLevel = 0U; LogInfo("Iden Table Lookups"); LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None"); if (idenReloadTime > 0U) LogInfo(" Reload: %u mins", idenReloadTime); m_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime); m_idenTable->read(); LogInfo("General Parameters"); std::string identity = systemConf["identity"].as(); ::LogInfo(" Identity: %s", identity.c_str()); yaml::Node rfssConfig = systemConf["config"]; m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); if (m_channelId > 15U) { // clamp to 15 m_channelId = 15U; } // if modem debug is being forced from the commandline -- enable modem debug if (g_modemDebug) m_debug = true; if (!calculateRxTxFreq(true)) { return 1; } // initialize modem if (!createModem(true)) { return 1; } p25::lc::TSBK::setVerbose(true); p25::lc::TSBK::setWarnCRC(true); p25::lc::tsbk::TSBKFactory::setWarnCRC(true); setupWnd.setMenuStates(); // show and start the application setupWnd.show(); finalcut::FApplication::setDarkTheme(); app.resetColors(); app.redraw(); return app.exec(); } #endif // defined(ENABLE_SETUP_TUI) // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /// /// /// /// bool HostSetup::portModemOpen(Modem* modem) { sleep(2000U); bool ret = writeRFParams(); if (!ret) { ret = writeRFParams(); if (!ret) { LogError(LOG_MODEM, "Modem unresponsive to RF parameters set after 2 attempts. Stopping."); m_modem->close(); m_isConnected = false; return false; } } ret = writeConfig(); if (!ret) { ret = writeConfig(); if (!ret) { LogError(LOG_MODEM, "Modem unresponsive to configuration set after 2 attempts. Stopping."); m_modem->close(); m_isConnected = false; return false; } } LogMessage(LOG_MODEM, "Modem Ready [Calibration Mode]"); // handled modem open return true; } /// /// /// /// bool HostSetup::portModemClose(Modem* modem) { // handled modem close return true; } /// /// /// /// /// /// /// /// /// /// bool HostSetup::portModemHandler(Modem* modem, uint32_t ms, RESP_TYPE_DVM rspType, bool rspDblLen, const uint8_t* buffer, uint16_t len) { switch (m_mode) { case STATE_P25: { if (m_transmit) { uint8_t dataLen = 0U; m_queue.peek(&dataLen, 1U); if (!m_queue.isEmpty() && m_modem->m_p25Space >= dataLen) { uint8_t data[p25::P25_LDU_FRAME_LENGTH_BYTES + 2U]; dataLen = 0U; m_queue.get(&dataLen, 1U); m_queue.get(data, dataLen); uint8_t buffer[250U]; buffer[0U] = DVM_FRAME_START; buffer[1U] = dataLen + 2U; buffer[2U] = CMD_P25_DATA; ::memcpy(buffer + 3U, data + 1U, dataLen - 1U); uint8_t len = dataLen + 2U; int ret = m_modem->write(buffer, len); if (ret != int(len)) LogError(LOG_MODEM, "Error writing P25 data"); m_modem->m_p25Space -= dataLen; } } } break; default: break; } if (rspType == RTM_OK && len > 0) { switch (buffer[2U]) { case CMD_CAL_DATA: { bool inverted = (buffer[3U] == 0x80U); short high = buffer[4U] << 8 | buffer[5U]; short low = buffer[6U] << 8 | buffer[7U]; short diff = high - low; short centre = (high + low) / 2; LogMessage(LOG_CAL, "Levels: inverted: %s, max: %d, min: %d, diff: %d, centre: %d", inverted ? "yes" : "no", high, low, diff, centre); } break; case CMD_RSSI_DATA: { uint16_t max = buffer[3U] << 8 | buffer[4U]; uint16_t min = buffer[5U] << 8 | buffer[6U]; uint16_t ave = buffer[7U] << 8 | buffer[8U]; LogMessage(LOG_CAL, "RSSI: max: %u, min: %u, ave: %u", max, min, ave); } break; case CMD_DMR_DATA1: case CMD_DMR_DATA2: processDMRBER(buffer + 4U, buffer[3]); break; case CMD_DMR_LOST1: case CMD_DMR_LOST2: { LogMessage(LOG_CAL, "DMR Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_dmrEnabled) { m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; } } break; case CMD_P25_DATA: processP25BER(buffer + 4U); break; case CMD_P25_LOST: { LogMessage(LOG_CAL, "P25 Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_p25Enabled) { m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; } } break; case CMD_NXDN_DATA: processNXDNBER(buffer + 4U); break; case CMD_NXDN_LOST: { LogMessage(LOG_CAL, "NXDN Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); if (m_nxdnEnabled) { m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; } } break; case CMD_GET_STATUS: { m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; m_modem->m_isHotspot = (buffer[3U] & 0x01U) == 0x01U; // override hotspot flag if we're forcing hotspot if (m_modem->m_forceHotspot) { m_isHotspot = m_modem->m_forceHotspot; m_modem->m_isHotspot = m_modem->m_forceHotspot; } uint8_t modemState = buffer[4U]; bool tx = (buffer[5U] & 0x01U) == 0x01U; bool adcOverflow = (buffer[5U] & 0x02U) == 0x02U; bool rxOverflow = (buffer[5U] & 0x04U) == 0x04U; bool txOverflow = (buffer[5U] & 0x08U) == 0x08U; bool dacOverflow = (buffer[5U] & 0x20U) == 0x20U; // spaces from the modem are returned in "logical" frame count, not raw byte size m_modem->m_dmrSpace1 = buffer[7U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); m_modem->m_dmrSpace2 = buffer[8U] * (dmr::DMR_FRAME_LENGTH_BYTES + 2U); m_modem->m_p25Space = buffer[10U] * (p25::P25_LDU_FRAME_LENGTH_BYTES); m_modem->m_nxdnSpace = buffer[11U] * (nxdn::NXDN_FRAME_LENGTH_BYTES); if (m_hasFetchedStatus && m_requestedStatus) { LogMessage(LOG_CAL, "Diagnostic Values [Modem State: %u, Transmitting: %d, ADC Overflow: %d, Rx Overflow: %d, Tx Overflow: %d, DAC Overflow: %d, HS: %u]", modemState, tx, adcOverflow, rxOverflow, txOverflow, dacOverflow, m_isHotspot); } m_hasFetchedStatus = true; m_requestedStatus = false; } break; case CMD_FLSH_READ: { uint8_t len = buffer[1U]; if (m_debug) { Utils::dump(1U, "Modem Flash Contents", buffer + 3U, len - 3U); } if (len == 249U) { bool ret = edac::CRC::checkCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); if (!ret) { LogWarning(LOG_CAL, "HostSetup::portModemHandler(), clearing modem configuration area; first setup?"); eraseFlash(); } else { bool isErased = (buffer[DVM_CONF_AREA_LEN] & 0x80U) == 0x80U; uint8_t confAreaVersion = buffer[DVM_CONF_AREA_LEN] & 0x7FU; if (!isErased) { if (confAreaVersion != DVM_CONF_AREA_VER) { LogError(LOG_MODEM, "HostSetup::portModemHandler(), invalid version for configuration area, %02X != %02X", DVM_CONF_AREA_VER, confAreaVersion); } else { processFlashConfig(buffer); // reset update config flag if its set if (m_updateConfigFromModem) { m_updateConfigFromModem = false; } } } else { LogWarning(LOG_MODEM, "HostSetup::portModemHandler(), modem configuration area was erased and does not contain active configuration!"); } } } else { LogWarning(LOG_MODEM, "Incorrect length for configuration area! Ignoring."); } } break; case CMD_GET_VERSION: case CMD_ACK: break; case CMD_NAK: LogWarning(LOG_CAL, "NAK, command = 0x%02X (%s), reason = %u (%s)", buffer[3U], m_modem->cmdToString(buffer[3U]).c_str(), buffer[4U], m_modem->rsnToString(buffer[4U]).c_str()); break; case CMD_DEBUG1: case CMD_DEBUG2: case CMD_DEBUG3: case CMD_DEBUG4: case CMD_DEBUG5: case CMD_DEBUG_DUMP: m_modem->printDebug(buffer, len); break; default: LogWarning(LOG_CAL, "Unknown message, type = %02X", buffer[2U]); Utils::dump("Buffer dump", buffer, len); break; } } // handled modem response return true; } /// /// Helper to save configuration. /// void HostSetup::saveConfig() { if (m_isConnected) { if (m_transmit) { setTransmit(); } writeConfig(); writeRFParams(); writeSymbolAdjust(); } yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); LogMessage(LOG_CAL, " - Saved configuration to %s", m_confFile.c_str()); if (m_isConnected) { if (writeFlash()) { LogMessage(LOG_CAL, " - Wrote configuration area on modem"); } } } /// /// Helper to calculate the Rx/Tx frequencies. /// /// /// bool HostSetup::calculateRxTxFreq(bool consoleDisplay) { IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { if (consoleDisplay) { g_logDisplayLevel = 1U; } ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); return false; } yaml::Node systemConf = m_conf["system"]; yaml::Node rfssConfig = systemConf["config"]; m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("1").c_str(), NULL, 16); if (m_channelNo == 0U) { // clamp to 1 m_channelNo = 1U; } if (m_channelNo > 4095U) { // clamp to 4095 m_channelNo = 4095U; } if (m_startupDuplex) { if (entry.txOffsetMhz() == 0U) { if (consoleDisplay) { g_logDisplayLevel = 1U; } ::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId); return false; } uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); float calcTxOffset = entry.txOffsetMhz() * 1000000; m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset); m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); } else { uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); m_txFrequency = m_rxFrequency; } if (m_isHotspot) { // apply the frequency tuning offsets m_rxAdjustedFreq = m_rxFrequency + m_modem->m_rxTuning; m_txAdjustedFreq = m_txFrequency + m_modem->m_txTuning; } return true; } /// /// Helper to log the system configuration parameters. /// void HostSetup::displayConfigParams() { IdenTable entry = m_idenTable->find(m_channelId); LogInfo("System Config Parameters"); LogInfo(" RX Frequency: %uHz", m_rxFrequency); LogInfo(" TX Frequency: %uHz", m_txFrequency); LogInfo(" Base Frequency: %uHz", entry.baseFrequency()); LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz()); } /// /// Initializes the modem DSP. /// /// /// bool HostSetup::createModem(bool consoleDisplay) { if (g_remoteModemMode) { if (consoleDisplay) { g_logDisplayLevel = 1U; } ::LogError(LOG_HOST, "Setup mode is unsupported with a remote modem!"); return false; } #if defined(ENABLE_SETUP_TUI) if (m_isConnected) { if (m_setupWnd != nullptr) { FMessageBox::error(m_setupWnd, "Cannot change modem configuration while connected!"); } return false; } #endif // defined(ENABLE_SETUP_TUI) displayConfigParams(); yaml::Node systemConf = m_conf["system"]; yaml::Node modemConf = systemConf["modem"]; bool rxInvert = modemConf["rxInvert"].as(false); bool txInvert = modemConf["txInvert"].as(false); bool pttInvert = modemConf["pttInvert"].as(false); bool dcBlocker = modemConf["dcBlocker"].as(true); int rxDCOffset = modemConf["rxDCOffset"].as(0); int txDCOffset = modemConf["txDCOffset"].as(0); float rxLevel = modemConf["rxLevel"].as(50.0F); float txLevel = modemConf["txLevel"].as(50.0F); yaml::Node hotspotParams = modemConf["hotspot"]; int dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as(0); int p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as(0); int nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as(0); int dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as(0); int p25PostBWAdj = hotspotParams["p25PostBWAdj"].as(0); int nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as(0); ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as(0U); bool afcEnable = hotspotParams["afcEnable"].as(false); uint8_t afcKI = (uint8_t)hotspotParams["afcKI"].as(11U); uint8_t afcKP = (uint8_t)hotspotParams["afcKP"].as(4U); uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as(1U); int rxTuning = hotspotParams["rxTuning"].as(0); int txTuning = hotspotParams["txTuning"].as(0); // apply the frequency tuning offsets m_rxAdjustedFreq = m_rxFrequency + rxTuning; m_txAdjustedFreq = m_txFrequency + txTuning; yaml::Node repeaterParams = modemConf["repeater"]; int dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as(0); int dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as(0); int p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as(0); int p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as(0); int nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as(0); int nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as(0); yaml::Node softpotParams = modemConf["softpot"]; uint8_t rxCoarsePot = (uint8_t)softpotParams["rxCoarse"].as(127U); uint8_t rxFinePot = (uint8_t)softpotParams["rxFine"].as(127U); uint8_t txCoarsePot = (uint8_t)softpotParams["txCoarse"].as(127U); uint8_t txFinePot = (uint8_t)softpotParams["txFine"].as(127U); uint8_t rssiCoarsePot = (uint8_t)softpotParams["rssiCoarse"].as(127U); uint8_t rssiFinePot = (uint8_t)softpotParams["rssiFine"].as(127U); uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as(DMR_TX_BUFFER_LEN); uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as(P25_TX_BUFFER_LEN); uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as(NXDN_TX_BUFFER_LEN); uint8_t fdmaPreamble = (uint8_t)modemConf["fdmaPreamble"].as(80U); uint8_t dmrRxDelay = (uint8_t)modemConf["dmrRxDelay"].as(7U); uint8_t p25CorrCount = (uint8_t)modemConf["p25CorrCount"].as(5U); bool ignoreModemConfigArea = modemConf["ignoreModemConfigArea"].as(false); yaml::Node modemProtocol = modemConf["protocol"]; std::string portType = modemProtocol["type"].as("null"); yaml::Node uartProtocol = modemProtocol["uart"]; std::string uartPort = uartProtocol["port"].as(); uint32_t uartSpeed = uartProtocol["speed"].as(115200); // delete existing modem instance if necessary if (m_modem != nullptr) { delete m_modem; } LogInfo("Modem Parameters"); LogInfo(" Port Type: %s", portType.c_str()); port::IModemPort* modemPort = nullptr; std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower); if (portType == NULL_PORT) { modemPort = new port::ModemNullPort(); } else if (portType == UART_PORT) { port::SERIAL_SPEED serialSpeed = port::SERIAL_115200; switch (uartSpeed) { case 1200: serialSpeed = port::SERIAL_1200; break; case 2400: serialSpeed = port::SERIAL_2400; break; case 4800: serialSpeed = port::SERIAL_4800; break; case 9600: serialSpeed = port::SERIAL_9600; break; case 19200: serialSpeed = port::SERIAL_19200; break; case 38400: serialSpeed = port::SERIAL_38400; break; case 76800: serialSpeed = port::SERIAL_76800; break; case 230400: serialSpeed = port::SERIAL_230400; break; case 460800: serialSpeed = port::SERIAL_460800; break; default: LogWarning(LOG_HOST, "Unsupported serial speed %u, defaulting to %u", uartSpeed, port::SERIAL_115200); uartSpeed = 115200; case 115200: break; } modemPort = new port::UARTPort(uartPort, serialSpeed, true); LogInfo(" UART Port: %s", uartPort.c_str()); LogInfo(" UART Speed: %u", uartSpeed); } LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no"); LogInfo(" TX Invert: %s", txInvert ? "yes" : "no"); LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no"); LogInfo(" DC Blocker: %s", dcBlocker ? "yes" : "no"); LogInfo(" FDMA Preambles: %u (%.1fms)", fdmaPreamble, float(fdmaPreamble) * 0.2083F); LogInfo(" DMR RX Delay: %u (%.1fms)", dmrRxDelay, float(dmrRxDelay) * 0.0416666F); LogInfo(" P25 Corr. Count: %u (%.1fms)", p25CorrCount, float(p25CorrCount) * 0.667F); LogInfo(" RX DC Offset: %d", rxDCOffset); LogInfo(" TX DC Offset: %d", txDCOffset); LogInfo(" RX Tuning Offset: %dhz", rxTuning); LogInfo(" TX Tuning Offset: %dhz", txTuning); LogInfo(" RX Effective Frequency: %uhz", m_rxAdjustedFreq); LogInfo(" TX Effective Frequency: %uhz", m_txAdjustedFreq); LogInfo(" RX Coarse: %u, Fine: %u", rxCoarsePot, rxFinePot); LogInfo(" TX Coarse: %u, Fine: %u", txCoarsePot, txFinePot); LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarsePot, rssiFinePot); LogInfo(" RX Level: %.1f%%", rxLevel); LogInfo(" TX Level: %.1f%%", txLevel); LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength); LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength); LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength); if (ignoreModemConfigArea) { LogInfo(" Ignore Modem Configuration Area: yes"); } if (modemPort == nullptr) { if (consoleDisplay) { g_logDisplayLevel = 1U; } ::LogError(LOG_HOST, "Invalid modem port type, %s!", portType.c_str()); return false; } m_modem = new Modem(modemPort, false, rxInvert, txInvert, pttInvert, dcBlocker, false, fdmaPreamble, dmrRxDelay, p25CorrCount, 3960U, 2592U, 1488U, false, ignoreModemConfigArea, false, false, false); m_modem->setLevels(rxLevel, txLevel, txLevel, txLevel, txLevel); m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj); m_modem->setDCOffsetParams(txDCOffset, rxDCOffset); m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, 100U, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj, p25PostBWAdj, nxdnPostBWAdj, adfGainMode, afcEnable, afcKI, afcKP, afcRange); m_modem->setSoftPot(rxCoarsePot, rxFinePot, txCoarsePot, txFinePot, rssiCoarsePot, rssiFinePot); m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(HostSetup::portModemOpen, this)); m_modem->setCloseHandler(MODEM_OC_PORT_HANDLER_BIND(HostSetup::portModemClose, this)); m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(HostSetup::portModemHandler, this)); m_modem->m_dmrFifoLength = dmrFifoLength; m_modem->m_p25FifoLength = p25FifoLength; m_modem->m_nxdnFifoLength = nxdnFifoLength; return true; } /// /// Helper to toggle modem transmit mode. /// /// True, if setting was applied, otherwise false. bool HostSetup::setTransmit() { if (m_dmrEnabled || (m_p25Enabled && !m_p25TduTest) || m_nxdnEnabled) { LogError(LOG_CAL, "No transmit allowed in a BER Test mode"); return false; } m_transmit = !m_transmit; if (m_p25Enabled && m_p25TduTest) { if (m_transmit) LogMessage(LOG_CAL, " - Modem start transmitting"); else LogMessage(LOG_CAL, " - Modem stop transmitting"); m_modem->clock(0U); return true; } uint8_t buffer[50U]; buffer[0U] = DVM_FRAME_START; buffer[1U] = 4U; buffer[2U] = CMD_CAL_DATA; buffer[3U] = m_transmit ? 0x01U : 0x00U; int ret = m_modem->write(buffer, 4U); if (ret <= 0) return false; sleep(25U); if (m_transmit) LogMessage(LOG_CAL, " - Modem start transmitting"); else LogMessage(LOG_CAL, " - Modem stop transmitting"); m_modem->clock(0U); return true; } /// /// Helper to update BER display window. /// /// Bit Error Rate Percentage void HostSetup::updateTUIBER(float ber) { #if defined(ENABLE_SETUP_TUI) if (m_setupWnd == nullptr) { return; } // handle displaying TUI std::stringstream ss; ss << std::fixed << std::setprecision(3) << ber; if (ber < 5.0F) { m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightGreen3); } else if (ber > 5.0F && ber < 8.0F) { m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightGoldenrod3); } else { m_setupWnd->m_berWnd.segmentColor(finalcut::FColor::LightRed); } m_setupWnd->m_berWnd.ber(ss.str()); #endif // defined(ENABLE_SETUP_TUI) } /// /// Process DMR Rx BER. /// /// Buffer containing DMR data /// DMR Audio Sequence void HostSetup::processDMRBER(const uint8_t* buffer, uint8_t seq) { if (seq == 65U) { timerStart(); LogMessage(LOG_CAL, "DMR voice header received"); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; return; } else if (seq == 66U) { if (m_berFrames != 0U) { LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); // handle displaying TUI float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); updateTUIBER(ber); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; return; } timerStart(); uint32_t errs = m_fec.measureDMRBER(buffer); float ber = float(errs) / 1.41F; if (ber < 10.0F) LogMessage(LOG_CAL, "DMR audio seq. %d, FEC BER %% (errs): %.3f%% (%u/141)", seq & 0x0FU, ber, errs); else { LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); m_berBits += 141U; m_berErrs += errs; m_berFrames++; } /// /// Process DMR Rx 1011hz BER. /// /// Buffer containing DMR data /// DMR Audio Sequence void HostSetup::processDMR1KBER(const uint8_t* buffer, uint8_t seq) { uint32_t errs = 0U; if (seq == 65U) { timerStart(); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; for (uint32_t i = 0U; i < 33U; i++) errs += countErrs(buffer[i], VH_DMO1K[i]); m_berErrs += errs; m_berBits += 264; m_berFrames++; LogMessage(LOG_CAL, "DMR voice header received, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", float(errs) / 2.64F, errs); } else if (seq == 66U) { for (uint32_t i = 0U; i < 33U; i++) errs += countErrs(buffer[i], VT_DMO1K[i]); m_berErrs += errs; m_berBits += 264; m_berFrames++; if (m_berFrames != 0U) { LogMessage(LOG_CAL, "DMR voice end received, total frames: %d, total bits: %d, uncorrectable frames: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); // handle displaying TUI float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); updateTUIBER(ber); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; return; } timerStart(); uint8_t dmrSeq = seq & 0x0FU; if (dmrSeq > 5U) dmrSeq = 5U; errs = 0U; for (uint32_t i = 0U; i < 33U; i++) errs += countErrs(buffer[i], VOICE_1K[i]); float ber = float(errs) / 2.64F; m_berErrs += errs; m_berBits += 264; m_berFrames++; if (ber < 10.0F) LogMessage(LOG_CAL, "DMR audio seq. %d, 1031 Test Pattern BER %% (errs): %.3f%% (%u/264)", seq & 0x0FU, ber, errs); else { LogWarning(LOG_CAL, "uncorrectable DMR audio seq. %d", seq & 0x0FU); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); } /// /// Process P25 Rx BER. /// /// Buffer containing P25 data void HostSetup::processP25BER(const uint8_t* buffer) { using namespace p25; uint8_t sync[P25_SYNC_LENGTH_BYTES]; ::memcpy(sync, buffer, P25_SYNC_LENGTH_BYTES); uint8_t syncErrs = 0U; for (uint8_t i = 0U; i < P25_SYNC_LENGTH_BYTES; i++) syncErrs += Utils::countBits8(sync[i] ^ P25_SYNC_BYTES[i]); LogMessage(LOG_CAL, "P25, sync word, errs = %u, sync word = %02X %02X %02X %02X %02X %02X", syncErrs, sync[0U], sync[1U], sync[2U], sync[3U], sync[4U], sync[5U]); uint8_t nid[P25_NID_LENGTH_BYTES]; P25Utils::decode(buffer, nid, 48U, 114U); uint8_t duid = nid[1U] & 0x0FU; uint32_t errs = 0U; uint8_t imbe[18U]; lc::LC lc = lc::LC(); data::DataHeader dataHeader = data::DataHeader(); if (duid == P25_DUID_HDU) { timerStart(); bool ret = lc.decodeHDU(buffer); if (!ret) { LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); } m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; } else if (duid == P25_DUID_TDU) { if (m_berFrames != 0U) { LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); // handle displaying TUI float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); updateTUIBER(ber); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; return; } else if (duid == P25_DUID_LDU1) { timerStart(); bool ret = lc.decodeLDU1(buffer); if (!ret) { LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); } P25Utils::decode(buffer, imbe, 114U, 262U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 262U, 410U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 452U, 600U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 640U, 788U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 830U, 978U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1020U, 1168U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1208U, 1356U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1398U, 1546U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1578U, 1726U); errs += m_fec.measureP25BER(imbe); float ber = float(errs) / 12.33F; if (ber < 10.0F) LogMessage(LOG_CAL, P25_LDU1_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); m_berBits += 1233U; m_berErrs += errs; m_berFrames++; } else if (duid == P25_DUID_LDU2) { timerStart(); bool ret = lc.decodeLDU2(buffer); if (!ret) { LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", lc.getMFId(), lc.getAlgId(), lc.getKId()); } P25Utils::decode(buffer, imbe, 114U, 262U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 262U, 410U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 452U, 600U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 640U, 788U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 830U, 978U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1020U, 1168U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1208U, 1356U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1398U, 1546U); errs += m_fec.measureP25BER(imbe); P25Utils::decode(buffer, imbe, 1578U, 1726U); errs += m_fec.measureP25BER(imbe); float ber = float(errs) / 12.33F; if (ber < 10.0F) LogMessage(LOG_CAL, P25_LDU2_STR ", audio FEC BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); m_berBits += 1233U; m_berErrs += errs; m_berFrames++; } else if (duid == P25_DUID_PDU) { timerStop(); // note: for the calibrator we will only process the PDU header -- and not the PDU data uint8_t pduBuffer[P25_LDU_FRAME_LENGTH_BYTES]; uint32_t bits = P25Utils::decode(buffer, pduBuffer, 0, P25_LDU_FRAME_LENGTH_BITS); uint8_t* rfPDU = new uint8_t[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U]; ::memset(rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U); uint32_t rfPDUBits = 0U; for (uint32_t i = 0U; i < bits; i++, rfPDUBits++) { bool b = READ_BIT(buffer, i); WRITE_BIT(rfPDU, rfPDUBits, b); } bool ret = dataHeader.decode(rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES); if (!ret) { LogWarning(LOG_RF, P25_PDU_STR ", unfixable RF 1/2 rate header data"); Utils::dump(1U, "Unfixable PDU Data", rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES, P25_PDU_HEADER_LENGTH_BYTES); } else { LogMessage(LOG_CAL, P25_PDU_STR ", ack = %u, outbound = %u, fmt = $%02X, mfId = $%02X, sap = $%02X, fullMessage = %u, blocksToFollow = %u, padCount = %u, n = %u, seqNo = %u, lastFragment = %u, hdrOffset = %u", dataHeader.getAckNeeded(), dataHeader.getOutbound(), dataHeader.getFormat(), dataHeader.getMFId(), dataHeader.getSAP(), dataHeader.getFullMessage(), dataHeader.getBlocksToFollow(), dataHeader.getPadCount(), dataHeader.getNs(), dataHeader.getFSN(), dataHeader.getLastFragment(), dataHeader.getHeaderOffset()); } delete[] rfPDU; } else if (duid == P25_DUID_TSDU) { timerStop(); std::unique_ptr tsbk = lc::tsbk::TSBKFactory::createTSBK(buffer); Utils::dump(1U, "Raw TSBK Dump", buffer, P25_TSDU_FRAME_LENGTH_BYTES); if (tsbk == nullptr) { LogWarning(LOG_CAL, P25_TSDU_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_TSDU_STR ", mfId = $%02X, lco = $%02X, srcId = %u, dstId = %u, service = %u, netId = %u, sysId = %u", tsbk->getMFId(), tsbk->getLCO(), tsbk->getSrcId(), tsbk->getDstId(), tsbk->getService(), tsbk->getNetId(), tsbk->getSysId()); } } } /// /// Process P25 Rx 1011hz BER. /// /// Buffer containing P25 data void HostSetup::processP251KBER(const uint8_t* buffer) { using namespace p25; uint8_t nid[P25_NID_LENGTH_BYTES]; P25Utils::decode(buffer, nid, 48U, 114U); uint8_t duid = nid[1U] & 0x0FU; uint32_t errs = 0U; lc::LC lc = lc::LC(); if (duid == P25_DUID_HDU) { timerStart(); bool ret = lc.decodeHDU(buffer); if (!ret) { LogWarning(LOG_CAL, P25_HDU_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_RF, P25_HDU_STR ", dstId = %u, algo = %X, kid = %X", lc.getDstId(), lc.getAlgId(), lc.getKId()); } m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; } else if (duid == P25_DUID_TDU) { if (m_berFrames != 0U) { LogMessage(LOG_CAL, P25_TDU_STR ", total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); } timerStop(); // handle displaying TUI float ber = (float)(float(m_berErrs * 100U) / float(m_berBits)); updateTUIBER(ber); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; return; } else if (duid == P25_DUID_LDU1) { timerStart(); bool ret = lc.decodeLDU1(buffer); if (!ret) { LogWarning(LOG_CAL, P25_LDU1_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_LDU1_STR " LC, mfId = $%02X, lco = $%02X, emerg = %u, encrypt = %u, prio = %u, group = %u, srcId = %u, dstId = %u", lc.getMFId(), lc.getLCO(), lc.getEmergency(), lc.getEncrypted(), lc.getPriority(), lc.getGroup(), lc.getSrcId(), lc.getDstId()); } for (uint32_t i = 0U; i < 216U; i++) errs += countErrs(buffer[i], LDU1_1K[i]); float ber = float(errs) / 12.33F; if (ber < 10.0F) LogMessage(LOG_CAL, P25_LDU1_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU1_STR ", uncorrectable audio"); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); m_berBits += 1233U; m_berErrs += errs; m_berFrames++; } else if (duid == P25_DUID_LDU2) { timerStart(); bool ret = lc.decodeLDU2(buffer); if (!ret) { LogWarning(LOG_CAL, P25_LDU2_STR ", undecodable LC"); m_berUndecodableLC++; } else { LogMessage(LOG_CAL, P25_LDU2_STR " LC, mfId = $%02X, algo = %X, kid = %X", lc.getMFId(), lc.getAlgId(), lc.getKId()); } for (uint32_t i = 0U; i < 216U; i++) errs += countErrs(buffer[i], LDU2_1K[i]); float ber = float(errs) / 12.33F; if (ber < 10.0F) LogMessage(LOG_CAL, P25_LDU2_STR ", 1011 Test Pattern BER (errs): %.3f%% (%u/1233)", ber, errs); else { LogWarning(LOG_CAL, P25_LDU2_STR ", uncorrectable audio"); m_berUncorrectable++; } // handle displaying TUI updateTUIBER(ber); m_berBits += 1233U; m_berErrs += errs; m_berFrames++; } } /// /// Process NXDN Rx BER. /// /// Buffer containing NXDN data void HostSetup::processNXDNBER(const uint8_t* buffer) { using namespace nxdn; uint8_t data[NXDN_FRAME_LENGTH_BYTES]; ::memcpy(data, buffer, NXDN_FRAME_LENGTH_BYTES); NXDNUtils::scrambler(data); channel::LICH lich; bool valid = lich.decode(data); if (valid) { uint8_t usc = lich.getFCT(); uint8_t opt = lich.getOption(); if (usc == NXDN_LICH_USC_SACCH_NS) { if (m_berFrames == 0U) { LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), BER Start"); timerStart(); m_berErrs = 0U; m_berBits = 0U; m_berFrames = 0U; return; } else { float ber = float(m_berErrs * 100U) / float(m_berBits); LogMessage(LOG_CAL, "NXDN TX_REL (Transmission Release), BER Test, frames: %u, errs: %.3f%% (%u/%u)", m_berFrames, ber, m_berErrs, m_berBits); // handle displaying TUI updateTUIBER(ber); timerStop(); m_berErrs = 0U; m_berBits = 0U; m_berFrames = 0U; return; } } else if (opt == NXDN_LICH_STEAL_NONE) { timerStart(); uint32_t errors = 0U; errors += m_fec.measureNXDNBER(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); errors += m_fec.measureNXDNBER(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); errors += m_fec.measureNXDNBER(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); errors += m_fec.measureNXDNBER(data + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); m_berBits += 188U; m_berErrs += errors; m_berFrames++; float ber = float(errors) / 1.88F; LogMessage(LOG_CAL, "NXDN VCALL (Voice Call), BER Test, (errs): %.3f%% (%u/188)", ber, errors); // handle displaying TUI updateTUIBER(ber); } } } /// /// Write configuration to the modem DSP. /// /// True, if configuration is written, otherwise false. bool HostSetup::writeConfig() { return writeConfig(m_mode); } /// /// Write configuration to the modem DSP. /// /// /// True, if configuration is written, otherwise false. bool HostSetup::writeConfig(uint8_t modeOverride) { if (m_isHotspot && m_transmit) { setTransmit(); sleep(25U); } uint8_t buffer[25U]; ::memset(buffer, 0x00U, 25U); uint8_t lengthToWrite = 17U; buffer[0U] = DVM_FRAME_START; buffer[2U] = CMD_SET_CONFIG; buffer[3U] = 0x00U; m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); if (m_modem->m_rxInvert) buffer[3U] |= 0x01U; m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); if (m_modem->m_txInvert) buffer[3U] |= 0x02U; m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); if (m_modem->m_pttInvert) buffer[3U] |= 0x04U; if (m_debug) buffer[3U] |= 0x10U; buffer[3U] |= 0x40U; // force DMO for calibration if (!m_duplex) buffer[3U] |= 0x80U; buffer[4U] = 0x00U; m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); if (m_modem->m_dcBlocker) buffer[4U] |= 0x01U; if (m_dmrEnabled) buffer[4U] |= 0x02U; if (m_p25Enabled) buffer[4U] |= 0x08U; if (m_modem->m_fdmaPreamble > MAX_FDMA_PREAMBLE) { LogWarning(LOG_P25, "oversized FDMA preamble count, reducing to maximum %u", MAX_FDMA_PREAMBLE); m_modem->m_fdmaPreamble = MAX_FDMA_PREAMBLE; } m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); buffer[5U] = m_modem->m_fdmaPreamble; buffer[6U] = modeOverride; m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); buffer[9U] = 1U; m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); buffer[10U] = m_modem->m_dmrRxDelay; uint32_t nac = 0xF7EU; buffer[11U] = (nac >> 4) & 0xFFU; buffer[12U] = (nac << 4) & 0xF0U; buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); buffer[14U] = (uint8_t)m_modem->m_p25CorrCount; // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { lengthToWrite = 24U; if (m_nxdnEnabled) buffer[4U] |= 0x10U; buffer[18U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); m_conf["system"]["modem"]["softpot"]["rxCoarse"] = __INT_STR(m_modem->m_rxCoarsePot); buffer[19U] = m_modem->m_rxCoarsePot; m_conf["system"]["modem"]["softpot"]["rxFine"] = __INT_STR(m_modem->m_rxFinePot); buffer[20U] = m_modem->m_rxFinePot; m_conf["system"]["modem"]["softpot"]["txCoarse"] = __INT_STR(m_modem->m_txCoarsePot); buffer[21U] = m_modem->m_txCoarsePot; m_conf["system"]["modem"]["softpot"]["txFine"] = __INT_STR(m_modem->m_txFinePot); buffer[22U] = m_modem->m_txFinePot; m_conf["system"]["modem"]["softpot"]["rssiCoarse"] = __INT_STR(m_modem->m_rssiCoarsePot); buffer[23U] = m_modem->m_rssiCoarsePot; m_conf["system"]["modem"]["softpot"]["rssiFine"] = __INT_STR(m_modem->m_rssiFinePot); buffer[24U] = m_modem->m_rssiFinePot; } buffer[1U] = lengthToWrite; int ret = m_modem->write(buffer, lengthToWrite); if (ret != lengthToWrite) return false; sleep(10U); m_modem->clock(0U); return true; } /// /// Write RF parameters to the air interface modem. /// /// bool HostSetup::writeRFParams() { uint8_t buffer[22U]; ::memset(buffer, 0x00U, 22U); uint8_t lengthToWrite = 18U; buffer[0U] = DVM_FRAME_START; buffer[2U] = CMD_SET_RFPARAMS; buffer[3U] = 0x00U; buffer[4U] = (m_rxAdjustedFreq >> 0) & 0xFFU; buffer[5U] = (m_rxAdjustedFreq >> 8) & 0xFFU; buffer[6U] = (m_rxAdjustedFreq >> 16) & 0xFFU; buffer[7U] = (m_rxAdjustedFreq >> 24) & 0xFFU; buffer[8U] = (m_txAdjustedFreq >> 0) & 0xFFU; buffer[9U] = (m_txAdjustedFreq >> 8) & 0xFFU; buffer[10U] = (m_txAdjustedFreq >> 16) & 0xFFU; buffer[11U] = (m_txAdjustedFreq >> 24) & 0xFFU; buffer[12U] = (unsigned char)(100 * 2.55F + 0.5F); // cal sets power fixed to 100 m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); buffer[13U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); buffer[14U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); buffer[15U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); buffer[16U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); buffer[17U] = (uint8_t)m_modem->m_adfGainMode; // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { lengthToWrite = 22U; m_conf["system"]["modem"]["hotspot"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); buffer[18U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); m_conf["system"]["modem"]["hotspot"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); buffer[19U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); // support optional AFC parameters m_conf["system"]["modem"]["hotspot"]["afcEnable"] = __BOOL_STR(m_modem->m_afcEnable); m_conf["system"]["modem"]["hotspot"]["afcKI"] = __INT_STR(m_modem->m_afcKI); m_conf["system"]["modem"]["hotspot"]["afcKP"] = __INT_STR(m_modem->m_afcKP); buffer[20U] = (m_modem->m_afcEnable ? 0x80 : 0x00) + (m_modem->m_afcKP << 4) + (m_modem->m_afcKI); m_conf["system"]["modem"]["hotspot"]["afcRange"] = __INT_STR(m_modem->m_afcRange); buffer[21U] = m_modem->m_afcRange; } buffer[1U] = lengthToWrite; int ret = m_modem->write(buffer, lengthToWrite); if (ret <= 0) return false; sleep(10U); m_modem->clock(0U); return true; } /// /// Write symbol level adjustments to the modem DSP. /// /// True, if level adjustments are written, otherwise false. bool HostSetup::writeSymbolAdjust() { uint8_t buffer[20U]; ::memset(buffer, 0x00U, 20U); uint8_t lengthToWrite = 7U; buffer[0U] = DVM_FRAME_START; buffer[2U] = CMD_SET_SYMLVLADJ; m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); buffer[3U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); buffer[4U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); buffer[5U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); buffer[6U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { lengthToWrite = 9U; m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); buffer[7U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); buffer[8U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); } buffer[1U] = lengthToWrite; int ret = m_modem->write(buffer, lengthToWrite); if (ret <= 0) return false; sleep(10U); m_modem->clock(0U); return true; } /// /// Write transmit FIFO buffer lengths. /// /// True, if level adjustments are written, otherwise false. bool HostSetup::writeFifoLength() { uint8_t buffer[9U]; ::memset(buffer, 0x00U, 9U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 9U; buffer[2U] = CMD_SET_BUFFERS; m_conf["system"]["modem"]["dmrFifoLength"] = __INT_STR(m_modem->m_dmrFifoLength); buffer[3U] = (uint8_t)(m_modem->m_dmrFifoLength >> 8) & 0xFFU; buffer[4U] = (uint8_t)(m_modem->m_dmrFifoLength & 0xFFU); m_conf["system"]["modem"]["p25FifoLength"] = __INT_STR(m_modem->m_p25FifoLength); buffer[5U] = (uint8_t)(m_modem->m_p25FifoLength >> 8) & 0xFFU; buffer[6U] = (uint8_t)(m_modem->m_p25FifoLength & 0xFFU); m_conf["system"]["modem"]["nxdnFifoLenth"] = __INT_STR(m_modem->m_nxdnFifoLength); buffer[7U] = (uint8_t)(m_modem->m_nxdnFifoLength >> 8) & 0xFFU; buffer[8U] = (uint8_t)(m_modem->m_nxdnFifoLength & 0xFFU); int ret = m_modem->write(buffer, 9U); if (ret <= 0) return false; sleep(10U); m_modem->clock(0U); return true; } /// /// Helper to sleep the calibration thread. /// /// Milliseconds to sleep. void HostSetup::sleep(uint32_t ms) { ::usleep(ms * 1000); } /// /// Read the configuration area on the air interface modem. /// bool HostSetup::readFlash() { if (m_modem->m_flashDisabled) { return false; } uint8_t buffer[3U]; ::memset(buffer, 0x00U, 3U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 3U; buffer[2U] = CMD_FLSH_READ; int ret = m_modem->write(buffer, 3U); if (ret <= 0) return false; sleep(1000U); m_modem->clock(0U); return true; } /// /// Process the configuration data from the air interface modem. /// /// void HostSetup::processFlashConfig(const uint8_t *buffer) { if (m_updateConfigFromModem) { LogMessage(LOG_CAL, " - Restoring local configuration from configuration area on modem"); // general config m_modem->m_rxInvert = (buffer[3U] & 0x01U) == 0x01U; m_conf["system"]["modem"]["rxInvert"] = __BOOL_STR(m_modem->m_rxInvert); m_modem->m_txInvert = (buffer[3U] & 0x02U) == 0x02U; m_conf["system"]["modem"]["txInvert"] = __BOOL_STR(m_modem->m_txInvert); m_modem->m_pttInvert = (buffer[3U] & 0x04U) == 0x04U; m_conf["system"]["modem"]["pttInvert"] = __BOOL_STR(m_modem->m_pttInvert); m_modem->m_dcBlocker = (buffer[4U] & 0x01U) == 0x01U; m_conf["system"]["modem"]["dcBlocker"] = __BOOL_STR(m_modem->m_dcBlocker); m_modem->m_fdmaPreamble = buffer[5U]; m_conf["system"]["modem"]["fdmaPreamble"] = __INT_STR(m_modem->m_fdmaPreamble); // levels m_modem->m_rxLevel = (float(buffer[7U]) - 0.5F) / 2.55F; m_conf["system"]["modem"]["rxLevel"] = __FLOAT_STR(m_modem->m_rxLevel); m_modem->m_cwIdTXLevel = (float(buffer[8U]) - 0.5F) / 2.55F; m_conf["system"]["modem"]["txLevel"] = __FLOAT_STR(m_modem->m_cwIdTXLevel); m_modem->m_dmrRxDelay = buffer[10U]; m_conf["system"]["modem"]["dmrRxDelay"] = __INT_STR(m_modem->m_dmrRxDelay); m_modem->m_p25CorrCount = buffer[11U]; m_conf["system"]["modem"]["p25CorrCount"] = __INT_STR(m_modem->m_p25CorrCount); m_modem->m_txDCOffset = int(buffer[16U]) - 128; m_conf["system"]["modem"]["txDCOffset"] = __INT_STR(m_modem->m_txDCOffset); m_modem->m_rxDCOffset = int(buffer[17U]) - 128; m_conf["system"]["modem"]["rxDCOffset"] = __INT_STR(m_modem->m_rxDCOffset); writeConfig(); sleep(500); // symbol adjust m_modem->m_dmrSymLevel3Adj = int(buffer[35U]) - 128; m_conf["system"]["modem"]["repeater"]["dmrSymLvl3Adj"] = __INT_STR(m_modem->m_dmrSymLevel3Adj); m_modem->m_dmrSymLevel1Adj = int(buffer[36U]) - 128; m_conf["system"]["modem"]["repeater"]["dmrSymLvl1Adj"] = __INT_STR(m_modem->m_dmrSymLevel1Adj); m_modem->m_p25SymLevel3Adj = int(buffer[37U]) - 128; m_conf["system"]["modem"]["repeater"]["p25SymLvl3Adj"] = __INT_STR(m_modem->m_p25SymLevel3Adj); m_modem->m_p25SymLevel1Adj = int(buffer[38U]) - 128; m_conf["system"]["modem"]["repeater"]["p25SymLvl1Adj"] = __INT_STR(m_modem->m_p25SymLevel1Adj); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { m_modem->m_nxdnSymLevel3Adj = int(buffer[41U]) - 128; m_conf["system"]["modem"]["repeater"]["nxdnSymLvl3Adj"] = __INT_STR(m_modem->m_nxdnSymLevel3Adj); m_modem->m_nxdnSymLevel1Adj = int(buffer[42U]) - 128; m_conf["system"]["modem"]["repeater"]["nxdnSymLvl1Adj"] = __INT_STR(m_modem->m_nxdnSymLevel1Adj); } writeSymbolAdjust(); sleep(500); // RF parameters m_modem->m_dmrDiscBWAdj = int8_t(buffer[20U]) - 128; m_conf["system"]["modem"]["hotspot"]["dmrDiscBWAdj"] = __INT_STR(m_modem->m_dmrDiscBWAdj); m_modem->m_p25DiscBWAdj = int8_t(buffer[21U]) - 128; m_conf["system"]["modem"]["hotspot"]["p25DiscBWAdj"] = __INT_STR(m_modem->m_p25DiscBWAdj); m_modem->m_dmrPostBWAdj = int8_t(buffer[22U]) - 128; m_conf["system"]["modem"]["hotspot"]["dmrPostBWAdj"] = __INT_STR(m_modem->m_dmrPostBWAdj); m_modem->m_p25PostBWAdj = int8_t(buffer[23U]) - 128; m_conf["system"]["modem"]["hotspot"]["p25PostBWAdj"] = __INT_STR(m_modem->m_p25PostBWAdj); m_modem->m_adfGainMode = (ADF_GAIN_MODE)buffer[24U]; m_conf["system"]["modem"]["hotspot"]["adfGainMode"] = __INT_STR((int)m_modem->m_adfGainMode); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { m_modem->m_nxdnDiscBWAdj = int8_t(buffer[39U]) - 128; m_conf["system"]["modem"]["repeater"]["nxdnDiscBWAdj"] = __INT_STR(m_modem->m_nxdnDiscBWAdj); m_modem->m_nxdnPostBWAdj = int8_t(buffer[40U]) - 128; m_conf["system"]["modem"]["repeater"]["nxdnPostBWAdj"] = __INT_STR(m_modem->m_nxdnPostBWAdj); } m_modem->m_txTuning = int(buffer[25U]) - 128; m_conf["system"]["modem"]["hotspot"]["txTuning"] = __INT_STR(m_modem->m_txTuning); m_txAdjustedFreq = m_txFrequency + m_modem->m_txTuning; m_modem->m_rxTuning = int(buffer[26U]) - 128; m_conf["system"]["modem"]["hotspot"]["rxTuning"] = __INT_STR(m_modem->m_rxTuning); m_rxAdjustedFreq = m_rxFrequency + m_modem->m_rxTuning; // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { m_modem->m_rxCoarsePot = buffer[43U]; m_conf["system"]["modem"]["softpot"]["rxCoarse"] = __INT_STR(m_modem->m_rxCoarsePot); m_modem->m_rxFinePot = buffer[44U]; m_conf["system"]["modem"]["softpot"]["rxFine"] = __INT_STR(m_modem->m_rxFinePot); m_modem->m_txCoarsePot = buffer[45U]; m_conf["system"]["modem"]["softpot"]["txCoarse"] = __INT_STR(m_modem->m_txCoarsePot); m_modem->m_txFinePot = buffer[46U]; m_conf["system"]["modem"]["softpot"]["txFine"] = __INT_STR(m_modem->m_txFinePot); m_modem->m_rssiCoarsePot = buffer[47U]; m_conf["system"]["modem"]["softpot"]["rssiCoarse"] = __INT_STR(m_modem->m_rssiCoarsePot); m_modem->m_rssiFinePot = buffer[48U]; m_conf["system"]["modem"]["softpot"]["rssiFine"] = __INT_STR(m_modem->m_rssiFinePot); } writeRFParams(); sleep(500); } } /// /// Erase the configuration area on the air interface modem. /// bool HostSetup::eraseFlash() { if (m_modem->m_flashDisabled) { return false; } uint8_t buffer[249U]; ::memset(buffer, 0x00U, 249U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 249U; buffer[2U] = CMD_FLSH_WRITE; // configuration version buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER & 0x7FU; buffer[DVM_CONF_AREA_LEN] |= 0x80U; // flag erased edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); int ret = m_modem->write(buffer, 249U); if (ret <= 0) return false; sleep(1000U); m_updateConfigFromModem = false; LogMessage(LOG_CAL, " - Erased configuration area on modem"); m_modem->clock(0U); return true; } /// /// Write the configuration area on the air interface modem. /// bool HostSetup::writeFlash() { if (m_modem->m_flashDisabled) { return false; } uint8_t buffer[249U]; ::memset(buffer, 0x00U, 249U); buffer[0U] = DVM_FRAME_START; buffer[1U] = 249U; buffer[2U] = CMD_FLSH_WRITE; // general config buffer[3U] = 0x00U; if (m_modem->m_rxInvert) buffer[3U] |= 0x01U; if (m_modem->m_txInvert) buffer[3U] |= 0x02U; if (m_modem->m_pttInvert) buffer[3U] |= 0x04U; buffer[4U] = 0x00U; if (m_modem->m_dcBlocker) buffer[4U] |= 0x01U; buffer[5U] = m_modem->m_fdmaPreamble; buffer[7U] = (uint8_t)(m_modem->m_rxLevel * 2.55F + 0.5F); buffer[8U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); buffer[10U] = m_modem->m_dmrRxDelay; buffer[11U] = (uint8_t)m_modem->m_p25CorrCount; buffer[13U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); buffer[15U] = (uint8_t)(m_modem->m_cwIdTXLevel * 2.55F + 0.5F); buffer[16U] = (uint8_t)(m_modem->m_txDCOffset + 128); buffer[17U] = (uint8_t)(m_modem->m_rxDCOffset + 128); // RF parameters buffer[20U] = (uint8_t)(m_modem->m_dmrDiscBWAdj + 128); buffer[21U] = (uint8_t)(m_modem->m_p25DiscBWAdj + 128); buffer[22U] = (uint8_t)(m_modem->m_dmrPostBWAdj + 128); buffer[23U] = (uint8_t)(m_modem->m_p25PostBWAdj + 128); buffer[24U] = (uint8_t)m_modem->m_adfGainMode; uint32_t txTuning = (uint32_t)m_modem->m_txTuning; __SET_UINT32(txTuning, buffer, 25U); uint32_t rxTuning = (uint32_t)m_modem->m_rxTuning; __SET_UINT32(rxTuning, buffer, 29U); // symbol adjust buffer[35U] = (uint8_t)(m_modem->m_dmrSymLevel3Adj + 128); buffer[36U] = (uint8_t)(m_modem->m_dmrSymLevel1Adj + 128); buffer[37U] = (uint8_t)(m_modem->m_p25SymLevel3Adj + 128); buffer[38U] = (uint8_t)(m_modem->m_p25SymLevel1Adj + 128); // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { buffer[39U] = (uint8_t)(m_modem->m_nxdnDiscBWAdj + 128); buffer[40U] = (uint8_t)(m_modem->m_nxdnPostBWAdj + 128); buffer[41U] = (uint8_t)(m_modem->m_nxdnSymLevel3Adj + 128); buffer[42U] = (uint8_t)(m_modem->m_nxdnSymLevel1Adj + 128); } // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { buffer[43U] = m_modem->m_rxCoarsePot; buffer[44U] = m_modem->m_rxFinePot; buffer[45U] = m_modem->m_txCoarsePot; buffer[46U] = m_modem->m_txFinePot; buffer[47U] = m_modem->m_rssiCoarsePot; buffer[48U] = m_modem->m_rssiFinePot; } // software signature std::string software; software.append(__NETVER__ " (built " __BUILD__ ")"); for (uint8_t i = 0; i < software.length(); i++) { buffer[176U + i] = software[i]; } // configuration version buffer[DVM_CONF_AREA_LEN] = DVM_CONF_AREA_VER; edac::CRC::addCCITT162(buffer + 3U, DVM_CONF_AREA_LEN); #if DEBUG_MODEM_CAL Utils::dump(1U, "HostSetup::writeFlash(), Written", buffer, 249U); #endif int ret = m_modem->write(buffer, 249U); if (ret <= 0) return false; sleep(1000U); m_updateConfigFromModem = false; m_modem->clock(0U); return true; } /// /// Helper to clock the calibration BER timer. /// void HostSetup::timerClock() { if (m_timer > 0U && m_timeout > 0U) { m_timer += 1U; if (m_timer >= m_timeout) { LogMessage(LOG_CAL, "Transmission lost, total frames: %d, bits: %d, uncorrectable frames: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_berFrames, m_berBits, m_berUncorrectable, m_berUndecodableLC, m_berErrs, float(m_berErrs * 100U) / float(m_berBits)); m_berBits = 0U; m_berErrs = 0U; m_berFrames = 0U; m_berUndecodableLC = 0U; m_berUncorrectable = 0U; timerStop(); } } } /// /// Helper to start the calibration BER timer. /// void HostSetup::timerStart() { if (m_timeout > 0U) m_timer = 1U; } /// /// Helper to stop the calibration BER timer. /// void HostSetup::timerStop() { m_timer = 0U; } /// /// Retrieve the current status from the air interface modem. /// void HostSetup::getStatus() { uint8_t buffer[50U]; buffer[0U] = DVM_FRAME_START; buffer[1U] = 4U; buffer[2U] = CMD_GET_STATUS; int ret = m_modem->write(buffer, 4U); if (ret <= 0) return; sleep(25U); m_requestedStatus = true; m_modem->clock(0U); } #if defined(ENABLE_SETUP_TUI) /// /// Prints the current status of the calibration. /// void HostSetup::printStatus() { if (m_setupWnd->m_statusWnd.isShown()) { m_setupWnd->m_statusWnd.clear(); } yaml::Node systemConf = m_conf["system"]; { std::stringstream status; yaml::Node modemConfig = m_conf["system"]["modem"]; std::string type = modemConfig["protocol"]["type"].as(); yaml::Node uartConfig = modemConfig["protocol"]["uart"]; std::string modemPort = uartConfig["port"].as(); uint32_t portSpeed = uartConfig["speed"].as(115200U); status << "Operating Mode: " << m_modeStr << std::endl; status << "Port Type: " << type << ", Port Speed: " << portSpeed << ", Proto Ver: " << (uint32_t)(m_modem->getVersion()) << std::endl; status << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } { std::stringstream status; std::string identity = systemConf["identity"].as(); uint32_t timeout = systemConf["timeout"].as(); bool duplex = systemConf["duplex"].as(true); bool simplexSameFrequency = systemConf["simplexSameFrequency"].as(false); uint32_t modeHang = systemConf["modeHang"].as(); uint32_t rfTalkgroupHang = systemConf["rfTalkgroupHang"].as(); bool fixedMode = systemConf["fixedMode"].as(false); bool dmrEnabled = m_conf["protocols"]["dmr"]["enable"].as(true); bool p25Enabled = m_conf["protocols"]["p25"]["enable"].as(true); bool nxdnEnabled = m_conf["protocols"]["nxdn"]["enable"].as(true); IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); } calculateRxTxFreq(); status << "Identity: " << identity << ", Duplex: " << ((duplex) ? "yes" : "no") << ", Simplex Frequency: " << ((simplexSameFrequency) ? "yes" : "no") << ", Timeout: " << timeout << std::endl; status << "Mode Hang: " << modeHang << ", RF TG Hang: " << rfTalkgroupHang << ", Fixed Mode: " << ((fixedMode) ? "yes" : "no") << std::endl; status << "DMR Enabled: " << ((dmrEnabled) ? "yes" : "no") << ", P25 Enabled: " << ((p25Enabled) ? "yes" : "no") << ", NXDN Enabled: " << ((nxdnEnabled) ? "yes" : "no") << std::endl; status << "Channel Id: " << (uint32_t)(m_channelId) << ", Channel No: $" << std::hex << m_channelNo << std::dec << std::endl; status << "Base Freq: " << entry.baseFrequency() << "Hz, TX Offset: " << entry.txOffsetMhz() << ", BW: " << entry.chBandwidthKhz() << ", Spacing: " << entry.chSpaceKhz() << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } { std::stringstream status; yaml::Node cwId = systemConf["cwId"]; bool enabled = cwId["enable"].as(false); uint32_t cwTime = cwId["time"].as(10U); std::string callsign = cwId["callsign"].as(); status << "Callsign: " << callsign << ", CW Interval: " << cwTime << " mins, CW Enabled: " << ((enabled) ? "yes" : "no") << std::endl; status << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } { std::stringstream status; yaml::Node rfssConfig = systemConf["config"]; uint32_t dmrColorCode = rfssConfig["colorCode"].as(2U); uint32_t p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as("293").c_str(), NULL, 16); uint32_t nxdnRAN = rfssConfig["ran"].as(1U); status << "DMR Color Code: " << dmrColorCode << ", P25 NAC: $" << std::hex << p25NAC << std::dec << ", NXDN RAN: " << nxdnRAN << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } { std::stringstream status; yaml::Node rfssConfig = systemConf["config"]; uint32_t siteId = (uint8_t)::strtoul(rfssConfig["siteId"].as("1").c_str(), NULL, 16); uint32_t dmrNetId = (uint32_t)::strtoul(rfssConfig["dmrNetId"].as("1").c_str(), NULL, 16); uint32_t p25NetId = (uint32_t)::strtoul(rfssConfig["netId"].as("BB800").c_str(), NULL, 16); uint32_t p25SysId = (uint32_t)::strtoul(rfssConfig["sysId"].as("001").c_str(), NULL, 16); uint32_t p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as("1").c_str(), NULL, 16); status << "Site Id: $" << std::hex << siteId << ", DMR Network Id: $" << std::hex << dmrNetId << std::dec << std::endl; status << "P25 Net Id: $" << std::hex << p25NetId << ", P25 System Id: $" << std::hex << p25SysId << ", P25 RFSS Id: $" << std::hex << p25RfssId << std::dec << std::endl; status << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } { std::stringstream status; if (!m_isHotspot) { status << "PTT Invert: " << (m_modem->m_pttInvert ? "yes" : "no") << ", RX Invert: " << (m_modem->m_rxInvert ? "yes" : "no") << ", TX Invert: " << (m_modem->m_txInvert ? "yes" : "no") << ", DC Blocker: " << (m_modem->m_dcBlocker ? "yes" : "no") << std::endl; } status << "Rx Level: " << m_modem->m_rxLevel << ", Tx Level: " << m_modem->m_cwIdTXLevel << ", Tx DC Offset: " << m_modem->m_txDCOffset << ", Rx DC Offset: " << m_modem->m_rxDCOffset << std::endl; if (!m_isHotspot) { status << "Rx Coarse: " << (int)(m_modem->m_rxCoarsePot) << ", Rx Fine: " << (int)(m_modem->m_rxFinePot) << ", Tx Coarse: " << (int)(m_modem->m_txCoarsePot) << ", RSSI Coarse: " << (int)(m_modem->m_rssiCoarsePot) << std::endl; status << "DMR Symb +/- 3 Adj.: " << m_modem->m_dmrSymLevel3Adj << ", DMR Symb +/- 1 Adj.: " << m_modem->m_dmrSymLevel1Adj << std::endl; status << "P25 Symb +/- 3 Adj.: " << m_modem->m_p25SymLevel3Adj << ", P25 Symb +/- 1 Adj.: " << m_modem->m_p25SymLevel1Adj << std::endl; // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { status << "NXDN Symb +/- 3 Adj.: " << m_modem->m_nxdnSymLevel3Adj << ", NXDN Symb +/- 1 Adj.: " << m_modem->m_nxdnSymLevel1Adj << std::endl; } } m_setupWnd->m_statusWnd.append(status.str()); } { if (m_isHotspot) { std::stringstream status; status << "DMR Disc. BW: " << (int)(m_modem->m_dmrDiscBWAdj) << ", DMR Post Demod BW: " << (int)(m_modem->m_dmrPostBWAdj) << std::endl; status << "P25 Disc BW: " << (int)(m_modem->m_p25DiscBWAdj) << ", P25 Post Demod BW: " << (int)(m_modem->m_p25PostBWAdj) << std::endl; // are we on a protocol version 3 firmware? if (m_modem->getVersion() >= 3U) { status << "NXDN Disc. BW: " << (int)(m_modem->m_nxdnDiscBWAdj) << ", NXDN Post Demod BW: " << (int)(m_modem->m_nxdnPostBWAdj) << std::endl; status << "AFC Enabled: " << (m_modem->m_afcEnable ? "yes" : "no") << ", KI: " << (uint32_t)(m_modem->m_afcKI) << ", KP: " << (uint32_t)(m_modem->m_afcKP) << ", Range: " << (uint32_t)(m_modem->m_afcRange) << std::endl; } switch (m_modem->m_adfGainMode) { case ADF_GAIN_AUTO_LIN: status << "ADF7021 Gain Mode: Auto High Linearity" << std::endl; break; case ADF_GAIN_LOW: status << "ADF7021 Gain Mode: Low" << std::endl; break; case ADF_GAIN_HIGH: status << "ADF7021 Gain Mode: High" << std::endl; break; case ADF_GAIN_AUTO: default: status << "ADF7021 Gain Mode: Auto" << std::endl; break; } m_setupWnd->m_statusWnd.append(status.str()); } } { std::stringstream status; status << "FDMA Preambles: " << (uint32_t)(m_modem->m_fdmaPreamble) << ", DMR Rx Delay: " << (uint32_t)(m_modem->m_dmrRxDelay) << ", P25 Corr. Count: " << (uint32_t)(m_modem->m_p25CorrCount) << std::endl; /* status << "FDMA Preambles: " << m_modem->m_fdmaPreamble << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_fdmaPreamble) * 0.2222F << ")" << ", DMR Rx Delay: " << m_modem->m_dmrRxDelay << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_dmrRxDelay) * 0.0416666F << ")" << ", P25 Corr. Count: " << m_modem->m_p25CorrCount << "(" << std::fixed << std::setprecision(1) << float(m_modem->m_p25CorrCount) * 0.667F << ")" << std::endl; */ status << "Rx Freq: " << m_modem->m_rxFrequency << "Hz, Tx Freq: " << m_modem->m_txFrequency << "Hz, Rx Offset: " << m_modem->m_rxTuning << "Hz, Tx Offset: " << m_modem->m_txTuning << "Hz" << std::endl; status << "Rx Effective Freq: " << m_rxAdjustedFreq << "Hz, Tx Effective Freq: " << m_txAdjustedFreq << std::endl; m_setupWnd->m_statusWnd.append(status.str()); } getStatus(); } #endif // defined(ENABLE_SETUP_TUI) /// /// Add data frame to the data ring buffer. /// /// /// /// void HostSetup::addFrame(const uint8_t* data, uint32_t length, uint32_t maxFrameSize) { assert(data != nullptr); uint32_t space = m_queue.freeSpace(); if (space < (length + 1U)) { uint32_t queueLen = m_queue.length(); m_queue.resize(queueLen + maxFrameSize); LogError(LOG_CAL, "overflow in the frame queue while writing data; queue free is %u, needed %u; resized was %u is %u", space, length, queueLen, m_queue.length()); return; } uint8_t len = length; m_queue.addData(&len, 1U); m_queue.addData(data, len); } /// /// Counts the total number of bit errors between bytes. /// /// /// /// uint8_t HostSetup::countErrs(uint8_t a, uint8_t b) { int cnt = 0; uint8_t tmp = a ^ b; while (tmp) { if (tmp % 2 == 1) cnt++; tmp /= 2; } return cnt; } #endif // !defined(CATCH2_TEST_COMPILATION)