/*
 * Copyright (C) 2019      Kai Ludwig, DG4KLU
 * Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
 *                         Colin, G4EML
 *                         Daniel Caujolle-Bert, F1RMB
 *
 *
 * 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/calibration.h"
#include "functions/ticks.h"
#include "hardware/HR-C6000.h"
#include "functions/settings.h"
#include "functions/trx.h"
#include "functions/rxPowerSaving.h"
#include "user_interface/menuSystem.h"
#include "user_interface/uiUtilities.h"
#include <FreeRTOS.h>
#include <string.h>


#if USE_DATASHEET_RANGES
const frequencyHardwareBand_t RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BANDS_TOTAL_NUM] =  {
													{
														.minFreq=13600000,
														.maxFreq=17400000
													},// VHF
#if !defined(PLATFORM_MD9600)
													{
														.minFreq=20000000,
														.maxFreq=26000000
													},// 220Mhz
#endif
													{
														.minFreq=40000000,
														.maxFreq=52000000
													}// UHF
};
#else
const frequencyHardwareBand_t RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BANDS_TOTAL_NUM] =  {
													{
														.calTableMinFreq = 13600000,
														.minFreq=12700000,
														.maxFreq=17800000
													},// VHF
#if !defined(PLATFORM_MD9600)
													{
														.calTableMinFreq = 13600000,
														.minFreq=19000000,
														.maxFreq=28200000
													},// 220Mhz
#endif
													{
														.calTableMinFreq = 40000000,
														.minFreq=38000000,
														.maxFreq=56400000
													}// UHF
};
#endif

#define TRX_SQUELCH_MAX    110
#define TRX_SQUELCH_HIST  12
#define TRX_SQUELCH_INC   5
const uint8_t TRX_NUM_CTCSS = 50U;

const uint16_t TRX_CTCSSTones[] = {
		670,  693,  719,  744,  770,  797,  825,  854,  885,  915,
		948,  974, 1000, 1035, 1072, 1109, 1148, 1188, 1230, 1273,
		1318, 1365, 1413, 1462, 1514, 1567, 1598, 1622, 1655, 1679,
		1713, 1738, 1773, 1799, 1835, 1862, 1899, 1928, 1966, 1995,
		2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503, 2541
};

const uint8_t TRX_NUM_DCS = 83U;

const uint16_t TRX_DCSCodes[] = {
		0x023, 0x025, 0x026, 0x031, 0x032, 0x043, 0x047, 0x051, 0x054, 0x065, 0x071, 0x072, 0x073, 0x074,
		0x114, 0x115, 0x116, 0x125, 0x131, 0x132, 0x134, 0x143, 0x152, 0x155, 0x156, 0x162, 0x165, 0x172, 0x174,
		0x205, 0x223, 0x226, 0x243, 0x244, 0x245, 0x251, 0x261, 0x263, 0x265, 0x271,
		0x306, 0x311, 0x315, 0x331, 0x343, 0x345, 0x351, 0x364, 0x365, 0x371,
		0x411, 0x412, 0x413, 0x423, 0x431, 0x432, 0x445, 0x464, 0x465, 0x466,
		0x503, 0x506, 0x516, 0x532, 0x546, 0x565,
		0x606, 0x612, 0x624, 0x627, 0x631, 0x632, 0x654, 0x662, 0x664,
		0x703, 0x712, 0x723, 0x731, 0x732, 0x734, 0x743, 0x754
};


frequencyBand_t USER_FREQUENCY_BANDS[RADIO_BANDS_TOTAL_NUM] =  {
													{
														.minFreq=14400000,
														.maxFreq=14800000
													},// VHF
#if !defined(PLATFORM_MD9600)
													{
														.minFreq=22200000,
														.maxFreq=22500000
													},// 220Mhz
#endif
													{
														.minFreq=42000000,
														.maxFreq=45000000
													}// UHF
};

const frequencyBand_t DEFAULT_USER_FREQUENCY_BANDS[RADIO_BANDS_TOTAL_NUM] =  {
													{
														.minFreq=14400000,
														.maxFreq=14800000
													},// VHF
#if !defined(PLATFORM_MD9600)
													{
														.minFreq=22200000,
														.maxFreq=22500000
													},// 220Mhz
#endif
													{
														.minFreq=42000000,
														.maxFreq=45000000
													}// UHF
};

//const uint32_t RSSI_NOISE_SAMPLE_PERIOD_PIT = 25U;// 25 milliseconds

static volatile ticksTimer_t trxNextRssiNoiseSampleTimer = { 0, 0 };
static volatile ticksTimer_t trxNextSquelchCheckingTimer = { 0, 0 };

static uint8_t trxCssMeasureCount = 0;
static uint8_t currentCC = 1;

#define CTCSS_HOLD_DELAY            6
#define SQUELCH_CLOSE_DELAY         1

#define SIZE_OF_FILL_BUFFER       128 // Tested by Jose EA5SW, and it's needed, 64 makes the beeps and audio to disappear.

static bool rxCSSactive = false;
//static uint8_t rxCSSTriggerCount = 0;
static int trxCurrentDMRTimeSlot;


volatile uint8_t trxTxVox;
volatile uint8_t trxTxMic;

volatile uint16_t txDACDrivePower;
volatile uint16_t UHFRxTuningVolts;
volatile uint16_t VHFRxTuningVolts;
volatile uint8_t analogIGain;
volatile uint8_t analogQGain;
volatile uint8_t digitalIGain;
volatile uint8_t digitalQGain;
volatile int8_t Mod2Offset;

volatile bool trxIsTransmittingDMR;
volatile uint32_t trxDMRstartTime;

static uint8_t voice_gain_tx = 0x31; // default voice_gain_tx fro calibration, needs to be declared here in case calibration:OFF

volatile bool trxTransmissionEnabled = false;
volatile bool trxIsTransmitting = false;
volatile bool txPAEnabled = false;

uint32_t trxTalkGroupOrPcId = 9;// Set to local TG just in case there is some problem with it not being loaded
uint32_t trxDMRID = 0;// Set ID to 0. Not sure if its valid. This value needs to be loaded from the codeplug.


// DTMF Order: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, *, #
const int trxDTMFfreq1[] = { 1336, 1209, 1336, 1477, 1209, 1336, 1477, 1209, 1336, 1477, 1633, 1633, 1633, 1633, 1209, 1477 };
const int trxDTMFfreq2[] = {  941,  697,  697,  697,  770,  770,  770,  852,  852,  852,  697,  770,  852,  941,  941,  941 };

calibrationPowerValues_t trxPowerSettings;

static bool powerUpDownState = true;

static uint8_t trxAnalogFilterLevel = ANALOG_FILTER_CSS;

volatile bool trxDMRSynchronisedRSSIReadPending = false;

static uint16_t convertCSSNative2BinaryCodedOctal(uint16_t nativeCSS);
static void trxUpdateC6000Calibration(void);
static void trxUpdateRadioCalibration(void);

//
// =================================================================
//

uint8_t trxGetAnalogFilterLevel(void)
{
	return trxAnalogFilterLevel;
}

void trxSetAnalogFilterLevel(uint8_t newFilterLevel)
{
	trxAnalogFilterLevel = newFilterLevel;
}

int trxGetMode(void)
{
	return currentRadioDevice->currentMode;
}

bool trxGetBandwidthIs25kHz(void)
{
	return currentRadioDevice->currentBandWidthIs25kHz;
}

void trxSetModeAndBandwidth(int mode, bool bandwidthIs25kHz)
{
	if (rxPowerSavingIsRxOn() == false)
	{
		rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
	}

	currentRadioDevice->digitalSignalReceived = false;
	currentRadioDevice->analogSignalReceived = false;
	ticksTimerStart((ticksTimer_t *)&trxNextRssiNoiseSampleTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
	ticksTimerStart((ticksTimer_t *)&trxNextSquelchCheckingTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
	trxCssMeasureCount = 0;

	// DMR (digital) is disabled, hence force it
	// to RADIO_MODE_ANALOG (has we could currently be in RADIO_MODE_NONE (CPS))
	if (uiDataGlobal.dmrDisabled && (mode == RADIO_MODE_DIGITAL))
	{
		mode = RADIO_MODE_ANALOG;
	}

	if ((mode != currentRadioDevice->currentMode) || (bandwidthIs25kHz != currentRadioDevice->currentBandWidthIs25kHz))
	{
		currentRadioDevice->currentMode = mode;

		taskENTER_CRITICAL();
		switch(mode)
		{
			case RADIO_MODE_NONE:// not truly off
				soundTerminateSound();
				HRC6000TerminateDigital();
				radioSetMode(RADIO_MODE_NONE);
				trxUpdateC6000Calibration();
				trxUpdateRadioCalibration();
				break;
			case RADIO_MODE_ANALOG:
				currentRadioDevice->currentBandWidthIs25kHz = bandwidthIs25kHz;
				radioSetAudioPath(true);							//select the FM audio Path
				HRC6000TerminateDigital();
				radioSetMode(RADIO_MODE_ANALOG);
				trxUpdateC6000Calibration();
				radioSetIF(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND], currentRadioDevice->currentBandWidthIs25kHz);
				trxUpdateRadioCalibration();
				break;
			case RADIO_MODE_DIGITAL:
				currentRadioDevice->currentBandWidthIs25kHz = BANDWIDTH_12P5KHZ;// DMR bandwidth is 12.5kHz
				radioSetMode(RADIO_MODE_DIGITAL);
				trxUpdateC6000Calibration();
				radioSetIF(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND], currentRadioDevice->currentBandWidthIs25kHz);
				trxUpdateRadioCalibration();
				HRC6000InitDigital();
				break;
		}
		taskEXIT_CRITICAL();
	}
	else
	{
		switch (mode)
		{
			case RADIO_MODE_ANALOG:
				audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
				break;

			case RADIO_MODE_DIGITAL:
				HRC6000ResetTimeSlotDetection();
				// We need to reset the slot state because some part of the UI
				// are getting stuck, like the green LED and QSO info, while navigating the UI.
				if (slotState != DMR_STATE_IDLE)
				{
					slotState = DMR_STATE_RX_END;
				}
				break;

			case RADIO_MODE_NONE:
				// nop
				break;
		}
	}
}

uint32_t trxGetNextOrPrevBandFromFrequency(uint32_t frequency, bool nextBand)
{
	if (nextBand)
	{
		if (frequency > RADIO_HARDWARE_FREQUENCY_BANDS[RADIO_BANDS_TOTAL_NUM - 1].maxFreq)
		{
			return 0; // First band
		}

		for(uint32_t band = 0; band < RADIO_BANDS_TOTAL_NUM - 1; band++)
		{
			if (frequency > RADIO_HARDWARE_FREQUENCY_BANDS[band].maxFreq && frequency < RADIO_HARDWARE_FREQUENCY_BANDS[band + 1].minFreq)
			{
				return (band + 1); // Next band
			}
		}
	}
	else
	{
		if (frequency < RADIO_HARDWARE_FREQUENCY_BANDS[0].minFreq)
		{
			return (RADIO_BANDS_TOTAL_NUM - 1); // Last band
		}

		for (uint32_t band = 1; band < RADIO_BANDS_TOTAL_NUM; band++)
		{
			if (frequency < RADIO_HARDWARE_FREQUENCY_BANDS[band].minFreq && frequency > RADIO_HARDWARE_FREQUENCY_BANDS[band - 1].maxFreq)
			{
				return (band - 1); // Prev band
			}
		}
	}

	return FREQUENCY_OUT_OF_BAND;
}

uint32_t trxGetBandFromFrequency(uint32_t frequency)
{
	for (uint32_t i = 0; i < RADIO_BANDS_TOTAL_NUM; i++)
	{
		if ((frequency >= RADIO_HARDWARE_FREQUENCY_BANDS[i].minFreq) && (frequency <= RADIO_HARDWARE_FREQUENCY_BANDS[i].maxFreq))
		{
			return i;
		}
	}

	return FREQUENCY_OUT_OF_BAND;
}

bool trxCheckFrequencyInAmateurBand(uint32_t frequency)
{
	if (nonVolatileSettings.txFreqLimited == BAND_LIMITS_FROM_CPS)
	{
		return ((frequency >= USER_FREQUENCY_BANDS[RADIO_BAND_VHF].minFreq) && (frequency <= USER_FREQUENCY_BANDS[RADIO_BAND_VHF].maxFreq)) ||
			((frequency >= USER_FREQUENCY_BANDS[RADIO_BAND_UHF].minFreq) && (frequency <= USER_FREQUENCY_BANDS[RADIO_BAND_UHF].maxFreq));
	}
	else if (nonVolatileSettings.txFreqLimited == BAND_LIMITS_ON_LEGACY_DEFAULT)
	{
		return ((frequency >= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_VHF].minFreq) && (frequency <= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_VHF].maxFreq)) ||
#if !defined(PLATFORM_MD9600)
			((frequency >= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_220MHz].minFreq) && (frequency <= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_220MHz].maxFreq)) ||
#endif
			((frequency >= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_UHF].minFreq) && (frequency <= DEFAULT_USER_FREQUENCY_BANDS[RADIO_BAND_UHF].maxFreq));
	}

	return true;// Setting must be BAND_LIMITS_NONE
}

void trxReadVoxAndMicStrength(void)
{
	radioReadVoxAndMicStrength();
}

// Need to postpone the next AT1846ReadRSSIAndNoise() call (see trxReadRSSIAndNoise())
// msOverride parameter is used if > 0
void trxPostponeReadRSSIAndNoise(uint32_t msOverride)
{
	ticksTimerStart((ticksTimer_t *)&trxNextRssiNoiseSampleTimer, (msOverride > 0 ? msOverride : RSSI_NOISE_SAMPLE_PERIOD_PIT));
}

// Check RSSI and Noise
void trxReadRSSIAndNoise(bool force)
{
	if (rxPowerSavingIsRxOn() && (ticksTimerHasExpired((ticksTimer_t *)&trxNextRssiNoiseSampleTimer) || force))
	{
		radioReadRSSIAndNoiseForBand(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]);
		ticksTimerStart((ticksTimer_t *)&trxNextRssiNoiseSampleTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
	}
}

bool trxCarrierDetected(RadioDevice_t deviceId)
{
	TRXDevice_t *radioDevice = &radioDevices[deviceId];// Get pointer to device to make code below more efficient
	uint8_t squelch = 0;

	trxReadRSSIAndNoise(true); // We need to get the RSSI and noise now.

	switch(radioDevice->currentMode)
	{
		case RADIO_MODE_NONE:
			return false;
			break;

		case RADIO_MODE_ANALOG:
			if (currentChannelData->sql != 0)
			{
				squelch = TRX_SQUELCH_MAX - ((currentChannelData->sql - 1) * TRX_SQUELCH_INC);
			}
			else
			{
				squelch = TRX_SQUELCH_MAX - ((nonVolatileSettings.squelchDefaults[radioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]) * TRX_SQUELCH_INC);
			}
			break;

		case RADIO_MODE_DIGITAL:
			squelch = TRX_SQUELCH_MAX - ((nonVolatileSettings.squelchDefaults[radioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]) * TRX_SQUELCH_INC);
			break;
	}

	return (radioDevice->trxRxNoise < squelch);
}

bool trxCheckDigitalSquelch(RadioDevice_t deviceId)
{
	TRXDevice_t *radioDevice = &radioDevices[deviceId];// Get pointer to device to make code below more efficient

	if (ticksTimerHasExpired((ticksTimer_t *)&trxNextSquelchCheckingTimer))
	{
		if (radioDevice->currentMode != RADIO_MODE_NONE)
		{
			uint8_t squelch;

			squelch = TRX_SQUELCH_MAX - ((nonVolatileSettings.squelchDefaults[radioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]) * TRX_SQUELCH_INC);

			if (radioDevice->trxRxNoise < squelch)
			{
				if ((uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED) == 0)
				{
					uiDataGlobal.rxBeepState |= (RX_BEEP_CARRIER_HAS_STARTED | RX_BEEP_CARRIER_HAS_STARTED_EXEC);
				}

				if(!radioDevice->digitalSignalReceived)
				{
					radioDevice->digitalSignalReceived = true;
					LedWrite(LED_GREEN, 1);
				}
			}
			else
			{
				if (radioDevice->digitalSignalReceived)
				{
					radioDevice->digitalSignalReceived = false;
					LedWrite(LED_GREEN, 0);
				}

				if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
				{
					uiDataGlobal.rxBeepState = RX_BEEP_CARRIER_HAS_ENDED;
				}
			}
		}

		if (deviceId == RADIO_DEVICE_PRIMARY)
		{
			ticksTimerStart((ticksTimer_t *)&trxNextSquelchCheckingTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
		}
	}

	return radioDevice->digitalSignalReceived;
}

void trxTerminateCheckAnalogSquelch(RadioDevice_t deviceId)
{
	audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
	radioDevices[deviceId].analogSignalReceived = false;
	radioDevices[deviceId].analogTriggeredAudio = false;
	trxCssMeasureCount = 0;
}

bool trxCheckAnalogSquelch(void)
{
	if (trxIsTransmitting)
	{
		audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
		radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived = false;
		radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio = false;
		return false;
	}

	if (uiVFOModeSweepScanning(false) || (currentRadioDevice->currentMode == RADIO_MODE_NONE))
	{
		return false;
	}

	trxReadRSSIAndNoise(false);

	if (ticksTimerHasExpired((ticksTimer_t *)&trxNextSquelchCheckingTimer))
	{
		uint8_t squelch;

		// check for variable squelch control
		if (currentChannelData->sql != 0)
		{
			squelch = TRX_SQUELCH_MAX - ((currentChannelData->sql - 1) * TRX_SQUELCH_INC);
		}
		else
		{
			squelch = TRX_SQUELCH_MAX - ((nonVolatileSettings.squelchDefaults[currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]) * TRX_SQUELCH_INC);
		}
		if (radioDevices[RADIO_DEVICE_PRIMARY].trxRxNoise < squelch) //noise less than squelch level = signal present.
		{
			if ((radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived == false) || (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) == 0)            // open squelch if this is the first occurrence or if the audio amp was turned off by something else.
			{
				radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived = true;
				LedWrite(LED_GREEN, 1);

				// FM: Replace Carrier beeps with Talker beeps if Caller beep option is selected.
				if (((nonVolatileSettings.beepOptions & BEEP_RX_CARRIER) == 0) && (nonVolatileSettings.beepOptions & BEEP_RX_TALKER))
				{
					if ((uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED) == 0)
					{
						uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_STARTED | RX_BEEP_TALKER_HAS_STARTED_EXEC);
					}
				}
				else
				{
					if ((uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED) == 0)
					{
						uiDataGlobal.rxBeepState |= (RX_BEEP_CARRIER_HAS_STARTED | RX_BEEP_CARRIER_HAS_STARTED_EXEC);
					}
				}

				radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio = true;
				trxCssMeasureCount = 0;
			}
		}

		if (radioDevices[RADIO_DEVICE_PRIMARY].trxRxNoise > squelch + TRX_SQUELCH_HIST)							//add hysteresis to delay squelch closing. This prevents squelch chattering.
		{
			if (radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived || LedRead(LED_GREEN))
			{
				radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived = false;
				LedWrite(LED_GREEN, 0);

				// FM: Replace Carrier beeps with Talker beeps if Caller beep option is selected.
				if (((nonVolatileSettings.beepOptions & BEEP_RX_CARRIER) == 0) && (nonVolatileSettings.beepOptions & BEEP_RX_TALKER))
				{
					if (uiDataGlobal.rxBeepState & RX_BEEP_TALKER_HAS_STARTED)
					{
						uiDataGlobal.rxBeepState |= (RX_BEEP_TALKER_HAS_ENDED | RX_BEEP_TALKER_HAS_ENDED_EXEC);
					}
				}
				else
				{
					if (uiDataGlobal.rxBeepState & RX_BEEP_CARRIER_HAS_STARTED)
					{
						uiDataGlobal.rxBeepState = RX_BEEP_CARRIER_HAS_ENDED;
					}
				}

				radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio = false;
				trxCssMeasureCount = 0;
			}
		}

		bool cssFlag = (rxCSSactive ? trxCheckCSSFlag(currentChannelData->rxTone) : false);

		if (radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived)
		{
			if (((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) == 0) && ((rxCSSactive == false) || cssFlag))
			{
				if (radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio) // Execute that block of code just once after valid signal is received.
				{
					taskENTER_CRITICAL();
					if (!voicePromptsIsPlaying())
					{
						radioSetAudioPath(true);						//Select the FM Audio Path
						audioAmpEnable(AUDIO_AMP_CHANNEL_RF);
						displayLightTrigger(false);
						radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio = false;
						trxCssMeasureCount = 0;
					}
					taskEXIT_CRITICAL();
				}
			}
			else if (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF)
			{
				if (rxCSSactive && (cssFlag == false)) // CSS disappeared.
				{
					trxCssMeasureCount++;
					// If using CTCSS or DCS and signal isn't lost, allow some loss of tone / code.
					// Note:
					//    It's not unusual to have the CSS detection failing (CTCSS, depending of the sub-tone) if
					//    the signal is over-modulated/deviated, so waiting for 150ms is fine, and almost needed.
					//    Waiting for shorter time will just constantly disable and enable the audio Amp.
					if (trxCssMeasureCount >= CTCSS_HOLD_DELAY)
					{
						audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
						radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived = false;
						radioDevices[RADIO_DEVICE_PRIMARY].analogTriggeredAudio = false;
						trxCssMeasureCount = 0;
					}
				}
				else
				{
					trxCssMeasureCount = 0;
				}
			}
		}
		else
		{
			if (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF)
			{
				trxCssMeasureCount++;
				// If using CTCSS or DCS and signal isn't lost, allow some loss of tone / code
				//
				// NOTE: Currently it is NOT waiting at all.
				//
				if ((rxCSSactive == false) || (trxCssMeasureCount >= SQUELCH_CLOSE_DELAY))
				{
					audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
					trxCssMeasureCount = 0;
				}
			}
		}

		if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
		{
			ticksTimerStart((ticksTimer_t *)&trxNextSquelchCheckingTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
		}
	}

	return radioDevices[RADIO_DEVICE_PRIMARY].analogSignalReceived;
}

void trxResetSquelchesState(RadioDevice_t deviceId)
{
	radioDevices[deviceId].digitalSignalReceived = false;
	radioDevices[deviceId].analogSignalReceived = false;
}

void trxSetFrequency(uint32_t fRx, uint32_t fTx, int dmrMode)
{
	//
	// Freq could be identical, but not the power of the current channel
	//
	if (currentChannelData->libreDMR_Power != 0x00)
	{
		currentRadioDevice->txPowerLevel = currentChannelData->libreDMR_Power - 1;
	}
	else
	{
		currentRadioDevice->txPowerLevel = nonVolatileSettings.txPowerLevel;
	}

	if (dmrMode == DMR_MODE_AUTO)
	{
		// Most DMR radios determine whether to use Active or Passive DMR depending on whether the Tx and Rx freq are the same
		// This prevents split simplex operation, but since no other radio appears to support split freq simplex
		// Its easier to do things the same way as othe radios, and revisit this again in the future if split freq simplex is required.
		if (fRx == fTx)
		{
			currentRadioDevice->trxDMRModeTx = DMR_MODE_DMO;
			currentRadioDevice->trxDMRModeRx = DMR_MODE_DMO;
		}
		else
		{
			currentRadioDevice->trxDMRModeTx = DMR_MODE_RMO;
			currentRadioDevice->trxDMRModeRx = DMR_MODE_RMO;
		}
	}
	else
	{
		currentRadioDevice->trxDMRModeTx = dmrMode;
		currentRadioDevice->trxDMRModeRx = dmrMode;
	}

	if ((currentRadioDevice->currentRxFrequency != fRx) || (currentRadioDevice->currentTxFrequency != fTx))
	{
		if (rxPowerSavingIsRxOn() == false)
		{
			rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
		}

		taskENTER_CRITICAL();
		currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND] = trxGetBandFromFrequency(fRx);

		currentRadioDevice->currentRxFrequency = fRx;
		currentRadioDevice->currentTxFrequency = fTx;

		if (currentRadioDevice->currentMode == RADIO_MODE_DIGITAL)
		{
			HRC6000TerminateDigital();
		}

		trxUpdateC6000Calibration();
		trxUpdateRadioCalibration();

		radioSetFrequency(currentRadioDevice->currentRxFrequency, false);
		radioSetIF(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND], currentRadioDevice->currentBandWidthIs25kHz);

		if (currentRadioDevice->currentMode == RADIO_MODE_DIGITAL)
		{
			HRC6000InitDigital();
		}

		if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
		{
			ticksTimerStart((ticksTimer_t *)&trxNextRssiNoiseSampleTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
			ticksTimerStart((ticksTimer_t *)&trxNextSquelchCheckingTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
		}
		taskEXIT_CRITICAL();
	}
}

uint32_t trxGetFrequency(void)
{
	if (trxTransmissionEnabled)
	{
		return currentRadioDevice->currentTxFrequency;
	}

	return currentRadioDevice->currentRxFrequency;
}

void trxSetRX(void)
{
	if (currentRadioDevice->currentMode == RADIO_MODE_ANALOG)
	{
		trxActivateRx(true);
	}
}

void trxConfigurePA_DAC_ForFrequencyBand(void)
{
	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		currentRadioDevice->trxCurrentBand[TRX_TX_FREQ_BAND] = trxGetBandFromFrequency(currentRadioDevice->currentTxFrequency);
		calibrationGetPowerForFrequency(currentRadioDevice->currentTxFrequency, &trxPowerSettings);
		currentRadioDevice->lastSetTxFrequency = currentRadioDevice->currentTxFrequency;
		currentRadioDevice->lastSetTxPowerLevel = currentRadioDevice->txPowerLevel;

		trxUpdate_PA_DAC_Drive();
	}
}

void trxSetTX(void)
{
	trxConfigurePA_DAC_ForFrequencyBand();

	trxTransmissionEnabled = true;

	if (currentRadioDevice->currentMode == RADIO_MODE_ANALOG)
	{
		trxActivateTx(true);
	}
}

void trxActivateRx(bool critical)
{
	UNUSED_PARAMETER(critical);

	trxIsTransmittingDMR = false;

	RadioDevice_t previousDevice = radioGetTRxDeviceId();

	for(RadioDevice_t device = RADIO_DEVICE_PRIMARY; device < RADIO_DEVICE_MAX; device++)
	{
		radioSetTRxDevice(device);
		radioSetRx(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]);
		radioSetFrequency(currentRadioDevice->currentRxFrequency, false);
		radioSetIF(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND], currentRadioDevice->currentBandWidthIs25kHz);
	}

	radioSetTRxDevice(previousDevice);
	trxUpdateC6000Calibration();// This seems to be needed, otherwise after transmission has ended the Rx appears to have a considerable freq offset

	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		ticksTimerStart((ticksTimer_t *)&trxNextRssiNoiseSampleTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
		ticksTimerStart((ticksTimer_t *)&trxNextSquelchCheckingTimer, RSSI_NOISE_SAMPLE_PERIOD_PIT);
	}
}

void trxActivateTx(bool critical)
{
	UNUSED_PARAMETER(critical);

	if (currentRadioDevice->currentMode == RADIO_MODE_NONE)
	{
		return;
	}

	txPAEnabled = true;
	radioDevices[RADIO_DEVICE_PRIMARY].trxRxSignal = 0;
	radioDevices[RADIO_DEVICE_PRIMARY].trxRxNoise = 255;

	radioSetFrequency(currentRadioDevice->currentTxFrequency, true);

	radioSetTx(currentRadioDevice->trxCurrentBand[TRX_TX_FREQ_BAND]);
}

//start of DMR transmission so do a full activation of the transmitter
void trxActivateDMRTx(void)
{
	trxActivateTx(false);
	trxIsTransmittingDMR = true;
}

//Already transmitting so just turn the output signal On or off
void trxFastDMRTx(bool tx)
{
	radioFastTx(tx);
}

void trxSetPowerFromLevel(uint8_t powerLevel)
{
	currentRadioDevice->txPowerLevel = powerLevel;
}

void trxUpdate_PA_DAC_Drive(void)
{
	// Note. Fraction values for 200Mhz are currently the same as the VHF band, because there isn't any way to set the 1W value on 220Mhz as there are only 2 calibration tables
	static const float fractionalPowers[RADIO_BANDS_TOTAL_NUM][5] =
	{
			{ 0.82f, 0.84f, 0.87f, 0.88f, 0.90f }, // VHF
#if !defined(PLATFORM_MD9600)
			{ 0.5f, 0.6f,  0.7f,  0.8f,  0.9f  }, // 220Mhz
#endif
			{ 0.41f, 0.47f,  0.54f,  0.59f,  0.64f  }  // UHF
	};

	switch(currentRadioDevice->txPowerLevel)
	{
		case 0:// 100mW
		case 1:// 250mW
		case 2:// 500mW
		case 3:// 750mW
		case 4:// 1W
			txDACDrivePower = (float) trxPowerSettings.lowPower * fractionalPowers[currentRadioDevice->trxCurrentBand[TRX_TX_FREQ_BAND]][currentRadioDevice->txPowerLevel];
			break;
		case 5:// 5W
			txDACDrivePower = trxPowerSettings.lowPower;
			break;
		case 6:// 10W
			txDACDrivePower = trxPowerSettings.midLowPower;
			break;
		case 7:// 25W
			txDACDrivePower = trxPowerSettings.midPower;
			break;
		case 8:// 40W
			txDACDrivePower = trxPowerSettings.highPower;
			break;
		case 9:// +W-
			txDACDrivePower = nonVolatileSettings.userPower;
			break;
		default:
			txDACDrivePower = trxPowerSettings.lowPower;
			break;
	}

	if (txDACDrivePower > MAX_PA_DAC_VALUE)
	{
		txDACDrivePower = MAX_PA_DAC_VALUE;
	}
}

uint16_t trxGetPA_DAC_Drive(void)
{
	return txDACDrivePower;
}

uint8_t trxGetPowerLevel(void)
{
	return currentRadioDevice->txPowerLevel;
}

void trxCalcBandAndFrequencyOffset(CalibrationBand_t *calibrationBand, uint32_t *freq_offset)
{
// NOTE. For crossband duplex DMR, the calibration potentially needs to be changed every time the Tx/Rx is switched over on each 30ms cycle
// But at the moment this is an unnecessary complication and I'll just use the Rx frequency to get the calibration offsets

	if (currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND] == RADIO_BAND_UHF)
	{
		*calibrationBand = CalibrationBandUHF;
		*freq_offset = (currentRadioDevice->currentTxFrequency - 40000000) / 1000000;
		if (*freq_offset > 8)
		{
			*freq_offset = 8;
		}
	}
	else
	{
		*calibrationBand = CalibrationBandVHF;
		*freq_offset = (currentRadioDevice->currentTxFrequency - 13600000) / 950000;
		if (*freq_offset > 4)
		{
			*freq_offset = 4;
		}
	}
}

static void trxUpdateC6000Calibration(void)
{
	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		int8_t cal = calibrationGetMod2Offset(currentRadioDevice->trxCurrentBand[trxTransmissionEnabled ? TRX_TX_FREQ_BAND : TRX_RX_FREQ_BAND]);
		SPI0WritePageRegByte(0x04, 0x47, cal);			// Set the reference tuning offset
		SPI0WritePageRegByte(0x04, 0x48, ((cal < 0) ? 0x03 : 0x00));
		SPI0WritePageRegByte(0x04, 0x04, cal);									//Set MOD 2 Offset (Cal Value)
	}
}

static void trxUpdateRadioCalibration(void)
{
	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		//More calibrations to add later.
		if (currentRadioDevice->currentRxFrequency > 30000000)
		{
			UHFRxTuningVolts = calibrationGetRxTuneForFrequency(currentRadioDevice->currentRxFrequency);
		}
		else
		{
			VHFRxTuningVolts = calibrationGetRxTuneForFrequency(currentRadioDevice->currentRxFrequency);
		}

		analogIGain = calibrationGetAnalogIGainForFrequency(currentRadioDevice->currentTxFrequency);
		analogQGain = calibrationGetAnalogQGainForFrequency(currentRadioDevice->currentTxFrequency);
		digitalIGain = calibrationGetDigitalIGainForFrequency(currentRadioDevice->currentTxFrequency);
		digitalQGain = calibrationGetDigitalQGainForFrequency(currentRadioDevice->currentTxFrequency);
		Mod2Offset = calibrationGetMod2Offset(currentRadioDevice->trxCurrentBand[trxTransmissionEnabled ? TRX_TX_FREQ_BAND : TRX_RX_FREQ_BAND]);
	}
}

void trxSetDMRColourCode(uint8_t colourCode)
{
	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		if (rxPowerSavingIsRxOn() == false)
		{
			rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
		}

		SPI0WritePageRegByte(0x04, 0x1F, (colourCode << 4)); // DMR Colour code in upper 4 bits.
		currentCC = colourCode;
	}
}

uint8_t trxGetDMRColourCode(void)
{
	return currentCC;
}

int trxGetDMRTimeSlot(void)
{
	return trxCurrentDMRTimeSlot;
}

void trxSetDMRTimeSlot(int timeslot, bool resync)
{
	if (currentRadioDeviceId == RADIO_DEVICE_PRIMARY)
	{
		if (rxPowerSavingIsRxOn() == false)
		{
			rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
		}

		trxCurrentDMRTimeSlot = timeslot;

		if (resync)
		{
			HRC6000ResyncTimeSlot();
		}
	}
}

void trxUpdateTsForCurrentChannelWithSpecifiedContact(CodeplugContact_t *contactData)
{
	// Contact TS override ?
	if ((nonVolatileSettings.overrideTG == 0) && (contactData->reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00)
	{
		if (tsIsContactHasBeenOverriddenFromCurrentChannel())
		{
			trxCurrentDMRTimeSlot = (tsGetManualOverrideFromCurrentChannel() - 1);
		}
		else
		{
			trxCurrentDMRTimeSlot = ((contactData->reserve1 & CODEPLUG_CONTACT_FLAG_TS_OVERRIDE_TIMESLOT_MASK) != 0) ? 1 : 0;
		}
	}
	else
	{
		int8_t overriddenTS = tsGetManualOverrideFromCurrentChannel();

		// No manual override
		if (overriddenTS == 0)
		{
			// Apply channnel TS
			trxCurrentDMRTimeSlot = (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_TIMESLOT_TWO) != 0) ? 1 : 0;
		}
		else
		{
			// Restore overriden TS (as previous contact may have changed it
			trxCurrentDMRTimeSlot = (overriddenTS - 1);
		}
	}

	HRC6000ResyncTimeSlot();
}

void trxSetTxCSS(uint16_t tone)
{
	taskENTER_CRITICAL();
	CodeplugCSSTypes_t type = codeplugGetCSSType(tone);

	if (type == CSS_TYPE_NONE)
	{
		radioTxCSSOff();
	}
	else if (type == CSS_TYPE_CTCSS)
	{
		// value that is stored is 100 time the tone freq but its stored in the codeplug as freq times 10
		tone *= 10;
		radioTxCTCSOn(tone);
	}
	else if (type & CSS_TYPE_DCS)
	{
		// The HRC-6000 in the MD-9600 does the Golay encoding for us.
		uint16_t code = convertCSSNative2BinaryCodedOctal(tone & ~CSS_TYPE_DCS_MASK);

		radioTxDCSOn(code, ((type & CSS_TYPE_DCS_INVERTED) != 0));
	}
	taskEXIT_CRITICAL();
}

void trxSetRxCSS(RadioDevice_t deviceId, uint16_t tone)
{
	taskENTER_CRITICAL();
	CodeplugCSSTypes_t type = codeplugGetCSSType(tone);

	if (type == CSS_TYPE_NONE)
	{
		radioRxCSSOff();
		rxCSSactive = false;
	}
	else if (type == CSS_TYPE_CTCSS)
	{
		// value that is stored is 100 time the tone freq but its stored in the codeplug as freq times 10
		tone *= 10;
		radioRxCTCSOn(tone);
		rxCSSactive = (trxAnalogFilterLevel != ANALOG_FILTER_NONE);
		// Force closing the AudioAmp
		audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
		radioDevices[deviceId].analogSignalReceived = false;
		radioDevices[deviceId].analogTriggeredAudio = false;
	}
	else if (type & CSS_TYPE_DCS)
	{
		// The HRC-6000 in the MD-9600 does the Golay encoding for us.
		uint16_t code = convertCSSNative2BinaryCodedOctal(tone & ~CSS_TYPE_DCS_MASK);

		radioRxDCSOn(code, ((type & CSS_TYPE_DCS_INVERTED) != 0));
		rxCSSactive = (trxAnalogFilterLevel != ANALOG_FILTER_NONE);
		// Force closing the AudioAmp
		audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
		radioDevices[deviceId].analogSignalReceived = false;
		radioDevices[deviceId].analogTriggeredAudio = false;
	}
	taskEXIT_CRITICAL();
}

bool trxCheckCSSFlag(uint16_t tone)
{
	CodeplugCSSTypes_t type = codeplugGetCSSType(tone);

	return ((type != CSS_TYPE_NONE) && (radioCheckCSS(tone, type)));
}

uint8_t trxGetCalibrationVoiceGainTx(void)
{
	return voice_gain_tx;
}

void trxSetTone1(int toneFreq)
{
    radioSetTone1(toneFreq);
}

void trxSetDTMF(int code)
{
	if (code < 16)
	{
		HRC6000SetDTMF(code);
	}
}

void trxDTMFoff(bool enableMic)
{
	HRC6000DTMFoff(enableMic);
}

// Codeplug format (hex) -> octal
static uint16_t convertCSSNative2BinaryCodedOctal(uint16_t nativeCSS)
{
	uint16_t octalCSS = 0;
	uint16_t shift = 0;

	while (nativeCSS)
	{
		octalCSS += (nativeCSS & 0xF) << shift;
		nativeCSS >>= 4;
		shift += 3;
	}
	return octalCSS;
}


void trxSetMicGainFM(uint8_t gain)
{
	radioSetMicGainFM(gain);
}

void trxEnableTransmission(void)
{
	LedWrite(LED_GREEN, 0);
	LedWrite(LED_RED, 1);
	trxSetTX();
}

void trxDisableTransmission(void)
{
    LedWrite(LED_RED, 0);
	trxActivateRx(true);
}

// Returns true if the HR-C6000 has been powered off
bool trxPowerUpDownRxAndC6000(bool powerUp, bool includeC6000)
{
	bool status = false;

	// Check the radio is not transmitting.
	if ((powerUp == powerUpDownState) || trxTransmissionEnabled || trxIsTransmitting)
	{
		return false;
	}

	// Force HRC6000 to power cycles in any ECO mode on STM32 platforms, as it seems
	// there are some bad batches that breaks Beep and Audio until the operator enables
	// monitor mode or restarts the radio.
#if defined(STM32F405xx)
	includeC6000 = true;
#endif

	if (powerUp)
	{
		if (includeC6000 && (voicePromptsIsPlaying() == false))
		{
			uint8_t spi_values[SIZE_OF_FILL_BUFFER];

			// Always power up the C6000 even if its may already be powered up, because VP was playing
			HAL_GPIO_WritePin(C6000_PWD_GPIO_Port, C6000_PWD_Pin, GPIO_PIN_RESET); // Power Up the C6000
			// Allow some time to the C6000 to get ready
			vTaskDelay((10U / portTICK_PERIOD_MS));

			memset(spi_values, 0xAA, SIZE_OF_FILL_BUFFER);
			SPI0ClearPageRegByteWithMask(0x04, 0x06, 0xFD, 0x02); // SET OpenMusic bit (play Boot sound and Call Prompts)
			SPI0WritePageRegByteArray(0x03, 0x00, spi_values, SIZE_OF_FILL_BUFFER);
			SPI0ClearPageRegByteWithMask(0x04, 0x06, 0xFD, 0x00); // CLEAR OpenMusic bit (play Boot sound and Call Prompts)

			SPI0WritePageRegByte(0x04, 0x06, 0x21); // Use SPI vocoder under MCU control

			// Needs to reset all the audio (I2S BUS/buffering and sound counters).
			soundInit();
		}

		radioSetBandwidth(currentRadioDevice->currentBandWidthIs25kHz);

#if 0
		// Enable the IRQ, conditionally.
		if (NVIC_GetEnableIRQ(PORTC_IRQn) == 0)
		{
			NVIC_EnableIRQ(PORTC_IRQn);
		}
#endif
	}
	else
	{
		taskENTER_CRITICAL();
		if (!voicePromptsIsPlaying())
		{
			if (includeC6000)
			{
				// NVIC_DisableIRQ(PORTC_IRQn);

				// Ensure the ISR has exited before powering off the chip.
				while (HRC6000IRQHandlerIsRunning());

				/*
				 * vk3yy commented out as not supported by the MD9600
				 *
				HAL_GPIO_WritePin(C6000_PWD_GPIO_Port, C6000_PWD_Pin, GPIO_PIN_SET); // Power down the C6000
				 */

				/*
				 * vk3kyy this is handled by the stm32cube
				 *
				SAI_TxEnable(I2S0, false);
				SAI_RxEnable(I2S0, false);
				 */
				status = true;
			}
		}
		taskEXIT_CRITICAL();
	}

	powerUpDownState = powerUp;

	return status;
}

