FreeTRX/MDUV380_firmware/application/source/functions/hotspot.c
2026-07-06 08:45:33 +02:00

2709 lines
72 KiB
C

/*
* 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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#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();
}