/* * Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF * Daniel Caujolle-Bert, F1RMB * * Low level DMR stream implementation informed by code written by * DSD Author (anonymous) * MBELib Author (anonymous) * Ian Wraith G7GHH * Jonathan Naylor G4KLX * * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "functions/hotspot.h" #include #include #include #include #include "functions/calibration.h" #include "functions/hotspot.h" #include "user_interface/menuSystem.h" #include "user_interface/uiUtilities.h" #include "user_interface/uiLocalisation.h" #include "hardware/HR-C6000.h" #include "functions/settings.h" #include "functions/sound.h" #include "functions/ticks.h" #include "functions/trx.h" #include "usb/usb_com.h" #include "functions/rxPowerSaving.h" #include "user_interface/uiHotspot.h" #if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) #include "hardware/radioHardwareInterface.h" #endif #define MMDVM_HEADER_LENGTH 4 #define concat(a, b) a " GitID #" b "" #define WRITE_BIT1(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7]) #define READ_BIT1(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7]) static void ReedSolomonDMREncode(const uint8_t *inputData, uint8_t *outputData); static uint8_t LUT_Mult(uint8_t a, uint8_t b); static void BPTCglobalsInit(void); static void BPTCdecode(const uint8_t *inputData, uint8_t *outputData); static void BPTCencode(const uint8_t *inputData, uint8_t *outputData); static void DMRLC2Bytes(const DMRLC_t *LC_DataInput, uint8_t *outputBytes); static uint8_t hammingGetBits(bool *inputOutputBooleanBitsArray, bool is16114); static void hammingEncode(bool *inputOutputBooleanBitsArray,bool is16114); static bool hammingDecodeType1(bool *inputOutputBooleanBitsArray); static bool hammingDecodeType2(bool *inputOutputBooleanBitsArray); static void embeddedDataDecodeEmbeddedData(void); static void embeddedDataEncodeEmbeddedData(void); static uint32_t CRC_encodeFiveBit(const bool *in); static void byteToBooleanBitsArray(uint8_t byteIn, bool *bitsOut); static uint8_t BooleanBitsArrayToByte(const bool *bitsIn); static uint8_t setFreq(const uint8_t *data, uint8_t length); static void sendNAK(uint8_t cmd, uint8_t err); static void sendACK(uint8_t cmd); static uint8_t hotspotModeReceiveNetFrame(const uint8_t *comBuffer, uint8_t timeSlot); static bool voiceLCHeaderDecode(const uint8_t *data, uint8_t type, DMRLC_t *lc); static bool DMRFullLC_encode(DMRLC_t *lc, uint8_t *data, uint8_t type); static void embeddedDataBuffersInt(void); static bool embeddedDataAddData(const uint8_t *data, uint8_t lcss); static void embeddedDataGetData(uint8_t sequenceNumber, uint8_t *outputData); static bool embeddedDataGetRawData(uint8_t *outputData); static void embeddedDataSetLC(const DMRLC_t *lc); static bool hasTXOverflow(void); static bool hasRXOverflow(void); extern LinkItem_t *LinkHead; static const uint8_t PROTOCOL_VERSION = 1; #if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) static const char HARDWARE[] = concat(HOTSPOT_VERSION_STRING, XSTRINGIFY(GITVERSION)); #else static const char HARDWARE[] = concat(HOTSPOT_VERSION_STRING, GITVERSION); #endif static const uint8_t MS_SOURCED_AUDIO_SYNC[] = { 0x07, 0xF7, 0xD5, 0xDD, 0x57, 0xDF, 0xD0 }; static const uint8_t SYNC_MASK[] = { 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0 }; static const uint8_t MMDVM_VOICE_SYNC_PATTERN = 0x20; static const int EMBEDDED_DATA_OFFSET = 13; static const int TX_BUFFER_MIN_BEFORE_TRANSMISSION = 4; static const uint8_t START_FRAME_PATTERN[] = { 0xFF,0x57,0xD7,0x5D,0xF5,0xD9 }; static const uint8_t END_FRAME_PATTERN[] = { 0x5D,0x7F,0x77,0xFD,0x75,0x79 }; static const uint8_t VOICE_LC_SYNC_FULL[] = { 0x04, 0x6D, 0x5D, 0x7F, 0x77, 0xFD, 0x75, 0x7E, 0x30 }; static const uint8_t TERMINATOR_LC_SYNC_FULL[] = { 0x04, 0xAD, 0x5D, 0x7F, 0x77, 0xFD, 0x75, 0x79, 0x60 }; static const uint8_t LC_SYNC_MASK_FULL[] = { 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0 }; static const uint8_t DMR_AUDIO_SEQ_SYNC[6][7] = { { 0x07, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xD0 }, // seq 0 NOT USED AS THIS IS THE SYNC { 0x01, 0x30, 0x00, 0x00, 0x00, 0x09, 0x10 }, // seq 1 { 0x01, 0x70, 0x00, 0x00, 0x00, 0x07, 0x40 }, // seq 2 { 0x01, 0x70, 0x00, 0x00, 0x00, 0x07, 0x40 }, // seq 3 { 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x70 }, // seq 4 { 0x01, 0x10, 0x00, 0x00, 0x00, 0x0E, 0x20 } // seq 5 }; static const uint8_t DMR_AUDIO_SEQ_MASK[] = { 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x0F, 0xF0 }; static const uint8_t DMR_EMBED_SEQ_MASK[] = { 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xF0, 0x00 }; static const uint8_t BIT_MASK_TABLE[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; static const struct { uint8_t c; uint32_t pattern; uint8_t length; } CW_SYMBOL_LIST[] = { {'A', 0xB8000000, 8}, {'B', 0xEA800000, 12}, {'C', 0xEBA00000, 14}, {'D', 0xEA000000, 10}, {'E', 0x80000000, 4}, {'F', 0xAE800000, 12}, {'G', 0xEE800000, 12}, {'H', 0xAA000000, 10}, {'I', 0xA0000000, 6}, {'J', 0xBBB80000, 16}, {'K', 0xEB800000, 12}, {'L', 0xBA800000, 12}, {'M', 0xEE000000, 10}, {'N', 0xE8000000, 8}, {'O', 0xEEE00000, 14}, {'P', 0xBBA00000, 14}, {'Q', 0xEEB80000, 16}, {'R', 0xBA000000, 10}, {'S', 0xA8000000, 8}, {'T', 0xE0000000, 6}, {'U', 0xAE000000, 10}, {'V', 0xAB800000, 12}, {'W', 0xBB800000, 12}, {'X', 0xEAE00000, 14}, {'Y', 0xEBB80000, 16}, {'Z', 0xEEA00000, 14}, {'1', 0xBBBB8000, 20}, {'2', 0xAEEE0000, 18}, {'3', 0xABB80000, 16}, {'4', 0xAAE00000, 14}, {'5', 0xAA800000, 12}, {'6', 0xEAA00000, 14}, {'7', 0xEEA80000, 16}, {'8', 0xEEEA0000, 18}, {'9', 0xEEEE8000, 20}, {'0', 0xEEEEE000, 22}, {'/', 0xEAE80000, 16}, {'?', 0xAEEA0000, 18}, {',', 0xEEAEE000, 22}, {'-', 0xEAAE0000, 18}, {'=', 0xEAB80000, 16}, {'.', 0xBAEB8000, 20}, {' ', 0x00000000, 4}, {0, 0x00000000, 0} }; static const uint8_t VOICE_LC_HEADER_CRC_MASK[] = {0x96, 0x96, 0x96}; static const uint8_t TERMINATOR_WITH_LC_CRC_MASK[] = {0x99, 0x99, 0x99}; static const int BPTC19696CopyRanges[][2] = {{4,11},{16,26},{31,41},{46,56},{61,71},{76,86},{91,101},{106,116},{121,131}}; static const int embedddataCopyRanges[][2] = {{0,10},{16,26},{32,41},{48,57},{64,73},{80,89},{96,105}}; static uint8_t hotspotTxLC[9]; static bool startedEmbeddedSearch = false; // USB TX read/write positions and count static volatile uint16_t usbComSendBufWritePosition = 0; static volatile uint16_t usbComSendBufReadPosition = 0; static volatile uint16_t usbComSendBufCount = 0; // RF data read/write positions and count static volatile uint32_t rfFrameBufReadIdx = 0; static volatile uint32_t rfFrameBufWriteIdx = 0; static volatile uint32_t rfFrameBufCount = 0; static uint8_t lastRxState = HOTSPOT_RX_IDLE; static const int TX_BUFFERING_TIMEOUT = 360; static const int RX_NET_FRAME_TIMEOUT = 360; static int timeoutCounter; static uint32_t mmdvmHostLastActiveTime = 0; // store last activity time (ms) static const uint32_t MMDVMHOST_TIMEOUT = 20000; // 20s timeout (MMDVMHost mode only, there is no timeout for BlueDV) static volatile HOTSPOT_STATE hotspotState = HOTSPOT_STATE_NOT_CONNECTED; static uint8_t rf_power = 255; static int txStopDelay = 0; static int netRXDataTimer = 0; static bool rxLCFrameSent = false; typedef enum { LCS_0, LCS_1, LCS_2, LCS_3 } LC_STATE_t; static uint8_t colorCode = 1; static char overriddenLCTA[2 * 9] = {0}; // 2 LC frame only (enough to store callsign) static bool overriddenLCAvailable = false; static uint32_t hotspotTxDelay = 0; static uint8_t overriddenBlocksTA = 0x00; static LC_STATE_t embeddedDataSequenceState; static bool embeddedDataRaw[128]; static bool embeddedDataProcessed[72]; static int embeddedDataFLCO; static bool embeddedDataIsValid; #if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_DM1801) || defined(PLATFORM_DM1801A) || defined(PLATFORM_RD5R) #define BPTRAMLOCATION ".data.$RAM2" #else #define BPTRAMLOCATION ".ccmram" #endif __attribute__((section(BPTRAMLOCATION))) static bool BPTCRaw[196]; __attribute__((section(BPTRAMLOCATION))) static bool BPTCDeInterleaved[196]; static const uint32_t cwDOTDuration = 60; // 60ms per DOT static ticksTimer_t cwNextPeriodTimer = { 0, 0 }; static uint8_t cwBuffer[64]; static uint16_t cwpoPtr; #if defined(PLATFORM_MD9600) static HRC6000_Tone1Config_t tone1Config; #endif bool hotspotCwKeying = false; uint16_t hotspotCwpoLen; uint8_t hotspotCurrentRxCommandState; uint32_t hotspotFreqRx = 0; uint32_t hotspotFreqTx = 0; char hotspotMmdvmQSOInfoIP[22] = {0}; // use 6x8 font; 21 char long DMRLC_t hotspotRxedDMR_LC; // used to stored LC info from RXed frames uint8_t hotspotSavedPowerLevel = POWER_UNSET;// no power level saved yet bool hotspotMmdvmHostIsConnected = false; uint8_t hotspotPowerLevel = 0;// no power level saved yet volatile MMDVM_STATE hotspotModemState = STATE_IDLE; static volatile MMDVMHOST_RX_STATE MMDVMHostRxState; static bool voiceLCHeaderDecode(const uint8_t *data, uint8_t type, DMRLC_t *lc) { uint8_t parityCheckArray[4]; BPTCglobalsInit(); BPTCdecode(data, lc->rawData); lc->rawData[9] ^= VOICE_LC_HEADER_CRC_MASK[0]; lc->rawData[10] ^= VOICE_LC_HEADER_CRC_MASK[1]; lc->rawData[11] ^= VOICE_LC_HEADER_CRC_MASK[2]; ReedSolomonDMREncode(lc->rawData, parityCheckArray); if (!((lc->rawData[9] == parityCheckArray[2]) && (lc->rawData[10] == parityCheckArray[1]) && (lc->rawData[11] == parityCheckArray[0]))) { return false; } lc->PF = (lc->rawData[0] & 0x80) == 0x80; lc->R = (lc->rawData[0] & 0x40) == 0x40; lc->FLCO = lc->rawData[0] & 0x3F; lc->FID = lc->rawData[1]; lc->options = lc->rawData[2]; lc->dstId = (((uint32_t)lc->rawData[3]) << 16) + (((uint32_t)lc->rawData[4]) << 8) + ((uint32_t)lc->rawData[5]); lc->srcId = (((uint32_t)lc->rawData[6]) << 16) + (((uint32_t)lc->rawData[7]) << 8) + ((uint32_t)lc->rawData[8]); return true; } bool DMRFullLC_encode(DMRLC_t *lc, uint8_t *data, uint8_t type) { uint8_t lcData[LC_DATA_LENGTH]; DMRLC2Bytes(lc, lcData); uint8_t parity[4]; ReedSolomonDMREncode(lcData, parity); if (type == DT_VOICE_LC_HEADER) { lcData[9] = parity[2] ^ VOICE_LC_HEADER_CRC_MASK[0]; lcData[10] = parity[1] ^ VOICE_LC_HEADER_CRC_MASK[1]; lcData[11] = parity[0] ^ VOICE_LC_HEADER_CRC_MASK[2]; } else { // must be DT_TERMINATOR_WITH_LC: lcData[9] = parity[2] ^ TERMINATOR_WITH_LC_CRC_MASK[0]; lcData[10] = parity[1] ^ TERMINATOR_WITH_LC_CRC_MASK[1]; lcData[11] = parity[0] ^ TERMINATOR_WITH_LC_CRC_MASK[2]; } BPTCglobalsInit(); BPTCencode(lcData, data); return true; } static void embeddedDataBuffersInt(void) { memset(embeddedDataRaw, 0, sizeof(embeddedDataRaw)); memset(embeddedDataProcessed, 0, sizeof(embeddedDataProcessed)); embeddedDataFLCO = 0; embeddedDataIsValid = false; } static bool embeddedDataAddData(const uint8_t *data, uint8_t lcss) { bool rawData[36]; for (int i = 0; i < 5; i++) { byteToBooleanBitsArray(data[i + 14], rawData + (i << 3)); } switch (lcss) { case 1: for (int i = 0; i < 32; i++) { embeddedDataRaw[i] = rawData[i + 4]; } embeddedDataSequenceState = LCS_1; embeddedDataIsValid = false; return false; break; case 2: if (embeddedDataSequenceState == LCS_3) { for (int i = 0; i < 32; i++) { embeddedDataRaw[i + 96] = rawData[i + 4]; } embeddedDataSequenceState = LCS_0; embeddedDataDecodeEmbeddedData(); if (embeddedDataIsValid) { embeddedDataEncodeEmbeddedData(); } return embeddedDataIsValid; } break; case 3: switch (embeddedDataSequenceState) { case LCS_1: for (int i = 4; i < 36; i++) { embeddedDataRaw[i + 28] = rawData[i]; } embeddedDataSequenceState = LCS_2; return false; break; case LCS_2: for (int i = 0; i < 32; i++) { embeddedDataRaw[i + 64] = rawData[i + 4]; } embeddedDataSequenceState = LCS_3; return false; break; default: break; } break; } return false; } static void embeddedDataGetData(uint8_t sequenceNumber, uint8_t *outputData) { memset(outputData, 0, DMR_FRAME_LENGTH_BYTES);//clear if ((sequenceNumber >= 1) && (sequenceNumber < 5)) { bool bits[40]; uint8_t bytes[5]; sequenceNumber--; memset(bits, 0, 40 * sizeof(bool)); memcpy(bits + 4, embeddedDataRaw + (sequenceNumber * 32), 32 * sizeof(bool)); for (int i = 0; i < 5; i++) { bytes[i] = BooleanBitsArrayToByte(bits + (i << 3)); } outputData[14] = (outputData[14] & 0xF0) | (bytes[0] & 0x0F); outputData[15] = bytes[1]; outputData[16] = bytes[2]; outputData[17] = bytes[3]; outputData[18] = (outputData[18] & 0x0F) | (bytes[4] & 0xF0); return; } outputData[14] &= 0xF0; outputData[15] = 0x00; outputData[16] = 0x00; outputData[17] = 0x00; outputData[18] &= 0x0F; } static bool embeddedDataGetRawData(uint8_t *outputData) { if (!embeddedDataIsValid) { return false; } for (int i = 0; i < 9; i++) { outputData[i] = BooleanBitsArrayToByte(embeddedDataProcessed + (i << 3)); } return true; } static void embeddedDataSetLC(const DMRLC_t *lc) { uint8_t bytes[9]; DMRLC2Bytes(lc, bytes); for (int i = 0; i < 9; i++) { byteToBooleanBitsArray(bytes[i], embeddedDataProcessed + (i << 3)); } embeddedDataFLCO = lc->FLCO; embeddedDataIsValid = true; embeddedDataEncodeEmbeddedData(); } static uint8_t LUT_Mult(uint8_t a, uint8_t b) { /* LUTs from * ETSI TS 102 361-1 V2.2.1 (2013-02) * Page 138 */ const uint8_t EXP_LUT[] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, 0x1D, 0x3A, 0x74, 0xE8, 0xCD, 0x87, 0x13, 0x26, 0x4C, 0x98, 0x2D, 0x5A, 0xB4, 0x75, 0xEA, 0xC9, 0x8F, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x9D, 0x27, 0x4E, 0x9C, 0x25, 0x4A, 0x94, 0x35, 0x6A, 0xD4, 0xB5, 0x77, 0xEE, 0xC1, 0x9F, 0x23, 0x46, 0x8C, 0x05, 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x5D, 0xBA, 0x69, 0xD2, 0xB9, 0x6F, 0xDE, 0xA1, 0x5F, 0xBE, 0x61, 0xC2, 0x99, 0x2F, 0x5E, 0xBC, 0x65, 0xCA, 0x89, 0x0F, 0x1E, 0x3C, 0x78, 0xF0, 0xFD, 0xE7, 0xD3, 0xBB, 0x6B, 0xD6, 0xB1, 0x7F, 0xFE, 0xE1, 0xDF, 0xA3, 0x5B, 0xB6, 0x71, 0xE2, 0xD9, 0xAF, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0D, 0x1A, 0x34, 0x68, 0xD0, 0xBD, 0x67, 0xCE, 0x81, 0x1F, 0x3E, 0x7C, 0xF8, 0xED, 0xC7, 0x93, 0x3B, 0x76, 0xEC, 0xC5, 0x97, 0x33, 0x66, 0xCC, 0x85, 0x17, 0x2E, 0x5C, 0xB8, 0x6D, 0xDA, 0xA9, 0x4F, 0x9E, 0x21, 0x42, 0x84, 0x15, 0x2A, 0x54, 0xA8, 0x4D, 0x9A, 0x29, 0x52, 0xA4, 0x55, 0xAA, 0x49, 0x92, 0x39, 0x72, 0xE4, 0xD5, 0xB7, 0x73, 0xE6, 0xD1, 0xBF, 0x63, 0xC6, 0x91, 0x3F, 0x7E, 0xFC, 0xE5, 0xD7, 0xB3, 0x7B, 0xF6, 0xF1, 0xFF, 0xE3, 0xDB, 0xAB, 0x4B, 0x96, 0x31, 0x62, 0xC4, 0x95, 0x37, 0x6E, 0xDC, 0xA5, 0x57, 0xAE, 0x41, 0x82, 0x19, 0x32, 0x64, 0xC8, 0x8D, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xDD, 0xA7, 0x53, 0xA6, 0x51, 0xA2, 0x59, 0xB2, 0x79, 0xF2, 0xF9, 0xEF, 0xC3, 0x9B, 0x2B, 0x56, 0xAC, 0x45, 0x8A, 0x09, 0x12, 0x24, 0x48, 0x90, 0x3D, 0x7A, 0xF4, 0xF5, 0xF7, 0xF3, 0xFB, 0xEB, 0xCB, 0x8B, 0x0B, 0x16, 0x2C, 0x58, 0xB0, 0x7D, 0xFA, 0xE9, 0xCF, 0x83, 0x1B, 0x36, 0x6C, 0xD8, 0xAD, 0x47, 0x8E }; const uint8_t LOG_LUT[] = { 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 }; if ((a == 0) || (b == 0)) { return 0; } int sum = LOG_LUT[a] + LOG_LUT[b]; if (sum != 511) { return EXP_LUT[sum % 255]; } return 0; } static void ReedSolomonDMREncode(const uint8_t *inputData, uint8_t *outputData) { const uint8_t POLYNOMIAL_FACTORS[3] = {64, 56, 14}; memset(outputData, 0, 4 * sizeof(uint8_t)); for (int i = 0; i < 9; i++) { uint8_t tmp = inputData[i] ^ outputData[2]; for (int j = 2; j > 0; j--) { outputData[j] = outputData[j - 1] ^ LUT_Mult(POLYNOMIAL_FACTORS[j], tmp); } outputData[0] = LUT_Mult(POLYNOMIAL_FACTORS[0], tmp); } } static void BPTCglobalsInit(void) { memset(BPTCRaw, 0, sizeof(BPTCRaw)); memset(BPTCDeInterleaved, 0, sizeof(BPTCDeInterleaved)); } static void BPTCdecode(const uint8_t *inputData, uint8_t *outputData) { // 0xFF means don't use this value const uint8_t BITS_LOOKUP[16] = {0xFF, 9, 10, 6, 11, 3, 7, 1, 12, 0xFF, 4, 0xFF, 8, 5, 2, 0}; bool bitData[96]; bool tmpArray[13]; uint32_t bitDataIndex = 0; bool stillProcessing; uint8_t n; for (int i = 0; i < 13; i++) { byteToBooleanBitsArray(inputData[i], BPTCRaw + (i << 3)); } byteToBooleanBitsArray(inputData[20], tmpArray); BPTCRaw[98] = tmpArray[6]; BPTCRaw[99] = tmpArray[7]; for (int i = 0; i < 13; i++) { byteToBooleanBitsArray(inputData[i + 21], BPTCRaw + (100 + (i << 3))); } for (int i = 0; i < 196; i++) { BPTCDeInterleaved[i] = BPTCRaw[(i * 181) % 196];// interleave } stillProcessing = true;// Need to initially set this to true to start the for loop for (int i = 0; ((i < 5) && stillProcessing); i++) { stillProcessing = false; for (int j = 0; j < 15; j++) { int pos = j + 1; for (int k = 0; k < 13; k++) { tmpArray[k] = BPTCDeInterleaved[pos]; pos += 15; } bool hammingOK = false; n = ((tmpArray[0] ^ tmpArray[1] ^ tmpArray[3] ^ tmpArray[5] ^ tmpArray[6]) != tmpArray[9]) ? 0x01 : 0x00; n |= ((tmpArray[0] ^ tmpArray[1] ^ tmpArray[2] ^ tmpArray[4] ^ tmpArray[6] ^ tmpArray[7]) != tmpArray[10]) ? 0x02 : 0x00; n |= ((tmpArray[0] ^ tmpArray[1] ^ tmpArray[2] ^ tmpArray[3] ^ tmpArray[5] ^ tmpArray[7] ^ tmpArray[8]) != tmpArray[11]) ? 0x04 : 0x00; n |= ((tmpArray[0] ^ tmpArray[2] ^ tmpArray[4] ^ tmpArray[5] ^ tmpArray[8]) != tmpArray[12]) ? 0x08 : 0x00; if (n < 16) { uint8_t bitLocation = BITS_LOOKUP[n]; if (bitLocation != 0xFF) { tmpArray[bitLocation] = !tmpArray[bitLocation]; hammingOK = true; } } if (hammingOK) { pos = j + 1; for (int k = 0; k < 13; k++) { BPTCDeInterleaved[pos] = tmpArray[k]; pos += 15; } stillProcessing = true; } } for (int j = 0; j < 9; j++) { uint32_t pos = (j * 15) + 1; if (hammingDecodeType2(BPTCDeInterleaved + pos)) { stillProcessing = true; } } } for (int range = 0; range < 9; range++) { for (uint32_t a = BPTC19696CopyRanges[range][0]; a <= BPTC19696CopyRanges[range][1]; a++, bitDataIndex++) { bitData[bitDataIndex] = BPTCDeInterleaved[a]; } } for (int i = 0; i < LC_DATA_LENGTH; i++) { outputData[i] = BooleanBitsArrayToByte(bitData + (i << 3)); } } static void BPTCencode(const uint8_t *inputData, uint8_t *outputData) { uint8_t byteData; uint32_t bitDataPosition = 0; bool bitData[96]; bool hammingBits[13]; for (int i = 0; i < LC_DATA_LENGTH; i++) { byteToBooleanBitsArray(inputData[i], bitData + (i << 3)); } memset(BPTCDeInterleaved, 0, 196 * sizeof(bool)); for (int range = 0; range < 9; range++) { for (uint32_t a = BPTC19696CopyRanges[range][0]; a <= BPTC19696CopyRanges[range][1]; a++, bitDataPosition++) { BPTCDeInterleaved[a] = bitData[bitDataPosition]; } } for (int i = 0; i < 9; i++) { hammingEncode(BPTCDeInterleaved + ((i * 15) + 1), false); } for (int i = 0; i < 15; i++) { int pos = i + 1; for (int j = 0; j < 13; j++) { hammingBits[j] = BPTCDeInterleaved[pos]; pos += 15; } hammingBits[9] = hammingBits[0] ^ hammingBits[1] ^ hammingBits[3] ^ hammingBits[5] ^ hammingBits[6]; hammingBits[10] = hammingBits[0] ^ hammingBits[1] ^ hammingBits[2] ^ hammingBits[4] ^ hammingBits[6] ^ hammingBits[7]; hammingBits[11] = hammingBits[0] ^ hammingBits[1] ^ hammingBits[2] ^ hammingBits[3] ^ hammingBits[5] ^ hammingBits[7] ^ hammingBits[8]; hammingBits[12] = hammingBits[0] ^ hammingBits[2] ^ hammingBits[4] ^ hammingBits[5] ^ hammingBits[8]; pos = i + 1; for (int j = 0; j < 13; j++) { BPTCDeInterleaved[pos] = hammingBits[j]; pos += 15; } } for (int i = 0; i < 196; i++) { BPTCRaw[(i * 181) % 196] = BPTCDeInterleaved[i];// interleave } for (int i = 0; i < LC_DATA_LENGTH; i++) { outputData[i] = BooleanBitsArrayToByte(BPTCRaw + (i << 3)); } byteData = BooleanBitsArrayToByte(BPTCRaw + 96); outputData[12] = (outputData[12] & 0x3F) | ((byteData >> 0) & 0xC0); outputData[20] = (outputData[20] & 0xFC) | ((byteData >> 4) & 0x03); for (int i = 0; i < 12; i++) { outputData[i + 21] = BooleanBitsArrayToByte(BPTCRaw + 100 + (i << 3)); } } static void DMRLC2Bytes(const DMRLC_t *LC_DataInput, uint8_t *outputBytes) { outputBytes[0] = (uint8_t)LC_DataInput->FLCO; if (LC_DataInput->PF) { outputBytes[0] |= 0x80; } if (LC_DataInput->R) { outputBytes[0] |= 0x40; } outputBytes[1] = LC_DataInput->FID; outputBytes[2] = LC_DataInput->options; outputBytes[3] = (LC_DataInput->dstId >> 16) & 0xFF; outputBytes[4] = (LC_DataInput->dstId >> 8) & 0xFF; outputBytes[5] = (LC_DataInput->dstId & 0xFF); outputBytes[6] = (LC_DataInput->srcId >> 16) & 0xFF; outputBytes[7] = (LC_DataInput->srcId >> 8) & 0xFF; outputBytes[8] = (LC_DataInput->srcId & 0xFF); } static bool hammingDecodeType2(bool *inputOutputBooleanBitsArray) { const uint8_t BITS_LOOKUP[16] = {0xFF, 11, 12, 8, 13, 5, 9, 3, 14, 0, 6, 1, 10, 7, 4, 2}; uint8_t numBits = hammingGetBits(inputOutputBooleanBitsArray, false); if (numBits < 16) { uint8_t bitLocation = BITS_LOOKUP[numBits]; if (bitLocation != 0xFF) { inputOutputBooleanBitsArray[bitLocation] = !inputOutputBooleanBitsArray[bitLocation]; return true; } } return false; } static bool hammingDecodeType1(bool *inputOutputBooleanBitsArray) { // 0xFF means don't use this value. Also Index 0 is never used, its only here to reduce the number of if's const uint8_t BITS_LOOKUP[32] = { 0xFF, 11, 12, 0xFF, 13, 0xFF, 0xFF, 3, 14, 0xFF, 0xFF, 1, 0xFF, 7, 4, 0xFF, 15, 0xFF, 0xFF, 8, 0xFF, 5, 9, 0xFF, 0xFF, 0, 6, 0xFF, 10, 0xFF ,0xFF, 2}; uint8_t c = hammingGetBits(inputOutputBooleanBitsArray, true); if (c == 0) { return true; } if (c < 32) { uint8_t bitLocation = BITS_LOOKUP[c]; if (bitLocation != 0xFF) { inputOutputBooleanBitsArray[bitLocation] = !inputOutputBooleanBitsArray[bitLocation]; return true; } } return false; } static void hammingEncode(bool *inputOutputBooleanBitsArray,bool is16114) { inputOutputBooleanBitsArray[11] = inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[8]; inputOutputBooleanBitsArray[12] = inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[8] ^ inputOutputBooleanBitsArray[9]; inputOutputBooleanBitsArray[13] = inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[9] ^ inputOutputBooleanBitsArray[10]; inputOutputBooleanBitsArray[14] = inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[10]; if (is16114) { inputOutputBooleanBitsArray[15] = inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[8] ^ inputOutputBooleanBitsArray[9] ^ inputOutputBooleanBitsArray[10]; } } static uint8_t hammingGetBits(bool *inputOutputBooleanBitsArray, bool is16114) { uint8_t n; n = ((inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[8]) != inputOutputBooleanBitsArray[11]) ? 0x01 : 0x00; n |= ((inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[8] ^ inputOutputBooleanBitsArray[9]) != inputOutputBooleanBitsArray[12]) ? 0x02 : 0x00; n |= ((inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[3] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[9] ^ inputOutputBooleanBitsArray[10]) != inputOutputBooleanBitsArray[13]) ? 0x04 : 0x00; n |= ((inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[1] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[4] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[7] ^ inputOutputBooleanBitsArray[10]) != inputOutputBooleanBitsArray[14]) ? 0x08 : 0x00; if (is16114) { n |= ((inputOutputBooleanBitsArray[0] ^ inputOutputBooleanBitsArray[2] ^ inputOutputBooleanBitsArray[5] ^ inputOutputBooleanBitsArray[6] ^ inputOutputBooleanBitsArray[8] ^ inputOutputBooleanBitsArray[9] ^ inputOutputBooleanBitsArray[10]) != inputOutputBooleanBitsArray[15]) ? 0x10 : 0x00; } return n; } static void embeddedDataEncodeEmbeddedData(void) { bool data[128]; uint32_t arrayIndex = 0; uint32_t crc = CRC_encodeFiveBit(embeddedDataProcessed); memset(data, 0, 128 * sizeof(bool)); data[106] = (crc & 0x01) == 0x01; data[90] = (crc & 0x02) == 0x02; data[74] = (crc & 0x04) == 0x04; data[58] = (crc & 0x08) == 0x08; data[42] = (crc & 0x10) == 0x10; for (int range = 0; range < 7; range++) { for (uint32_t i = embedddataCopyRanges[range][0]; i <= embedddataCopyRanges[range][1]; i++, arrayIndex++) { data[i] = embeddedDataProcessed[arrayIndex]; } } for (int i = 0; i < 112; i += 16) { hammingEncode(data + i, true); } for (int i = 0; i < 16; i++) { data[i + 112] = data[i + 0] ^ data[i + 16] ^ data[i + 32] ^ data[i + 48] ^ data[i + 64] ^ data[i + 80] ^ data[i + 96]; } arrayIndex = 0; for (int i = 0; i < 128; i++) { embeddedDataRaw[i] = data[arrayIndex]; arrayIndex += 16; if (arrayIndex > 127) { arrayIndex -= 127; } } } static void embeddedDataDecodeEmbeddedData(void) { uint32_t crc = 0; bool tmpBooleanBitsArray[128]; int bitArrayIndex = 0; memset(tmpBooleanBitsArray, 0, 128 * sizeof(bool)); for (int i = 0; i < 128; i++) { tmpBooleanBitsArray[bitArrayIndex] = embeddedDataRaw[i]; bitArrayIndex += 16; if (bitArrayIndex > 127) { bitArrayIndex -= 127; } } for (int i = 0; i < 112; i += 16) { if (!hammingDecodeType1(tmpBooleanBitsArray + i)) { return; } } // Check parity for (int i = 0; i < 16; i++) { bool parity = tmpBooleanBitsArray[i + 0] ^ tmpBooleanBitsArray[i + 16] ^ tmpBooleanBitsArray[i + 32] ^ tmpBooleanBitsArray[i + 48] ^ tmpBooleanBitsArray[i + 64] ^ tmpBooleanBitsArray[i + 80] ^ tmpBooleanBitsArray[i + 96] ^ tmpBooleanBitsArray[i + 112]; if (parity) { return; } } bitArrayIndex = 0; for (int range = 0; range < 7; range++) { for (uint32_t i = embedddataCopyRanges[range][0]; i <= embedddataCopyRanges[range][1]; i++, bitArrayIndex++) { embeddedDataProcessed[bitArrayIndex] = tmpBooleanBitsArray[i]; } } if (tmpBooleanBitsArray[42]) { crc += 16; } if (tmpBooleanBitsArray[58]) { crc += 8; } if (tmpBooleanBitsArray[74]) { crc += 4; } if (tmpBooleanBitsArray[90]) { crc += 2; } if (tmpBooleanBitsArray[106]) { crc += 1; } if (crc != CRC_encodeFiveBit(embeddedDataProcessed)) { return; } embeddedDataIsValid = true; uint8_t flco = BooleanBitsArrayToByte(embeddedDataProcessed + 0); embeddedDataFLCO = (int)(flco & 0x3F); } static uint32_t CRC_encodeFiveBit(const bool *in) { uint32_t total = 0; for (int i = 0; i < 72; i += 8) { total += BooleanBitsArrayToByte(in + i); } total %= 31; return total; } static void byteToBooleanBitsArray(uint8_t byteIn, bool *bitsOut) { for (int i = 0, shift = 7; i < 8; i++, shift--) { bitsOut[i] = (byteIn >> shift) & 0x01; } } static uint8_t BooleanBitsArrayToByte(const bool *bitsIn) { uint8_t out = 0; for (int i = 0, shift = 7; i < 8; i++, shift--) { out |= bitsIn[i] << shift; } return out; } void cwProcess(void) { if (hotspotCwpoLen == 0) { return; } if (ticksTimerHasExpired(&cwNextPeriodTimer)) { ticksTimerStart(&cwNextPeriodTimer, cwDOTDuration); bool b = READ_BIT1(cwBuffer, cwpoPtr); static bool lastValue = true; if (lastValue != b) { lastValue = b; if (b) { trxSetTone1(880); } else { trxSetTone1(0); } } cwpoPtr++; if (cwpoPtr >= hotspotCwpoLen) { cwpoPtr = 0; hotspotCwpoLen = 0; return; } } } void cwReset(void) { hotspotCwpoLen = 0; cwpoPtr = 0; } static uint8_t handleCWID(const uint8_t *data, uint8_t length) { cwReset(); memset(cwBuffer, 0x00, sizeof(cwBuffer)); hotspotCwpoLen = 6; // Silence at the beginning cwpoPtr = 0; for (uint8_t i = 0; i < length; i++) { for (uint8_t j = 0; CW_SYMBOL_LIST[j].c != 0; j++) { if (CW_SYMBOL_LIST[j].c == data[i]) { uint32_t MASK = 0x80000000; for (uint8_t k = 0; k < CW_SYMBOL_LIST[j].length; k++, hotspotCwpoLen++, MASK >>= 1) { bool b = (CW_SYMBOL_LIST[j].pattern & MASK) == MASK; WRITE_BIT1(cwBuffer, hotspotCwpoLen, b); if (hotspotCwpoLen >= ((sizeof(cwBuffer) * 8) - 3)) // Will overflow otherwise { hotspotCwpoLen = 0; return 4; } } break; } } } // An empty message if (hotspotCwpoLen == 6) { hotspotCwpoLen = 0; return 4; } // Silence at the end hotspotCwpoLen += 3; return 0; } static void getStatus(void) { uint8_t buf[16]; // Send all sorts of interesting internal values buf[0] = MMDVM_FRAME_START; buf[1] = 13; buf[2] = MMDVM_GET_STATUS; buf[3] = (0x02 | 0x20); // DMR and POCSAG enabled buf[4] = hotspotModemState; buf[5] = ( ((hotspotState == HOTSPOT_STATE_TX_START_BUFFERING) || (hotspotState == HOTSPOT_STATE_TRANSMITTING) || (hotspotState == HOTSPOT_STATE_TX_SHUTDOWN)) || hotspotCwKeying ) ? 0x01 : 0x00; if (hasRXOverflow()) { buf[5] |= 0x04; } if (hasTXOverflow()) { buf[5] |= 0x08; } buf[6] = 0; // No DSTAR space buf[7] = 10; // DMR Simplex buf[8] = (HOTSPOT_BUFFER_COUNT - wavbuffer_count); // DMR space buf[9] = 0; // No YSF space buf[10] = 0; // No P25 space buf[11] = 0; // no NXDN space buf[12] = 1; // virtual space for POCSAG if (!hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } enqueueUSBData(buf, buf[1]); } static uint8_t setConfig(const uint8_t *data, uint8_t length) { if (length < 13) { return 4; } uint32_t tempTXDelay = (data[2] * 10); // MMDVMHost send in 10ms units, we use 1ms PIT counter unit if (tempTXDelay > 1000) // 1s limit { return 4; } hotspotTxDelay = tempTXDelay; // Only supported mode are DMR, CWID, POCSAG and IDLE switch (data[3]) { case STATE_IDLE: case STATE_DMR: case STATE_CWID: case STATE_POCSAG: break; default: return 4; break; } hotspotModemState = (MMDVM_STATE)data[3]; uint8_t tmpColorCode = data[6]; if (tmpColorCode > 15) { return 4; } colorCode = tmpColorCode; trxSetDMRColourCode(colorCode); /* To Do m_cwIdTXLevel = data[5]>>2; uint8_t dmrTXLevel = data[10]; io.setDeviations(dstarTXLevel, dmrTXLevel, ysfTXLevel, p25TXLevel, nxdnTXLevel, pocsagTXLevel, ysfLoDev); dmrDMOTX.setTXDelay(txDelay); */ if ((hotspotModemState == STATE_DMR) && (hotspotState == HOTSPOT_STATE_NOT_CONNECTED)) { hotspotState = HOTSPOT_STATE_INITIALISE; if (!hotspotMmdvmHostIsConnected) { hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } } return 0; } static uint8_t setMode(const uint8_t *data, uint8_t length) { if (length < 1) { return 4; } if (hotspotModemState == data[0]) { return 0; } // Only supported mode are DMR, CWID, POCSAG and IDLE switch (data[0]) { case STATE_IDLE: case STATE_DMR: case STATE_CWID: case STATE_POCSAG: break; default: return 4; break; } hotspotModemState = data[0]; #if 0 // MMDVMHost seems to send setMode commands longer than 1 byte. This seems wrong according to the spec, so we ignore those. if (data[0] == STATE_IDLE || (length == 1 && data[0] == STATE_DMR) || data[0] != STATE_POCSAG) { hotspotModemState = data[0]; } #endif #if 0 // MMDVHost on the PC seems to send mode DMR when the transmitter should be turned on and IDLE when it should be turned off. switch(hotspotModemState) { case STATE_IDLE: //enableTransmission(false); break; case STATE_DMR: //enableTransmission(true); break; default: break; } #endif if (!hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } return 0; } static void getVersion(void) { char buffer[80]; uint8_t buf[128]; uint8_t count = 0; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_GET_VERSION; buf[3] = PROTOCOL_VERSION; count = 4; snprintf(buffer, sizeof(buffer), "%s (Radio:%s, Mode:%s)", HARDWARE, #if defined(PLATFORM_GD77) "GD-77" #elif defined(PLATFORM_GD77S) "GD-77S" #elif defined(PLATFORM_DM1801) "DM-1801" #elif defined(PLATFORM_DM1801A) "DM-1801A" #elif defined(PLATFORM_RD5R) "RD-5R" #elif defined(PLATFORM_MD9600) "MD-9600" #elif defined(PLATFORM_MDUV380) "MD-UV380" #elif defined(PLATFORM_MD380) "MD-380" #elif defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) "DM-1701" #else "Unknown" #endif ,(nonVolatileSettings.hotspotType == HOTSPOT_TYPE_MMDVM ? "MMDVM" : "BlueDV")); for (uint8_t i = 0; buffer[i] != 0x00; i++, count++) { buf[count] = buffer[i]; } buf[1] = count; if (!hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } enqueueUSBData(buf, buf[1]); } static uint8_t handleDMRShortLC(const uint8_t *data, uint8_t length) { //// uint8_t LCBuf[5]; //// DMRShortLC_decode((uint8_t *) com_requestbuffer + 3,LCBuf); if (!hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } return 0; } static uint8_t setQSOInfo(const uint8_t *data, uint8_t length) { if (length < (MMDVM_HEADER_LENGTH + 1)) { return 4; } if (data[3] == 250) // IP info from MMDVMHost's CAST display driver { char buf[26]; // MMDVMHost use an array of 25 char *pBuf = buf; char *pIface; memcpy(&buf, (char *)(data + MMDVM_HEADER_LENGTH), (length - MMDVM_HEADER_LENGTH)); buf[(length - MMDVM_HEADER_LENGTH)] = 0; // Get rid of the interface name as it could be too large to fit in the screen. if ((pIface = strchr(pBuf, ':')) != NULL) { pBuf = pIface + 1; } snprintf(hotspotMmdvmQSOInfoIP, 22, "%s", pBuf); uiHotspotUpdateScreen(hotspotCurrentRxCommandState); return 0; } else if ((data[3] != STATE_DMR) && (data[3] != STATE_POCSAG)) // We just want DMR and POCSAG QSO info { return 4; } if (data[3] == STATE_DMR) { if (length != 47) // Check for QSO Info frame length { return 5; } // Source and destination are both fitted in 20 arrays, in MMDVMHost // We just store use and store source info char QSOInfo[21]; // QSO infos are array[20] char source[21]; char *p; uint8_t len; memcpy(&source, (char *)(data + 5), 20); source[20] = 0; memset(&QSOInfo, 0, sizeof(QSOInfo)); sprintf(QSOInfo, "%s", chomp(source)); len = strlen(QSOInfo); // Keep the callsign only. if ((p = strchr(QSOInfo, ' ')) != NULL) { *p = 0; // zeroing p++; for (uint8_t i = 0; i < (len - (p - QSOInfo)); i++) { *(p + i) = 0; } len = strlen(QSOInfo); } // Non empty string, check if it's not numerical (TG/PC), as it will be ignored if (len > 0) { bool onlyDigits = true; for (uint8_t i = 0; i < len; i++) { if (isalpha((int)QSOInfo[i])) { onlyDigits = false; break; } } if (onlyDigits) { overriddenLCAvailable = false; return 0; } // Build fake TA (2 blocks max) memset(&overriddenLCTA, 0, sizeof(overriddenLCTA)); overriddenLCTA[0] = 0x04; overriddenLCTA[2] = (0x01 << 6) | (len << 1); overriddenLCTA[9] = 0x05; p = QSOInfo; for (uint8_t i = 0; i < 2; i++) // 2 blocks only, that enough for store a callsign { memcpy(&overriddenLCTA[(i * 9) + ((i == 0) ? 3 : 2)], p, ((i == 0) ? 6 : 7)); p += ((i == 0) ? 6 : 7); } overriddenBlocksTA = 0x03; // 2 blocks are now available } overriddenLCAvailable = (len > 0); } else if (data[3] == STATE_POCSAG) { if ((length - 11) > (17 + 4)) { for (int8_t i = 0; i < (((length - 11) / (17 + 4)) - 1); i++) { sendACK(data[2]); } } } return 0; } #if 0 static uint8_t handlePOCSAG(const uint8_t *data, uint8_t length) { return 0; } #endif void handleHotspotRequest(void) { mmdvmHostLastActiveTime = ticksGetMillis(); // MMDVMHost sign of life. // Rebuild the MMDVMHost frame stored in the USB (circular) buffer uint8_t currentFrame[256]; // Absolute max frame length that MMDVMHost can send (+1) uint8_t frameLength = (com_requestbuffer[comRecvMMDVMIndexOut++] - 1); // Remove our data block length header // Make sure the index stays within limits. if (comRecvMMDVMIndexOut >= COM_REQUESTBUFFER_SIZE) { comRecvMMDVMIndexOut = 0; } // Extract the MMDVMHost frame for (uint8_t i = 0; i < frameLength; i++) { currentFrame[i] = com_requestbuffer[comRecvMMDVMIndexOut++]; // Make sure the index stays within limits. if (comRecvMMDVMIndexOut >= COM_REQUESTBUFFER_SIZE) { comRecvMMDVMIndexOut = 0; } } // Make sure the index stays within limits. if (comRecvMMDVMIndexOut >= COM_REQUESTBUFFER_SIZE) { comRecvMMDVMIndexOut = 0; } // Decrement the frame counter comRecvMMDVMFrameCount--; // Something went wrong, resync to the RX buffer if (comRecvMMDVMFrameCount < 0) { comRecvMMDVMIndexIn = comRecvMMDVMIndexOut = 0; // Resync, it may skip few frames. comRecvMMDVMFrameCount = 0; } // Handle the frame, if valid. if (currentFrame[0] == MMDVM_FRAME_START) { uint8_t err = 2; switch(currentFrame[2]) { case MMDVM_GET_STATUS: getStatus(); break; case MMDVM_GET_VERSION: getVersion(); break; case MMDVM_SET_CONFIG: err = setConfig(currentFrame + 3, frameLength - 3); if (err == 0) { sendACK(currentFrame[2]); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } else { sendNAK(currentFrame[2], err); } break; case MMDVM_SET_MODE: { MMDVM_STATE prevState = hotspotModemState; err = setMode(currentFrame + 3, frameLength - 3); if (((prevState == STATE_POCSAG) && (hotspotModemState != STATE_POCSAG)) || ((prevState != STATE_POCSAG) && (hotspotModemState == STATE_POCSAG))) { uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); // refresh screen } if (err == 0) { sendACK(currentFrame[2]); } else { sendNAK(currentFrame[2], err); } } break; case MMDVM_SET_FREQ: err = setFreq(currentFrame + 3, frameLength - 3); if (err == 0) { sendACK(currentFrame[2]); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } else { sendNAK(currentFrame[2], err); } break; case MMDVM_SEND_CWID: err = 5; if (hotspotModemState == STATE_IDLE) { err = handleCWID(currentFrame + 3, frameLength - 3); } if (err == 0) { hotspotCwKeying = true; ticksTimerStart(&cwNextPeriodTimer, hotspotTxDelay); sendACK(currentFrame[2]); } else { sendNAK(currentFrame[2], err); } break; case MMDVM_DMR_DATA2: // It seems BlueDV under Windows forget to set correct mode // when it connect a TG with an already running QSO. So we force // the modemState and init the HS. if ((nonVolatileSettings.hotspotType == HOTSPOT_TYPE_BLUEDV) && (hotspotModemState == STATE_IDLE)) { hotspotModemState = STATE_DMR; } err = hotspotModeReceiveNetFrame(currentFrame, 2); if (err == 0) { sendACK(currentFrame[2]); } else { sendNAK(currentFrame[2], err); } break; case MMDVM_DMR_START: // Only for duplex sendACK(currentFrame[2]); break; case MMDVM_DMR_SHORTLC: err = handleDMRShortLC(currentFrame, frameLength); if (err == 0) { sendACK(currentFrame[2]); } else { sendNAK(currentFrame[2], err); } break; case MMDVM_DMR_ABORT: // Only for duplex sendACK(currentFrame[2]); break; case MMDVM_DSTAR_HEADER: case MMDVM_DSTAR_DATA: case MMDVM_DSTAR_EOT: case MMDVM_YSF_DATA: case MMDVM_P25_HDR: case MMDVM_P25_LDU: case MMDVM_NXDN_DATA: case MMDVM_DMR_DATA1: case MMDVM_CAL_DATA: sendNAK(currentFrame[2], err); break; case MMDVM_POCSAG_DATA: if ((hotspotModemState == STATE_IDLE) || (hotspotModemState == STATE_POCSAG)) { //err = handlePOCSAG(currentFrame + 3, frameLength - 3); err = 0; } if (err == 0) { sendACK(currentFrame[2]); // We don't send pages, but POCSAG can be enabled in Pi-Star } else { sendNAK(currentFrame[2], err); } break; case MMDVM_TRANSPARENT: // Do nothing, stay silent break; case MMDVM_QSO_INFO: err = setQSOInfo(currentFrame, frameLength); if ((err == 0) || (err == 4) /* non DMR mode, ignored */) { sendACK(currentFrame[2]); } else { sendNAK(currentFrame[2], err); } break; default: sendNAK(currentFrame[2], 1); break; } } else { // Invalid MMDVM header byte: resync (it may skip few frames). comRecvMMDVMIndexIn = comRecvMMDVMIndexOut = 0; comRecvMMDVMFrameCount = 0; } if ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) || (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA_UPDATE)) { uiHotspotUpdateScreen(hotspotCurrentRxCommandState); uiDataGlobal.displayQSOState = QSO_DISPLAY_IDLE; } if (hotspotCwKeying) { if (!trxTransmissionEnabled) { // Start TX CWID, prepare for ANALOG if (trxGetMode() != RADIO_MODE_ANALOG) { trxSetModeAndBandwidth(RADIO_MODE_ANALOG, false); trxSetTxCSS(CODEPLUG_CSS_TONE_NONE); } HRC6000ClearIsWakingState(); trxSetTone1(0); trxEnableTransmission(); #if defined(CPU_MK22FN512VLL12) trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_TONE1); audioAmpEnable(AUDIO_AMP_CHANNEL_RF); GPIO_PinWrite(GPIO_RX_audio_mux, Pin_RX_audio_mux, 1); #elif defined(PLATFORM_MD9600) // As the HRC6000 tone system is made, we can't set more than // one trxSetTone1() call without a trxDTMFoff() in the middle, as some // configuration values need to be restored each time. // So, grabbing the original values (after the first trxSetTone1() call permits // us to restore these settings after the CW ID beaconing is done. // // Get the previous Tone1 config (collected by HRC6000SendTone(). HRC6000GetTone1Config(&tone1Config); #endif uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } // CWID has been TXed, restore DIGITAL if (hotspotCwpoLen == 0) { trxDisableTransmission(); if (trxTransmissionEnabled) { trxTransmissionEnabled = false; trxIsTransmitting = false; PTTToggledDown = false; if (trxGetMode() == RADIO_MODE_ANALOG) { trxSetModeAndBandwidth(RADIO_MODE_DIGITAL, false); #if defined(CPU_MK22FN512VLL12) trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC); audioAmpDisable(AUDIO_AMP_CHANNEL_RF); #elif defined(PLATFORM_MD9600) // Restore original Tone1 config, otherwise DIGITAL won't work until power-cycling. HRC6000SetTone1Config(&tone1Config); trxDTMFoff(true); // Apply restored Tone1 config. #endif } } hotspotCwKeying = false; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } } } // Queue system is a single byte header containing the length of the item, followed by the data // if the block won't fit in the space between the current write location and the end of the buffer, // a zero is written to the length for that block and the data and its length byte is put at the beginning of the buffer void enqueueUSBData(uint8_t *data, uint8_t length) { if ((usbComSendBufWritePosition + (length + 1)) > (COM_BUFFER_SIZE - 1)) { usbComSendBuf[usbComSendBufWritePosition] = 0xFF; // flag that the data block won't fit and will be put at the start of the buffer usbComSendBufWritePosition = 0; } usbComSendBuf[usbComSendBufWritePosition] = length; memcpy((uint8_t *)&usbComSendBuf[usbComSendBufWritePosition + 1], data, length); usbComSendBufWritePosition += (length + 1); usbComSendBufCount++; if (length <= 1) { // Blah } } void processUSBDataQueue(void) { if (usbComSendBufCount > 0) { if (usbComSendBuf[usbComSendBufReadPosition] == 0xFF) // End marker { usbComSendBuf[usbComSendBufReadPosition] = 0; usbComSendBufReadPosition = 0; } uint8_t len = usbComSendBuf[usbComSendBufReadPosition] + 1; if (len < (3 + 1)) // the shortest MMDVM frame length (3 = DMRLost) { usbComSendBufCount--; } else { #if defined(STM32F405xx) uint8_t status = CDC_Transmit_FS((uint8_t *)&usbComSendBuf[usbComSendBufReadPosition + 1], usbComSendBuf[usbComSendBufReadPosition]); if (status == USBD_OK) #else usb_status_t status = USB_DeviceCdcAcmSend(s_cdcVcom.cdcAcmHandle, USB_CDC_VCOM_BULK_IN_ENDPOINT, &usbComSendBuf[usbComSendBufReadPosition + 1], usbComSendBuf[usbComSendBufReadPosition]); if (status == kStatus_USB_Success) #endif { usbComSendBufReadPosition += len; if (usbComSendBufReadPosition >= (COM_BUFFER_SIZE - 1)) // reaching the end of the buffer { usbComSendBufReadPosition = 0; } usbComSendBufCount--; } else { // USB Send Fail } } } } static void swapWithFakeTA(uint8_t *lc) { if ((lc[0] >= DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER) && (lc[0] < DMR_EMBEDDED_DATA_TALKER_ALIAS_BLOCK2)) { uint8_t blockID = lc[0] - 4; if ((overriddenBlocksTA & (1 << blockID)) != 0) { // Clear the bit as it was consumed overriddenBlocksTA &= ~(1 << blockID); memcpy(lc, overriddenLCTA + (blockID * 9), 9); } } } static void setRSSIToFrame(uint8_t *frameData) { frameData[DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH] = 0; frameData[DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 1] = radioDevices[RADIO_DEVICE_PRIMARY].trxRxSignal; } static bool hotspotSendVoiceFrame(volatile const uint8_t *receivedDMRDataAndAudio) { if ((hotspotRxedDMR_LC.srcId == 0) || (hotspotRxedDMR_LC.dstId == 0)) { return false; } uint8_t frameData[DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2] = {MMDVM_FRAME_START, (DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2), MMDVM_DMR_DATA2}; uint8_t embData[DMR_FRAME_LENGTH_BYTES]; uint8_t sequenceNumber = receivedDMRDataAndAudio[AMBE_AUDIO_LENGTH + LC_DATA_LENGTH + 1] - 1; // copy the audio sections memcpy(frameData + MMDVM_HEADER_LENGTH, (uint8_t *)receivedDMRDataAndAudio + LC_DATA_LENGTH, 14); memcpy(frameData + MMDVM_HEADER_LENGTH + EMBEDDED_DATA_OFFSET + 6, (uint8_t *)receivedDMRDataAndAudio + LC_DATA_LENGTH + EMBEDDED_DATA_OFFSET, 14); if (sequenceNumber == 0) { frameData[3] = MMDVM_VOICE_SYNC_PATTERN;// sequence 0 for (uint8_t i = 0; i < 7; i++) { frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] = (frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] & ~SYNC_MASK[i]) | MS_SOURCED_AUDIO_SYNC[i]; } } else { frameData[3] = sequenceNumber; embeddedDataGetData(sequenceNumber, embData); for (uint8_t i = 0; i < 7; i++) { frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] = (frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] & ~DMR_AUDIO_SEQ_MASK[i]) | DMR_AUDIO_SEQ_SYNC[sequenceNumber][i]; frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] = (frameData[i + EMBEDDED_DATA_OFFSET + MMDVM_HEADER_LENGTH] & ~DMR_EMBED_SEQ_MASK[i]) | embData[i + EMBEDDED_DATA_OFFSET]; } } // Add RSSI into frame setRSSIToFrame(frameData); enqueueUSBData(frameData, frameData[1]); return true; } static bool sendVoiceHeaderLC_Frame(volatile const uint8_t *receivedDMRDataAndAudio) { uint8_t frameData[DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2] = {MMDVM_FRAME_START, (DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2), MMDVM_DMR_DATA2, DMR_SYNC_DATA | DT_VOICE_LC_HEADER}; DMRLC_t lc; hotspotRxedDMR_LC.srcId = 0; hotspotRxedDMR_LC.dstId = 0; memset(&lc, 0, sizeof(DMRLC_t));// clear automatic variable lc.srcId = (receivedDMRDataAndAudio[6] << 16) + (receivedDMRDataAndAudio[7] << 8) + (receivedDMRDataAndAudio[8] << 0); lc.dstId = (receivedDMRDataAndAudio[3] << 16) + (receivedDMRDataAndAudio[4] << 8) + (receivedDMRDataAndAudio[5] << 0); lc.FLCO = receivedDMRDataAndAudio[0];// Private or group call if ((lc.srcId == 0) || (lc.dstId == 0)) { return false; } // Encode the src and dst Ids etc if (!DMRFullLC_encode(&lc, frameData + MMDVM_HEADER_LENGTH, DT_VOICE_LC_HEADER)) // Encode the src and dst Ids etc { return false; } memcpy(&hotspotRxedDMR_LC, &lc, sizeof(DMRLC_t)); embeddedDataSetLC(&lc); for (uint8_t i = 0; i < 8; i++) { frameData[i + LC_DATA_LENGTH + MMDVM_HEADER_LENGTH] = (frameData[i + LC_DATA_LENGTH + MMDVM_HEADER_LENGTH] & ~LC_SYNC_MASK_FULL[i]) | VOICE_LC_SYNC_FULL[i]; } // Add RSSI into frame setRSSIToFrame(frameData); enqueueUSBData(frameData, frameData[1]); return true; } static void sendTerminator_LC_Frame(volatile const uint8_t *receivedDMRDataAndAudio) { uint8_t frameData[DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2] = {MMDVM_FRAME_START, (DMR_FRAME_LENGTH_BYTES + MMDVM_HEADER_LENGTH + 2), MMDVM_DMR_DATA2, DMR_SYNC_DATA | DT_TERMINATOR_WITH_LC}; DMRLC_t lc; memset(&lc, 0, sizeof(DMRLC_t)); lc.srcId = hotspotRxedDMR_LC.srcId; lc.dstId = hotspotRxedDMR_LC.dstId; // Encode the src and dst Ids etc if (!DMRFullLC_encode(&lc, frameData + MMDVM_HEADER_LENGTH, DT_TERMINATOR_WITH_LC)) { return; } for (uint8_t i = 0; i < 8; i++) { frameData[i + LC_DATA_LENGTH + MMDVM_HEADER_LENGTH] = (frameData[i + LC_DATA_LENGTH + MMDVM_HEADER_LENGTH] & ~LC_SYNC_MASK_FULL[i]) | TERMINATOR_LC_SYNC_FULL[i]; } // Add RSSI into frame setRSSIToFrame(frameData); enqueueUSBData(frameData, frameData[1]); } void hotspotRxFrameHandler(uint8_t* frameBuf) // It's called by and ISR in HRC-6000 code. { memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufWriteIdx], frameBuf, AMBE_AUDIO_LENGTH + LC_DATA_LENGTH + 2);// 27 audio + 0x0c header + 2 hotspot signalling bytes rfFrameBufCount++; rfFrameBufWriteIdx = ((rfFrameBufWriteIdx + 1) % HOTSPOT_BUFFER_COUNT); } static bool getEmbeddedData(volatile const uint8_t *comBuffer) { uint8_t lcss; uint8_t DMREMB[2]; // the following is used for fake TA static uint32_t oldTrxDMRID = 0; static uint8_t rawDataCount = 0; static uint8_t fakeTABlockID = DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER; static bool sendFakeTA = true; DMREMB[0] = (comBuffer[MMDVM_HEADER_LENGTH + 13] << 4) & 0xF0; DMREMB[0] |= (comBuffer[MMDVM_HEADER_LENGTH + 14] >> 4) & 0x0F; DMREMB[1] = (comBuffer[MMDVM_HEADER_LENGTH + 18] << 4) & 0xF0; DMREMB[1] |= (comBuffer[MMDVM_HEADER_LENGTH + 19] >> 4) & 0x0F; // m_colorCode = (DMREMB[0] >> 4) & 0x0F; // m_PI = (DMREMB[0] & 0x08) == 0x08; lcss = (DMREMB[0] >> 1) & 0x03; if (startedEmbeddedSearch == false) { embeddedDataBuffersInt(); startedEmbeddedSearch = true; } if (embeddedDataAddData((uint8_t *)comBuffer + MMDVM_HEADER_LENGTH, lcss)) { bool res = embeddedDataGetRawData(hotspotTxLC); if (res) { if (overriddenLCAvailable) // We can send fake talker aliases. { if (trxDMRID != oldTrxDMRID) { // reset counters rawDataCount = 0; sendFakeTA = true; fakeTABlockID = DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER; oldTrxDMRID = trxDMRID; } if (sendFakeTA) { if ((hotspotTxLC[0] >= DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER) && (hotspotTxLC[0] <= DMR_EMBEDDED_DATA_TALKER_ALIAS_BLOCK3)) { sendFakeTA = false; rawDataCount = 0; fakeTABlockID = DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER; overriddenLCAvailable = false; } else { rawDataCount++; if (rawDataCount > 4) { hotspotTxLC[0] = fakeTABlockID; swapWithFakeTA(&hotspotTxLC[0]); // Update LH with fake TA lastHeardListUpdate(hotspotTxLC, true); fakeTABlockID++; // We just send 2 fake blocks, as it only contains callsign if (fakeTABlockID == DMR_EMBEDDED_DATA_TALKER_ALIAS_BLOCK2) { rawDataCount = 0; sendFakeTA = false; fakeTABlockID = DMR_EMBEDDED_DATA_TALKER_ALIAS_HEADER; oldTrxDMRID = trxDMRID; overriddenLCAvailable = false; } } } } } else { lastHeardListUpdate(hotspotTxLC, true); } } startedEmbeddedSearch = false; } return false; } static void storeNetFrame(volatile const uint8_t *comBuffer) { bool foundEmbedded; if (memcmp((uint8_t *)&comBuffer[18], END_FRAME_PATTERN, 6) == 0) { return; } if (memcmp((uint8_t *)&comBuffer[18], START_FRAME_PATTERN, 6) == 0) { return; } foundEmbedded = getEmbeddedData(comBuffer); if ((foundEmbedded || (nonVolatileSettings.hotspotType == HOTSPOT_TYPE_BLUEDV)) && (hotspotTxLC[0] == TG_CALL_FLAG || hotspotTxLC[0] == PC_CALL_FLAG) && (hotspotState != HOTSPOT_STATE_TX_START_BUFFERING && hotspotState != HOTSPOT_STATE_TRANSMITTING)) { timeoutCounter = TX_BUFFERING_TIMEOUT;// set buffering timeout hotspotState = HOTSPOT_STATE_TX_START_BUFFERING; } if (hotspotState == HOTSPOT_STATE_TRANSMITTING || hotspotState == HOTSPOT_STATE_TX_SHUTDOWN || hotspotState == HOTSPOT_STATE_TX_START_BUFFERING) { if (wavbuffer_count >= HOTSPOT_BUFFER_COUNT) { // Buffer overflow } taskENTER_CRITICAL(); memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx][LC_DATA_LENGTH], (uint8_t *)comBuffer + 4, 13);//copy the first 13, whole bytes of audio audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx][LC_DATA_LENGTH + 13] = (comBuffer[17] & 0xF0) | (comBuffer[23] & 0x0F); memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx][LC_DATA_LENGTH + 14], (uint8_t *)&comBuffer[24], 13);//copy the last 13, whole bytes of audio memcpy((uint8_t *)&audioAndHotspotDataBuffer.hotspotBuffer[wavbuffer_write_idx], hotspotTxLC, 9);// copy the current LC into the data (mainly for use with the embedded data); wavbuffer_count++; wavbuffer_write_idx = ((wavbuffer_write_idx + 1) % HOTSPOT_BUFFER_COUNT); taskEXIT_CRITICAL(); } } static uint8_t hotspotModeReceiveNetFrame(const uint8_t *comBuffer, uint8_t timeSlot) { DMRLC_t lc; if (!hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } netRXDataTimer = RX_NET_FRAME_TIMEOUT; lc.srcId = 0;// zero these values as they are checked later in the function, but only updated if the data type is DT_VOICE_LC_HEADER lc.dstId = 0; voiceLCHeaderDecode((uint8_t *)comBuffer + MMDVM_HEADER_LENGTH, DT_VOICE_LC_HEADER, &lc);// Need to decode the frame to get the source and destination // update the src and destination ID's if valid if ((lc.srcId != 0) && (lc.dstId != 0)) { trxTalkGroupOrPcId = lc.dstId | (lc.FLCO << 24); trxDMRID = lc.srcId; if (hotspotState != HOTSPOT_STATE_TX_START_BUFFERING) { memcpy(hotspotTxLC, lc.rawData, 9);//Hotspot uses LC Data bytes rather than the src and dst ID's for the embed data lastHeardListUpdate(hotspotTxLC, true); // the Src and Dst Id's have been sent, and we are in RX mode then an incoming Net normally arrives next timeoutCounter = TX_BUFFERING_TIMEOUT; hotspotState = HOTSPOT_STATE_TX_START_BUFFERING; } } else { storeNetFrame(comBuffer); } return 0; } #if defined(MMDVM_SEND_DEBUG) #warning MMDVM_SEND_DEBUG is defined void mmdvmSendDebug1(const char *text) { uint8_t buf[130]; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_DEBUG1; uint8_t count = 3; for (uint8_t i = 0; text[i] != '\0'; i++, count++) buf[count] = text[i]; buf[1] = count; enqueueUSBData(buf, buf[1]); } void mmdvmSendDebug2(const char *text, int16_t n1) { uint8_t buf[130]; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_DEBUG2; uint8_t count = 3; for (uint8_t i = 0; text[i] != '\0'; i++, count++) buf[count] = text[i]; buf[count++] = (n1 >> 8) & 0xFF; buf[count++] = (n1 >> 0) & 0xFF; buf[1] = count; enqueueUSBData(buf, buf[1]); } void mmdvmSendDebug3(const char *text, int16_t n1, int16_t n2) { uint8_t buf[130]; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_DEBUG3; uint8_t count = 3; for (uint8_t i = 0; text[i] != '\0'; i++, count++) buf[count] = text[i]; buf[count++] = (n1 >> 8) & 0xFF; buf[count++] = (n1 >> 0) & 0xFF; buf[count++] = (n2 >> 8) & 0xFF; buf[count++] = (n2 >> 0) & 0xFF; buf[1] = count; enqueueUSBData(buf, buf[1]); } void mmdvmSendDebug4(const char *text, int16_t n1, int16_t n2, int16_t n3) { uint8_t buf[130]; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_DEBUG4; uint8_t count = 3; for (uint8_t i = 0; text[i] != '\0'; i++, count++) buf[count] = text[i]; buf[count++] = (n1 >> 8) & 0xFF; buf[count++] = (n1 >> 0) & 0xFF; buf[count++] = (n2 >> 8) & 0xFF; buf[count++] = (n2 >> 0) & 0xFF; buf[count++] = (n3 >> 8) & 0xFF; buf[count++] = (n3 >> 0) & 0xFF; buf[1] = count; enqueueUSBData(buf, buf[1]); } void mmdvmSendDebug5(const char *text, int16_t n1, int16_t n2, int16_t n3, int16_t n4) { uint8_t buf[130]; buf[0] = MMDVM_FRAME_START; buf[1] = 0; buf[2] = MMDVM_DEBUG5; uint8_t count = 3; for (uint8_t i = 0; text[i] != '\0'; i++, count++) buf[count] = text[i]; buf[count++] = (n1 >> 8) & 0xFF; buf[count++] = (n1 >> 0) & 0xFF; buf[count++] = (n2 >> 8) & 0xFF; buf[count++] = (n2 >> 0) & 0xFF; buf[count++] = (n3 >> 8) & 0xFF; buf[count++] = (n3 >> 0) & 0xFF; buf[count++] = (n4 >> 8) & 0xFF; buf[count++] = (n4 >> 0) & 0xFF; buf[1] = count; enqueueUSBData(buf, buf[1]); } #endif static void sendDMRLost(void) { uint8_t buf[3]; buf[0] = MMDVM_FRAME_START; buf[1] = 3; buf[2] = MMDVM_DMR_LOST2; enqueueUSBData(buf, buf[1]); } static void sendACK(uint8_t cmd) { uint8_t buf[4]; buf[0] = MMDVM_FRAME_START; buf[1] = 4; buf[2] = MMDVM_ACK; buf[3] = cmd; enqueueUSBData(buf, buf[1]); } static void sendNAK(uint8_t cmd, uint8_t err) { uint8_t buf[5]; buf[0] = MMDVM_FRAME_START; buf[1] = 5; buf[2] = MMDVM_NAK; buf[3] = cmd; buf[4] = err; enqueueUSBData(buf, buf[1]); } void hotspotStateMachine(void) { static uint32_t rxFrameTime = 0; switch(hotspotState) { case HOTSPOT_STATE_NOT_CONNECTED: // do nothing lastRxState = HOTSPOT_RX_UNKNOWN; // force immediate shutdown of Tx if we get here and the tx is on for some reason. if (trxTransmissionEnabled) { trxTransmissionEnabled = false; trxDisableTransmission(); } rfFrameBufCount = 0; if (hotspotMmdvmHostIsConnected) { hotspotState = HOTSPOT_STATE_INITIALISE; } else { if ((nonVolatileSettings.hotspotType == HOTSPOT_TYPE_MMDVM) && ((ticksGetMillis() - mmdvmHostLastActiveTime) > MMDVMHOST_TIMEOUT)) { wavbuffer_count = 0; hotspotExit(); break; } } break; case HOTSPOT_STATE_INITIALISE: wavbuffer_read_idx = 0; wavbuffer_write_idx = 0; wavbuffer_count = 0; rfFrameBufCount = 0; overriddenLCAvailable = false; hotspotState = HOTSPOT_STATE_RX_START; break; case HOTSPOT_STATE_RX_START: // force immediate shutdown of Tx if we get here and the tx is on for some reason. if (trxTransmissionEnabled) { trxTransmissionEnabled = false; trxDisableTransmission(); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } rxLCFrameSent = false; wavbuffer_read_idx = 0; wavbuffer_write_idx = 0; wavbuffer_count = 0; rxFrameTime = ticksGetMillis(); hotspotState = HOTSPOT_STATE_RX_PROCESS; break; case HOTSPOT_STATE_RX_PROCESS: if (hotspotMmdvmHostIsConnected) { // No activity from MMDVMHost if ((nonVolatileSettings.hotspotType == HOTSPOT_TYPE_MMDVM) && ((ticksGetMillis() - mmdvmHostLastActiveTime) > MMDVMHOST_TIMEOUT)) { hotspotMmdvmHostIsConnected = false; hotspotState = HOTSPOT_STATE_NOT_CONNECTED; rfFrameBufCount = 0; wavbuffer_count = 0; hotspotExit(); break; } } else { hotspotState = HOTSPOT_STATE_NOT_CONNECTED; rfFrameBufCount = 0; wavbuffer_count = 0; if (trxTransmissionEnabled) { trxTransmissionEnabled = false; trxDisableTransmission(); } uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); break; } if (rfFrameBufCount > 0) { // We have pending data in RF side, but don't process it when MMDVMHost // set the hotspot in POCSAG mode. Just trash it. if (hotspotModemState == STATE_POCSAG) { memset((void *)&audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx], 0, HOTSPOT_BUFFER_SIZE); } if (MMDVMHostRxState == MMDVMHOST_RX_READY) { uint8_t rx_command = audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx][AMBE_AUDIO_LENGTH + LC_DATA_LENGTH]; switch(rx_command) { case HOTSPOT_RX_IDLE: break; case HOTSPOT_RX_START: if (sendVoiceHeaderLC_Frame(audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx])) { rxLCFrameSent = true; uiHotspotUpdateScreen(rx_command); lastRxState = HOTSPOT_RX_START; rxFrameTime = ticksGetMillis(); } break; case HOTSPOT_RX_START_LATE: if (sendVoiceHeaderLC_Frame(audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx])) { rxLCFrameSent = true; uiHotspotUpdateScreen(rx_command); lastRxState = HOTSPOT_RX_START_LATE; rxFrameTime = ticksGetMillis(); } break; case HOTSPOT_RX_AUDIO_FRAME: if (rxLCFrameSent) { if (hotspotSendVoiceFrame(audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx])) { lastRxState = HOTSPOT_RX_AUDIO_FRAME; rxFrameTime = ticksGetMillis(); } } else { // Under some conditions, starting frames were missed, probably due to frequency instabilities. // This will pick the LC data from this voice frame, and send a voice frame header to MMDVMHost. if (sendVoiceHeaderLC_Frame(audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx])) { rxLCFrameSent = true; uiHotspotUpdateScreen(HOTSPOT_RX_START_LATE); lastRxState = HOTSPOT_RX_START_LATE; rxFrameTime = ticksGetMillis(); } } break; case HOTSPOT_RX_STOP: uiHotspotUpdateScreen(rx_command); if (rxLCFrameSent) { sendTerminator_LC_Frame(audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx]); } lastRxState = HOTSPOT_RX_STOP; hotspotState = HOTSPOT_STATE_RX_END; break; default: lastRxState = HOTSPOT_RX_UNKNOWN; break; } memset((void *)&audioAndHotspotDataBuffer.hotspotBuffer[rfFrameBufReadIdx], 0, HOTSPOT_BUFFER_SIZE); rfFrameBufReadIdx = ((rfFrameBufReadIdx + 1) % HOTSPOT_BUFFER_COUNT); if (rfFrameBufCount > 0) { rfFrameBufCount--; } } else { // Rx Error NAK hotspotState = HOTSPOT_STATE_RX_END; } } else { // Timeout: no RF data for too long if (((lastRxState == HOTSPOT_RX_AUDIO_FRAME) || (lastRxState == HOTSPOT_RX_START) || (lastRxState == HOTSPOT_RX_START_LATE)) && ((ticksGetMillis() - rxFrameTime) > 300)) // 300ms { sendDMRLost(); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); lastRxState = HOTSPOT_RX_STOP; hotspotState = HOTSPOT_STATE_RX_END; rfFrameBufCount = 0; return; } } break; case HOTSPOT_STATE_RX_END: hotspotState = HOTSPOT_STATE_RX_START; rxLCFrameSent = false; break; case HOTSPOT_STATE_TX_START_BUFFERING: // If MMDVMHost tells us to go back to idle. (receiving) if (hotspotModemState == STATE_IDLE) { //modemState = STATE_DMR; //wavbuffer_read_idx = 0; //wavbuffer_write_idx = 0; //wavbuffer_count = 0; rfFrameBufCount = 0; lastRxState = HOTSPOT_RX_IDLE; hotspotState = HOTSPOT_STATE_TX_SHUTDOWN; hotspotMmdvmHostIsConnected = false; trxTransmissionEnabled = false; trxDisableTransmission(); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } else { if (wavbuffer_count > TX_BUFFER_MIN_BEFORE_TRANSMISSION) { if (hotspotCwKeying == false) { HRC6000ClearIsWakingState(); hotspotState = HOTSPOT_STATE_TRANSMITTING; trxEnableTransmission(); uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } } else { // Buffering time has expired, put the modem in RX. if (--netRXDataTimer <= 0) { if (--timeoutCounter <= 0) { hotspotState = HOTSPOT_STATE_INITIALISE; } } } } break; case HOTSPOT_STATE_TRANSMITTING: // Stop transmitting when there is no data in the buffer or if MMDVMHost sends the idle command if (((wavbuffer_count == 0) && (--netRXDataTimer <= 0)) || (hotspotModemState == STATE_IDLE)) { hotspotState = HOTSPOT_STATE_TX_SHUTDOWN; txStopDelay = ((hotspotModemState == STATE_IDLE) ? TX_BUFFERING_TIMEOUT : (TX_BUFFERING_TIMEOUT * 2)); } break; case HOTSPOT_STATE_TX_SHUTDOWN: overriddenLCAvailable = false; if ((hotspotModemState != STATE_IDLE) && (txStopDelay > 0)) { txStopDelay--; // Some data appeared in the buffer while shutting down, restart buffering. if (wavbuffer_count > 0) { // restart timeoutCounter = TX_BUFFERING_TIMEOUT; hotspotState = HOTSPOT_STATE_TX_START_BUFFERING; } } else { txStopDelay = 0; // ensure its value is 0; if (trxIsTransmitting || ((hotspotModemState == STATE_IDLE) && trxTransmissionEnabled)) // MMDVMHost asked to go back to IDLE (mostly on shutdown) { trxTransmissionEnabled = false; trxDisableTransmission(); hotspotState = HOTSPOT_STATE_RX_START; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } } break; } } static uint8_t setFreq(const uint8_t *data, uint8_t length) { if (length < 9) { return 4; } // satellite frequencies banned frequency ranges const int BAN1_MIN = 14580000; const int BAN1_MAX = 14600000; const int BAN2_MIN = 43500000; const int BAN2_MAX = 43800000; uint32_t fRx, fTx; hotspotState = HOTSPOT_STATE_INITIALISE; if (!hotspotMmdvmHostIsConnected) { hotspotMmdvmHostIsConnected = true; uiHotspotUpdateScreen(HOTSPOT_RX_IDLE); } // Very old MMDVMHost, set full power if (length == 9) { rf_power = 255; } // Current MMDVMHost, set power from MMDVM.ini if (length >= 10) { rf_power = data[9];// 255 = max power } fRx = (data[1] << 0 | data[2] << 8 | data[3] << 16 | data[4] << 24) / 10; fTx = (data[5] << 0 | data[6] << 8 | data[7] << 16 | data[8] << 24) / 10; if ((fTx >= BAN1_MIN && fTx <= BAN1_MAX) || (fTx >= BAN2_MIN && fTx <= BAN2_MAX)) { return 4;// invalid frequency } if (trxCheckFrequencyInAmateurBand(fRx) && trxCheckFrequencyInAmateurBand(fTx)) { hotspotFreqRx = fRx; hotspotFreqTx = fTx; trxSetFrequency(hotspotFreqRx, hotspotFreqTx, DMR_MODE_DMO);// Override the default assumptions about DMR mode based on frequency } else { return 4;// invalid frequency } hotspotPowerLevel = nonVolatileSettings.txPowerLevel; // If the power level sent by MMDVMHost is 255 it means the user has left the setting at 100% and potentially does not realise that there is even a setting for this // As the GD-77 can't be run at full power, this power level will be ignored and instead the level specified for normal operation will be used. if (rf_power != 255) { hotspotSavedPowerLevel = nonVolatileSettings.txPowerLevel; if (rf_power < 50) { hotspotPowerLevel = rf_power / 12; } else { hotspotPowerLevel = (rf_power / 50) + 3; } trxSetPowerFromLevel(hotspotPowerLevel); } return 0; } static bool hasRXOverflow(void) { return ((HOTSPOT_BUFFER_SIZE - rfFrameBufCount) <= 0); } static bool hasTXOverflow(void) { return ((HOTSPOT_BUFFER_COUNT - wavbuffer_count) <= 0); } void hotspotInit(void) { hotspotMmdvmHostIsConnected = false; trxTalkGroupOrPcId = 0; hotspotCurrentRxCommandState = HOTSPOT_RX_UNKNOWN; hotspotState = HOTSPOT_STATE_NOT_CONNECTED; overriddenLCTA[0] = 0; overriddenBlocksTA = 0x0; overriddenLCAvailable = false; hotspotCwKeying = false; cwReset(); hotspotTxDelay = 0; memset(&hotspotRxedDMR_LC, 0, sizeof(DMRLC_t));// clear automatic variable rxLCFrameSent = false; // Clear RF buffers rfFrameBufCount = 0; rfFrameBufReadIdx = 0; rfFrameBufWriteIdx = 0; for (uint8_t i = 0; i < HOTSPOT_BUFFER_COUNT; i++) { memset((void *)&audioAndHotspotDataBuffer.hotspotBuffer[i], 0, HOTSPOT_BUFFER_SIZE); } // Clear USB TX buffers usbComSendBufWritePosition = 0; usbComSendBufReadPosition = 0; usbComSendBufCount = 0; memset((uint8_t *)&usbComSendBuf, 0, sizeof(usbComSendBuf)); trxSetModeAndBandwidth(RADIO_MODE_DIGITAL, false);// hotspot mode is for DMR i.e Digital mode if (hotspotFreqTx == 0) { hotspotFreqTx = 43000000; } if (hotspotFreqRx == 0) { hotspotFreqRx = 43000000; } MMDVMHostRxState = MMDVMHOST_RX_READY; // We have not sent anything to MMDVMHost, so it can't be busy yet. // Set CC, QRG and power, in case hotspot menu has left then re-enter. trxSetDMRColourCode(colorCode); if (trxCheckFrequencyInAmateurBand(hotspotFreqRx) && trxCheckFrequencyInAmateurBand(hotspotFreqTx)) { trxSetFrequency(hotspotFreqRx, hotspotFreqTx, DMR_MODE_DMO); } if (rf_power != 255) { if (rf_power < 50) { hotspotPowerLevel = rf_power / 12; } else { hotspotPowerLevel = (rf_power / 50) + 3; } trxSetPowerFromLevel(hotspotPowerLevel); } HRC6000ResetTimeSlotDetection(); mmdvmHostLastActiveTime = ticksGetMillis(); }