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

625 lines
19 KiB
C

/*
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
* Daniel Caujolle-Bert, F1RMB
*
* Using information from the MMDVM_HS source code by Andy CA6JAU
*
*
* 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/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"
#include "interfaces/gps.h"
// Uncomment the following to enable demo screen, access it with function events
//#define DEMO_SCREEN
/*
Problems with MD-390 on the same frequency
------------------------------------------
Using a Dual Hat:
M: 2020-01-07 08:46:23.337 DMR Slot 2, received RF voice header from F1RMB to 5000
M: 2020-01-07 08:46:24.143 DMR Slot 2, received RF end of voice transmission from F1RMB to 5000, 0.7 seconds, BER: 0.4%
M: 2020-01-07 08:46:24.644 DMR Slot 2, RF user 12935424 rejected
Using OpenHD77 HS
PC 5000 sent from a GD-77
M: 2020-01-07 09:51:55.467 DMR Slot 2, received RF voice header from F1RMB to 5000
M: 2020-01-07 09:51:56.727 DMR Slot 2, received RF end of voice transmission from F1RMB to 5000, 1.1 seconds, BER: 0.3%
M: 2020-01-07 09:52:00.068 DMR Slot 2, received network voice header from 5000 to TG 9
M: 2020-01-07 09:52:03.428 DMR Slot 2, received network end of voice transmission from 5000 to TG 9, 3.5 seconds, 0% packet loss, BER: 0.0%
Its echo (data sent from the MD-390, by itself):
M: 2020-01-07 09:52:07.300 DMR Slot 2, received RF voice header from F1RMB to 5000
M: 2020-01-07 09:52:09.312 DMR Slot 2, RF voice transmission lost from F1RMB to 5000, 0.0 seconds, BER: 0.0%
M: 2020-01-07 09:52:11.856 DMR Slot 2, received network voice header from 5000 to TG 9
M: 2020-01-07 09:52:15.246 DMR Slot 2, received network end of voice transmission from 5000 to TG 9, 3.5 seconds, 0% packet loss, BER: 0.0%
There is a problem if you have a MD-390, GPS enabled, listening on the same frequency (even without different DMRId, I checked !).
It send invalid data from time to time (that's not critical, it's simply rejected), but once it heard a PC or TG call (from other
transceiver), it will keep repeating that, instead of invalid data.
After investigations, it's turns out if you enable 'GPS System' (even with correct configuration, like
GC xxx999 for BM), it will send such weird or echo data each GPS interval.
** Long story short: turn off GPS stuff on your MD-390 if it's listening local transmissions from other transceivers. **
*/
typedef struct
{
uint32_t savedTGorPC;
uint32_t batteryUpdateTimeout;
#if defined(HAS_GPS)
gpsMode_t previousGPSState;
#endif
uint16_t batteryAverageMillivolts;
uint8_t savedDMRDestinationFilter; // 0xFF value means unset
uint8_t savedDMRCcTsFilter; // 0xFF value means unset
uint8_t savedLibreDMR_Power;
bool displayFWVersion;
#if ! defined(HAS_COLOURS)
const bool doubleHeight;
#else
bool doubleHeight;
#endif
} hostpotData_t;
static hostpotData_t hotspotData =
{
.savedTGorPC = 0U,
.batteryUpdateTimeout = 0U,
#if defined(HAS_GPS)
.previousGPSState = GPS_NOT_DETECTED,
#endif
.batteryAverageMillivolts = 0U,
.savedDMRDestinationFilter = 0xFF, // 0xFF value means unset
.savedDMRCcTsFilter = 0xFF, // 0xFF value means unset
.savedLibreDMR_Power = 0U,
.displayFWVersion = false,
.doubleHeight = false
};
#if defined(PLATFORM_VARIANT_DM1701)
#define DOUBLE_SIZE_MULT 1.8
#else
#define DOUBLE_SIZE_MULT 2.0
#endif
#if defined(HAS_COLOURS)
#define GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(x) ((uint16_t)((x) * (hotspotData.doubleHeight ? DOUBLE_SIZE_MULT : 1)))
#else
#define GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(x) (x)
#endif
static bool handleEvent(uiEvent_t *ev);
static uint16_t getBatteryAverageInMillivolts(void);
#if defined(DEMO_SCREEN)
bool demoScreen = false;
#endif
menuStatus_t menuHotspotMode(uiEvent_t *ev, bool isFirstRun)
{
if (isFirstRun)
{
#if defined(HAS_GPS)
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_ON)
{
hotspotData.previousGPSState = SETTINGS_GPS_MODE_GET(nonVolatileSettings);
#if defined(LOG_GPS_DATA)
gpsLoggingStop();
#endif
gpsOff();
nonVolatileSettings.gpsModeAndBaudsIndex = SETTINGS_GPS_MODE_SET(nonVolatileSettings, GPS_MODE_OFF);
}
#endif
rxPowerSavingSetLevel(0);// disable power saving
// DMR filter level isn't saved yet (cycling power OFF/ON quickly can corrupt
// this value otherwise, as menuHotspotMode(true) could be called twice.
if (hotspotData.savedDMRDestinationFilter == 0xFF)
{
// Override DMR filtering
hotspotData.savedDMRDestinationFilter = nonVolatileSettings.dmrDestinationFilter;
hotspotData.savedDMRCcTsFilter = nonVolatileSettings.dmrCcTsFilter;
nonVolatileSettings.dmrDestinationFilter = DMR_DESTINATION_FILTER_NONE;
nonVolatileSettings.dmrCcTsFilter = DMR_CCTS_FILTER_CC_TS;
}
// Do not user per channel power settings
hotspotData.savedLibreDMR_Power = currentChannelData->libreDMR_Power;
currentChannelData->libreDMR_Power = 0;
hotspotData.savedTGorPC = trxTalkGroupOrPcId;// Save the current TG or PC
hotspotData.displayFWVersion = false;
#if defined(HAS_COLOURS)
hotspotData.doubleHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
#endif
displayClearBuf();
displayPrintCenteredDoubleHeight(0, "Hotspot", FONT_SIZE_3, hotspotData.doubleHeight);
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(32), "Waiting for", FONT_SIZE_3, hotspotData.doubleHeight);
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(48), "Pi-Star", FONT_SIZE_3, hotspotData.doubleHeight);
displayRender();
hotspotInit();
displayLightTrigger(false);
}
else
{
if (ev->hasEvent)
{
if (handleEvent(ev) == false)
{
return MENU_STATUS_SUCCESS;
}
}
}
#if defined(DEMO_SCREEN)
if (! demoScreen)
{
#endif
processUSBDataQueue();
if (comRecvMMDVMFrameCount > 0)
{
handleHotspotRequest();
}
hotspotStateMachine();
// CW beaconing
if (hotspotCwKeying)
{
if (hotspotCwpoLen > 0)
{
cwProcess();
}
}
#if defined(DEMO_SCREEN)
}
#endif
// Battery level has changed in the last 5 minutes, but screen wasn't redrawn yet
if ((hotspotData.batteryAverageMillivolts != getBatteryAverageInMillivolts()) &&
((ticksGetMillis() - hotspotData.batteryUpdateTimeout) > 300000U))
{
uiHotspotUpdateScreen(hotspotCurrentRxCommandState);
}
return MENU_STATUS_SUCCESS;
}
void menuHotspotRestoreSettings(void)
{
if (hotspotData.savedDMRDestinationFilter != 0xFF)
{
nonVolatileSettings.dmrDestinationFilter = hotspotData.savedDMRDestinationFilter;
hotspotData.savedDMRDestinationFilter = 0xFF; // Unset saved DMR destination filter level
nonVolatileSettings.dmrCcTsFilter = hotspotData.savedDMRCcTsFilter;
hotspotData.savedDMRCcTsFilter = 0xFF; // Unset saved CC TS filter level
HRC6000ResetTimeSlotDetection();
}
#if defined(HAS_GPS)
// restore GPS state if needed
if (hotspotData.previousGPSState >= GPS_MODE_ON)
{
nonVolatileSettings.gpsModeAndBaudsIndex = SETTINGS_GPS_MODE_SET(nonVolatileSettings, hotspotData.previousGPSState);
gpsOn();
#if defined(LOG_GPS_DATA)
gpsLoggingStart();
#endif
}
#endif
}
static void displayContactInfo(uint8_t y, char *text, size_t maxLen)
{
// Max for TalkerAlias is 37: TA 27 (in 7bit format) + ' [' + 6 (Maidenhead) + ']' + NULL
// Max for DMRID Database is MAX_DMR_ID_CONTACT_TEXT_LENGTH (50 + NULL)
char buffer[MAX_DMR_ID_CONTACT_TEXT_LENGTH];
if (strlen(text) >= 5)
{
int32_t cpos;
if ((cpos = getFirstSpacePos(text)) != -1)
{
// Callsign found
memcpy(buffer, text, cpos);
buffer[cpos] = 0;
displayPrintCenteredDoubleHeight(y, chomp(buffer), FONT_SIZE_3, hotspotData.doubleHeight);
}
else
{
// No space found, use a chainsaw
memcpy(buffer, text, 16);
buffer[16] = 0;
displayPrintCenteredDoubleHeight(y, chomp(buffer), FONT_SIZE_3, hotspotData.doubleHeight);
}
}
else
{
memcpy(buffer, text, strlen(text));
buffer[strlen(text)] = 0;
displayPrintCenteredDoubleHeight(y, chomp(buffer), FONT_SIZE_3, hotspotData.doubleHeight);
}
}
static void updateContactLine(uint8_t y)
{
if ((LinkHead->talkGroupOrPcId >> 24) == PC_CALL_FLAG) // Its a Private call
{
displayPrintCentered(y, LinkHead->contact, FONT_SIZE_3);
}
else // Group call
{
switch (nonVolatileSettings.contactDisplayPriority)
{
case CONTACT_DISPLAY_PRIO_CC_DB_TA:
case CONTACT_DISPLAY_PRIO_DB_CC_TA:
// No contact found is codeplug and DMRIDs, use TA as fallback, if any.
if ((strncmp(LinkHead->contact, "ID:", 3) == 0) && (LinkHead->talkerAlias[0] != 0x00))
{
displayContactInfo(y, LinkHead->talkerAlias, sizeof(LinkHead->talkerAlias));
}
else
{
displayContactInfo(y, LinkHead->contact, sizeof(LinkHead->contact));
}
break;
case CONTACT_DISPLAY_PRIO_TA_CC_DB:
case CONTACT_DISPLAY_PRIO_TA_DB_CC:
// Talker Alias have the priority here
if (LinkHead->talkerAlias[0] != 0x00)
{
displayContactInfo(y, LinkHead->talkerAlias, sizeof(LinkHead->talkerAlias));
}
else // No TA, then use the one extracted from Codeplug or DMRIdDB
{
displayContactInfo(y, LinkHead->contact, sizeof(LinkHead->contact));
}
break;
}
}
}
void uiHotspotUpdateScreen(uint8_t rxCommandState)
{
int val_before_dp;
int val_after_dp;
char buffer[22]; // set to 22 due to FW info
hotspotCurrentRxCommandState = rxCommandState;
displayClearBuf();
displayPrintAt(4, 4, "DMR", FONT_SIZE_1);
displayPrintCentered(0, "Hotspot", FONT_SIZE_3);
// Display battery percentage/voltage
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
{
int volts, mvolts;
int16_t xV = (DISPLAY_SIZE_X - ((4 * 6) + 6));
getBatteryVoltage(&volts, &mvolts);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%2d", volts);
displayPrintCore(xV, 4, buffer, FONT_SIZE_1, TEXT_ALIGN_LEFT, false);
displayDrawRect(xV + (6 * 2), 4 + 5, 2, 2, true);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%1dV", mvolts);
displayPrintCore(xV + (6 * 2) + 3, 4, buffer, FONT_SIZE_1, TEXT_ALIGN_LEFT, false);
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d%%", getBatteryPercentage());
displayPrintAt(DISPLAY_SIZE_X - (strlen(buffer) * 6) - 4, 4, buffer, FONT_SIZE_1);
}
hotspotData.batteryUpdateTimeout = ticksGetMillis();
hotspotData.batteryAverageMillivolts = getBatteryAverageInMillivolts();
if (trxTransmissionEnabled)
{
if (hotspotData.displayFWVersion)
{
snprintf(buffer, 22, "%s", &HOTSPOT_VERSION_STRING[12]);
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16) + 4, buffer, FONT_SIZE_1, hotspotData.doubleHeight);
}
else
{
if (hotspotCwKeying)
{
sprintf(buffer, "%s", "<Tx CW ID>");
displayPrintCentered(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16), buffer, FONT_SIZE_3);
}
else
{
updateContactLine(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16));
}
}
if (hotspotCwKeying)
{
buffer[0] = 0;
}
else
{
if ((trxTalkGroupOrPcId >> 24) == PC_CALL_FLAG)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %u", currentLanguage->pc, trxTalkGroupOrPcId & 0x00FFFFFF);
}
else
{
uint32_t id = (trxTalkGroupOrPcId & 0x00FFFFFF);
if (id == 0)
{
buffer[0] = 0; // Do not display "TG 0"
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %u", currentLanguage->tg, id);
}
}
}
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(32), buffer, FONT_SIZE_3, hotspotData.doubleHeight);
val_before_dp = hotspotFreqTx / 100000;
val_after_dp = hotspotFreqTx - val_before_dp * 100000;
sprintf(buffer, "T %d.%05d MHz", val_before_dp, val_after_dp);
}
else
{
if (rxCommandState == HOTSPOT_RX_START || rxCommandState == HOTSPOT_RX_START_LATE)
{
dmrIdDataStruct_t currentRec;
if (hotspotData.displayFWVersion)
{
snprintf(buffer, 22, "%s", &HOTSPOT_VERSION_STRING[12]);
}
else
{
if (dmrIDLookup(hotspotRxedDMR_LC.srcId, &currentRec))
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", currentRec.text);
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "ID: %u", hotspotRxedDMR_LC.srcId);
}
}
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16) + (hotspotData.displayFWVersion ? 4 : 0), buffer, (hotspotData.displayFWVersion ? FONT_SIZE_1 : FONT_SIZE_3), hotspotData.doubleHeight);
if (hotspotRxedDMR_LC.FLCO == 0)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %u", currentLanguage->tg, hotspotRxedDMR_LC.dstId);
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %u", currentLanguage->pc, hotspotRxedDMR_LC.dstId);
}
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(32), buffer, FONT_SIZE_3, hotspotData.doubleHeight);
}
else
{
if (hotspotData.displayFWVersion)
{
snprintf(buffer, 22, "%s", &HOTSPOT_VERSION_STRING[12]);
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16) + 4, buffer, FONT_SIZE_1, hotspotData.doubleHeight);
}
else
{
if (hotspotModemState == STATE_POCSAG)
{
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16), "<POCSAG>", FONT_SIZE_3, hotspotData.doubleHeight);
}
else
{
if (strlen(hotspotMmdvmQSOInfoIP))
{
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(16) + 4, hotspotMmdvmQSOInfoIP, FONT_SIZE_1, hotspotData.doubleHeight);
}
}
}
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "CC:%u", trxGetDMRColourCode());//, trxGetDMRTimeSlot()+1) ;
displayPrintCoreDoubleHeight(0, GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(32), buffer, FONT_SIZE_3, TEXT_ALIGN_LEFT, false, hotspotData.doubleHeight);
sprintf(buffer, "%s%s", getPowerLevel(hotspotPowerLevel), getPowerLevelUnit(hotspotPowerLevel));
displayPrintCoreDoubleHeight(0, GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(32), buffer, FONT_SIZE_3, TEXT_ALIGN_RIGHT, false, hotspotData.doubleHeight);
}
val_before_dp = hotspotFreqRx / 100000;
val_after_dp = hotspotFreqRx - val_before_dp * 100000;
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "R %d.%05d MHz", val_before_dp, val_after_dp);
}
displayPrintCenteredDoubleHeight(GET_Y_POS_WHEN_DOUBLE_HEIGHT_ENABLED(48), buffer, FONT_SIZE_3, hotspotData.doubleHeight);
displayRender();
if ((trxTransmissionEnabled && (hotspotCwKeying == false)) || ((rxCommandState == HOTSPOT_RX_START) || (rxCommandState == HOTSPOT_RX_START_LATE)))
{
displayLightTrigger(false);
}
}
static bool handleEvent(uiEvent_t *ev)
{
displayLightTrigger(ev->hasEvent);
#if defined(DEMO_SCREEN)
if (ev->events & FUNCTION_EVENT)
{
demoScreen = ((ev->function >= 90) && (ev->function < 100));
switch (ev->function)
{
case 90:
sprintf(hotspotMmdvmQSOInfoIP, "%s", "192.168.100.10");
settingsUsbMode = USB_MODE_CPS;
break;
case 91:
trxTalkGroupOrPcId = 98977;
LinkHead->talkGroupOrPcId = 0;
sprintf(LinkHead->contact, "%s", "VK3KYY");
trxTransmissionEnabled = true;
uiHotspotUpdateScreen(HOTSPOT_STATE_TX_START_BUFFERING);
break;
case 92:
hotspotRxedDMR_LC.FLCO = 0;
hotspotRxedDMR_LC.srcId = 5053238;
hotspotRxedDMR_LC.dstId = 91;
trxTransmissionEnabled = false;
uiHotspotUpdateScreen(HOTSPOT_RX_START);
break;
case 93:
hotspotModemState = STATE_POCSAG;
trxTransmissionEnabled = false;
uiHotspotUpdateScreen(HOTSPOT_RX_IDLE);
break;
}
}
#endif
if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)
#if defined(PLATFORM_GD77S)
// There is no RED key on the GD-77S, use Orange button to exit the hotspot mode
|| ((ev->events & BUTTON_EVENT) && (ev->buttons & BUTTON_ORANGE))
#endif
)
{
// Do not permit to leave HS in MMDVMHost mode, otherwise that will mess up the communication
// and MMDVMHost won't recover from that, sometimes.
// Anyway, in MMDVMHost mode, there is a timeout after MMDVMHost stop responding (or went to shutdown).
if (nonVolatileSettings.hotspotType == HOTSPOT_TYPE_BLUEDV)
{
hotspotExit();
return false;
}
}
if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW))
{
uint8_t prevRxCmd = hotspotCurrentRxCommandState;
uiHotspotUpdateScreen(hotspotCurrentRxCommandState);
hotspotCurrentRxCommandState = prevRxCmd;
return true;
}
if (ev->events & BUTTON_EVENT)
{
// Display HS FW version
if ((hotspotData.displayFWVersion == false) && (ev->buttons == BUTTON_SK1))
{
uint8_t prevRxCmd = hotspotCurrentRxCommandState;
hotspotData.displayFWVersion = true;
uiHotspotUpdateScreen(hotspotCurrentRxCommandState);
hotspotCurrentRxCommandState = prevRxCmd;
return true;
}
else if (hotspotData.displayFWVersion && ((ev->buttons & BUTTON_SK1) == 0))
{
hotspotData.displayFWVersion = false;
uiHotspotUpdateScreen(hotspotCurrentRxCommandState);
return true;
}
}
return true;
}
void hotspotExit(void)
{
trxDisableTransmission();
if (trxTransmissionEnabled)
{
trxTransmissionEnabled = false;
//trxSetRX();
LedWrite(LED_GREEN, 0);
if (hotspotCwKeying)
{
cwReset();
hotspotCwKeying = false;
}
}
currentChannelData->libreDMR_Power = hotspotData.savedLibreDMR_Power;
trxTalkGroupOrPcId = hotspotData.savedTGorPC;// restore the current TG or PC
if (hotspotSavedPowerLevel != POWER_UNSET)
{
trxSetPowerFromLevel(hotspotSavedPowerLevel);
}
trxDMRID = codeplugGetUserDMRID();
settingsUsbMode = USB_MODE_CPS;
hotspotMmdvmHostIsConnected = false;
rxPowerSavingSetLevel(nonVolatileSettings.ecoLevel);
menuHotspotRestoreSettings();
menuSystemPopAllAndDisplayRootMenu();
}
static uint16_t getBatteryAverageInMillivolts(void)
{
int volts, mvolts;
getBatteryVoltage(&volts, &mvolts);
return ((volts * 1000) + (mvolts * 100));
}