625 lines
19 KiB
C
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, ¤tRec))
|
|
{
|
|
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));
|
|
}
|