2709 lines
72 KiB
C
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();
|
|
}
|