void trxInvalidateCurrentFrequency(void)
{
	currentRadioDevice->currentRxFrequency = -1;
	currentRadioDevice->currentTxFrequency = -1;
	currentRadioDevice->currentMode = RADIO_MODE_NONE;
}


// vk3kyy Dummy functions
void trxSelectVoiceChannel(uint8_t channel)
{
	UNUSED_PARAMETER(channel);
}

void trxRxAndTxOff(bool critical)
{
	UNUSED_PARAMETER(critical);
}

void trxRxOn(bool critical)
{
	UNUSED_PARAMETER(critical);
}

#if defined(MD9600_VERSION_2) || defined (MD9600_VERSION_4) || defined (MD9600_VERSION_1)
#define VHF_RSSI_OFFSET -135
#define UHF_RSSI_OFFSET -145
#define VHF_RSSI_DIVISOR 2
#define UHF_RSSI_DIVISOR 2
#else
#define VHF_RSSI_OFFSET -155
#define UHF_RSSI_OFFSET -155
#define VHF_RSSI_DIVISOR 1.95
#define UHF_RSSI_DIVISOR 1.95
#endif

int trxGetRSSIdBm(RadioDevice_t deviceId)
{
	int dBm = 0;

	if (radioDevices[deviceId].trxCurrentBand[TRX_RX_FREQ_BAND] == RADIO_BAND_UHF)
	{
		dBm = UHF_RSSI_OFFSET + (radioDevices[deviceId].trxRxSignal / UHF_RSSI_DIVISOR);     //V2 Radio IF chip is approximately 2 ADC counts per dB
	}
	else
	{
		dBm = VHF_RSSI_OFFSET + (radioDevices[deviceId].trxRxSignal / VHF_RSSI_DIVISOR);     //V2 Radio IF chip is approximately 2 ADC counts per dB
	}

	return dBm;
}

int trxGetNoisedBm(RadioDevice_t deviceId)
{
	int dBm = 0;

	if (radioDevices[deviceId].trxCurrentBand[TRX_RX_FREQ_BAND] == RADIO_BAND_UHF)
	{
		dBm = -135 + radioDevices[deviceId].trxRxNoise / 2;     //V2 Radio IF chip is approximately 2 ADC counts per dB
	}
	else
	{
		// VHF

		dBm = -145 + radioDevices[deviceId].trxRxNoise / 2;     //V2 Radio IF chip is approximately 2 ADC counts per dB
	}

	return dBm;
}

int trxGetSNRMargindBm(RadioDevice_t deviceId)
{
	return (trxGetRSSIdBm(deviceId) - trxGetNoisedBm(deviceId));
}

uint8_t trxGetSignalRaw(RadioDevice_t deviceId)
{
	return radioDevices[deviceId].trxRxSignal;
}

uint8_t trxGetNoiseRaw(RadioDevice_t deviceId)
{
	return radioDevices[deviceId].trxRxNoise;
}
