Add delete_channel and keep_one_channel to the language table (after mute) and fill all 20 language files (13 translated, English fallback for the rest), with correct single-byte ISO-8859 accents. Wire them into the Delete Channel label, confirmation and last-channel guard. The new strings land on empty voice-table slots, so they play as silence (no crash, no .vpr regeneration). flash.sh now always rebuilds the .gla language packs so edits to the language files are actually picked up (previously a stale pack was re-flashed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4289 lines
134 KiB
C
4289 lines
134 KiB
C
/*
|
|
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
|
|
* 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 "user_interface/uiGlobals.h"
|
|
#include "functions/trx.h"
|
|
#include "user_interface/menuSystem.h"
|
|
#include "user_interface/uiUtilities.h"
|
|
#include "user_interface/uiLocalisation.h"
|
|
#include "functions/voicePrompts.h"
|
|
#include "functions/rxPowerSaving.h"
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
#include "hardware/radioHardwareInterface.h"
|
|
#endif
|
|
#include "interfaces/gps.h"
|
|
|
|
#if defined(HAS_COLOURS)
|
|
#define NAME_BUFFER_LEN 25
|
|
#else
|
|
#define NAME_BUFFER_LEN 22
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
uint16_t index;
|
|
int32_t distance;
|
|
} channelDistance_t;
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
typedef enum
|
|
{
|
|
GD77S_UIMODE_TG_OR_SQUELCH,
|
|
GD77S_UIMODE_SCAN,
|
|
GD77S_UIMODE_TS,
|
|
GD77S_UIMODE_CC,
|
|
GD77S_UIMODE_FILTER,
|
|
GD77S_UIMODE_DTMF_CONTACTS,
|
|
GD77S_UIMODE_ZONE,
|
|
GD77S_UIMODE_POWER,
|
|
GD77S_UIMODE_ECO,
|
|
GD77S_UIMODE_MAX
|
|
} GD77S_UIMODES_t;
|
|
|
|
typedef struct
|
|
{
|
|
bool firstRun;
|
|
GD77S_UIMODES_t uiMode;
|
|
bool channelOutOfBounds;
|
|
uint16_t dtmfListSelected;
|
|
int32_t dtmfListCount;
|
|
} GD77SParameters_t;
|
|
|
|
static GD77SParameters_t GD77SParameters =
|
|
{
|
|
.firstRun = true,
|
|
.uiMode = GD77S_UIMODE_TG_OR_SQUELCH,
|
|
.channelOutOfBounds = false,
|
|
.dtmfListSelected = 0,
|
|
.dtmfListCount = 0
|
|
};
|
|
|
|
static void buildSpeechUiModeForGD77S(GD77S_UIMODES_t uiMode);
|
|
|
|
static void checkAndUpdateSelectedChannelForGD77S(uint16_t chanNum, bool forceSpeech);
|
|
static void handleEventForGD77S(uiEvent_t *ev);
|
|
static uint16_t getCurrentChannelInCurrentZoneForGD77S(void);
|
|
|
|
#else // ! PLATFORM_GD77S
|
|
|
|
static void selectPrevNextZone(bool nextZone);
|
|
static void handleUpKey(uiEvent_t *ev);
|
|
static void handleDownKey(uiEvent_t *ev);
|
|
|
|
#endif // PLATFORM_GD77S
|
|
|
|
static void handleEvent(uiEvent_t *ev);
|
|
|
|
static void updateQuickMenuScreen(bool isFirstRun);
|
|
static void handleQuickMenuEvent(uiEvent_t *ev);
|
|
|
|
static void scanning(void);
|
|
static void scanStart(bool longPressBeep);
|
|
static void scanSearchForNextChannel(void);
|
|
static void scanApplyNextChannel(void);
|
|
static void scanStop(bool loadChannel);
|
|
static void updateTrxID(void);
|
|
static void initSortedChannels(void);
|
|
|
|
static char currentZoneName[SCREEN_LINE_BUFFER_SIZE];
|
|
static int directChannelNumber = 0;
|
|
|
|
static CodeplugChannel_t scanNextChannelData = { .rxFreq = 0 };
|
|
static bool scanNextChannelReady = false;
|
|
static int scanNextChannelIndex = 0;
|
|
static bool scobAlreadyTriggered = false;
|
|
static bool quickmenuChannelFromVFOHandled = false; // Quickmenu new channel confirmation window
|
|
static const uint32_t ROAMING_DISTANCE_KM_ARRAY[] = { 5, 10, 20 };
|
|
static const uint32_t ROAMING_POSITION_SAMPLING_PERIOD = 5000U;
|
|
|
|
static menuStatus_t menuChannelExitStatus = MENU_STATUS_SUCCESS;
|
|
static menuStatus_t menuQuickChannelExitStatus = MENU_STATUS_SUCCESS;
|
|
|
|
|
|
static void checkChannelLocation(CodeplugChannel_t *channelData)
|
|
{
|
|
if (settingsLocationIsValid() && codeplugChannelGetFlag(channelData, CHANNEL_FLAG_USE_LOCATION))
|
|
{
|
|
uint32_t tmp1 = channelData->locationLat2;
|
|
tmp1 = (tmp1 << 8) + channelData->locationLat1;
|
|
tmp1 = (tmp1 << 8) + channelData->locationLat0;
|
|
|
|
if (tmp1 != 0)
|
|
{
|
|
uint32_t tmp2 = channelData->locationLon2;
|
|
tmp2 = (tmp2 << 8) + channelData->locationLon1;
|
|
tmp2 = (tmp2 << 8) + channelData->locationLon0;
|
|
|
|
if (tmp2 != 0)
|
|
{
|
|
double lat = latLonFixed24ToDouble(tmp1);
|
|
double lon = latLonFixed24ToDouble(tmp2);
|
|
|
|
channelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = distanceToLocation(lat, lon) * 10;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
channelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = -1;
|
|
}
|
|
|
|
static menuStatus_t uiChannelModeFirstRun(void)
|
|
{
|
|
#if ! defined(PLATFORM_GD77S)
|
|
bool isLockMenu = (menuSystemGetPreviouslyPushedMenuNumber(true) == UI_LOCK_SCREEN);
|
|
// We're coming back from the lock screen (hence no channel init is needed, at all).
|
|
if (isLockMenu || lockscreenIsRearming)
|
|
{
|
|
if (isLockMenu)
|
|
{
|
|
menuSystemGetPreviouslyPushedMenuNumber(false); // Clear the previous lock screen trace
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
return MENU_STATUS_SUCCESS;
|
|
}
|
|
|
|
// GD77S speech can be triggered in main(), so let it ends
|
|
voicePromptsTerminate();
|
|
#endif
|
|
|
|
settingsSet(nonVolatileSettings.initialMenuNumber, (uint8_t) UI_CHANNEL_MODE);// This menu.
|
|
uiDataGlobal.displayChannelSettings = false;
|
|
scanNextChannelReady = false;
|
|
uiDataGlobal.Scan.refreshOnEveryStep = false;
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
|
|
lastHeardClearLastID();
|
|
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_IDLE;
|
|
currentChannelData = &channelScreenChannelData;// Need to set this as currentChannelData is used by functions called by loadChannelData()
|
|
|
|
if (currentChannelData->rxFreq != 0U)
|
|
{
|
|
uiChannelModeLoadChannelData(true, false);
|
|
}
|
|
else
|
|
{
|
|
uiChannelInitializeCurrentZone();
|
|
#if defined(PLATFORM_RD5R) || defined(PLATFORM_MD9600)
|
|
settingsSet(nonVolatileSettings.currentChannelIndexInZone, codeplugGetLastUsedChannelInCurrentZone());
|
|
#endif
|
|
uiChannelModeLoadChannelData(false, false);
|
|
}
|
|
|
|
if ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) && (trxGetMode() == RADIO_MODE_ANALOG))
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
}
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
trxSetPowerFromLevel(nonVolatileSettings.txPowerLevel);// ensure the power level is available for the Power announcement
|
|
|
|
//Reset to first UiMode if the radio is in Analog mode and the current UiMode only applies to DMR
|
|
if ((trxGetMode() == RADIO_MODE_ANALOG) &&
|
|
((GD77SParameters.uiMode == GD77S_UIMODE_TS ) ||
|
|
(GD77SParameters.uiMode == GD77S_UIMODE_CC ) ||
|
|
(GD77SParameters.uiMode == GD77S_UIMODE_FILTER )))
|
|
{
|
|
GD77SParameters.uiMode = GD77S_UIMODE_TG_OR_SQUELCH;
|
|
}
|
|
|
|
// Ensure the correct channel is loaded, on the very first run
|
|
if (GD77SParameters.firstRun)
|
|
{
|
|
if (voicePromptsIsPlaying() == false)
|
|
{
|
|
GD77SParameters.firstRun = false;
|
|
checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), true);
|
|
}
|
|
|
|
|
|
GD77SParameters.dtmfListCount = codeplugDTMFContactsGetCount();
|
|
}
|
|
#endif
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
if (uiDataGlobal.Scan.active == false)
|
|
{
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
}
|
|
|
|
// Need to do this last, as other things in the screen init, need to know whether the main screen has just changed
|
|
if (uiDataGlobal.VoicePrompts.inhibitInitial)
|
|
{
|
|
uiDataGlobal.VoicePrompts.inhibitInitial = false;
|
|
}
|
|
|
|
// Scan On Boot is enabled, but has to be run only once.
|
|
if (settingsIsOptionBitSet(BIT_SCAN_ON_BOOT_ENABLED) && (scobAlreadyTriggered == false))
|
|
{
|
|
scanStart(false);
|
|
}
|
|
|
|
// Disable ScOB for this session, and also prevent false triggering (like exiting the Options screen)
|
|
scobAlreadyTriggered = true;
|
|
|
|
return MENU_STATUS_SUCCESS; // Due to Orange Quick Menu
|
|
}
|
|
|
|
|
|
menuStatus_t uiChannelMode(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
static uint32_t m = 0;
|
|
|
|
if (isFirstRun)
|
|
{
|
|
return uiChannelModeFirstRun();
|
|
}
|
|
else
|
|
{
|
|
menuChannelExitStatus = MENU_STATUS_SUCCESS;
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
uiChannelModeHeartBeatActivityForGD77S(ev);
|
|
#else
|
|
// Don't go any further while APRS is beaconing
|
|
if (aprsBeaconingForcedManualBeaconingTriggered() && aprsBeaconingIsTransmitting())
|
|
{
|
|
return menuChannelExitStatus;
|
|
}
|
|
#endif
|
|
|
|
if (ev->events == NO_EVENT)
|
|
{
|
|
bool updateLHDisplay = ((nonVolatileSettings.lastTalkerOnScreenTimer > 0U) &&
|
|
(uiDataGlobal.lastHeardCount > 0) &&
|
|
(uiDataGlobal.Scan.active == false) &&
|
|
ticksTimerIsEnabled(&uiDataGlobal.DMRLastTalkerOnScreen.timer) &&
|
|
ticksTimerHasExpired(&uiDataGlobal.DMRLastTalkerOnScreen.timer) &&
|
|
((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) == 0));
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
// Just ensure rotary's selected channel is matching the already loaded one
|
|
// as rotary selector could be turned while the GD is OFF, or in hotspot mode.
|
|
if ((uiDataGlobal.Scan.active == false) && ((rotarySwitchGetPosition() != getCurrentChannelInCurrentZoneForGD77S()) || GD77SParameters.firstRun))
|
|
{
|
|
if (voicePromptsIsPlaying() == false)
|
|
{
|
|
checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), GD77SParameters.firstRun);
|
|
|
|
// Opening channel number announce has not took place yet, probably because it was telling
|
|
// parameter like new hotspot mode selection.
|
|
if (GD77SParameters.firstRun)
|
|
{
|
|
GD77SParameters.firstRun = false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// is there an incoming DMR signal
|
|
if ((uiDataGlobal.displayQSOState != QSO_DISPLAY_IDLE) || updateLHDisplay)
|
|
{
|
|
if (updateLHDisplay)
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
}
|
|
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
else
|
|
{
|
|
if ((ev->time - m) > RSSI_UPDATE_COUNTER_RELOAD)
|
|
{
|
|
if (rxPowerSavingIsRxOn())
|
|
{
|
|
if (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED))
|
|
{
|
|
#if defined(PLATFORM_RD5R)
|
|
displayClearRows(0, 1, false);
|
|
#else
|
|
displayClearRows(0, 2, false);
|
|
#endif
|
|
uiUtilityRenderHeader(false, false, ((ev->buttons & BUTTON_SK1) == BUTTON_SK1));
|
|
}
|
|
else
|
|
{
|
|
uiUtilityDrawRSSIBarGraph();
|
|
}
|
|
|
|
if (uiNotificationIsVisible())
|
|
{
|
|
displayRender();
|
|
}
|
|
else
|
|
{
|
|
// Only render the second row which contains the bar graph, if we're not scanning,
|
|
// as there is no need to redraw the rest of the screen
|
|
displayRenderRows(((uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)) ? 0 : 1), 2);
|
|
}
|
|
}
|
|
m = ev->time;
|
|
}
|
|
}
|
|
#if ! (defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017))
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
scanning();
|
|
}
|
|
#endif
|
|
|
|
if ((uiDataGlobal.Scan.active == false) &&
|
|
settingsLocationIsValid() &&
|
|
(settingsIsOptionBitSet(BIT_DISPLAY_CHANNEL_DISTANCE) || (nonVolatileSettings.roaming != ROAMING_OFF))
|
|
#if defined(HAS_GPS)
|
|
&& ((SETTINGS_GPS_MODE_GET(nonVolatileSettings) <= GPS_MODE_OFF) ||
|
|
((SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_ON) && ((gpsData.Status & (GPS_STATUS_HAS_FIX | GPS_STATUS_POSITION_UPDATED)) != 0)))
|
|
#endif
|
|
)
|
|
{
|
|
uint32_t m = ticksGetMillis();
|
|
|
|
if ((m - uiDataGlobal.lastDistanceMillis) > ROAMING_POSITION_SAMPLING_PERIOD)
|
|
{
|
|
bool newChannelLoaded = false;
|
|
|
|
uiDataGlobal.lastDistanceMillis = m;
|
|
#if defined(HAS_GPS)
|
|
gpsData.Status &= ~GPS_STATUS_POSITION_UPDATED; // Clear GPS position updated flag.
|
|
#endif
|
|
|
|
if ((nonVolatileSettings.roaming >= ROAMING_5KM) &&
|
|
(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false))
|
|
{
|
|
double lat1 = settingsLocationGetLatitude();
|
|
double lon1 = settingsLocationGetLongitude();
|
|
double d = distanceBetweenTwoCoords(lat1, lon1, uiDataGlobal.lastDistanceSortLocationLat, uiDataGlobal.lastDistanceSortLocationLon, true);
|
|
|
|
if (d >= (double)ROAMING_DISTANCE_KM_ARRAY[nonVolatileSettings.roaming - ROAMING_5KM])
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, 0);
|
|
|
|
uint16_t currentChannel = currentZone.channels[0];
|
|
|
|
initSortedChannels();
|
|
|
|
if (currentChannel != currentZone.channels[0])
|
|
{
|
|
newChannelLoaded = true;
|
|
uiChannelModeLoadChannelData(false, false);
|
|
displayLightTrigger(true);
|
|
soundSetMelody(MELODY_ACK_BEEP);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newChannelLoaded ||
|
|
(uiDataGlobal.displayQSOStatePrev == QSO_DISPLAY_IDLE) || (uiDataGlobal.displayQSOStatePrev == QSO_DISPLAY_DEFAULT_SCREEN))
|
|
{
|
|
checkChannelLocation(currentChannelData);
|
|
|
|
if (newChannelLoaded || (uiDataGlobal.lastDistanceDisplayed != currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10))
|
|
{
|
|
uiDataGlobal.lastDistanceDisplayed = currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ev->hasEvent)
|
|
{
|
|
handleEvent(ev);
|
|
}
|
|
}
|
|
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
scanning();
|
|
}
|
|
#endif
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
dtmfSequenceTick(false);
|
|
#endif
|
|
}
|
|
return menuChannelExitStatus;
|
|
}
|
|
|
|
static void hidesChannelDetails(void)
|
|
{
|
|
if (uiDataGlobal.displayChannelSettings)
|
|
{
|
|
uiDataGlobal.displayChannelSettings = false;
|
|
uiDataGlobal.displayQSOState = uiDataGlobal.displayQSOStatePrev;
|
|
|
|
// Maybe QSO State has been overridden, double check if we could now
|
|
// display QSO Data
|
|
if (uiDataGlobal.displayQSOState == QSO_DISPLAY_DEFAULT_SCREEN)
|
|
{
|
|
if (isQSODataAvailableForCurrentTalker())
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_CALLER_DATA;
|
|
}
|
|
}
|
|
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
|
|
static bool canCurrentZoneBeScanned(int *availableChannels)
|
|
{
|
|
int enabledChannels = 0;
|
|
int chanIdx = codeplugGetLastUsedChannelNumberInCurrentZone();
|
|
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1)
|
|
{
|
|
int chansInZone = currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone;
|
|
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
do
|
|
{
|
|
do
|
|
{
|
|
chanIdx = ((chanIdx % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1);
|
|
} while (!codeplugAllChannelsIndexIsInUse(chanIdx));
|
|
|
|
chansInZone--;
|
|
// Get flag4 only
|
|
codeplugChannelGetDataWithOffsetAndLengthForIndex(chanIdx, &scanNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1);
|
|
|
|
if (codeplugChannelGetFlag(&scanNextChannelData, CHANNEL_FLAG_ALL_SKIP) == 0)
|
|
{
|
|
enabledChannels++;
|
|
}
|
|
|
|
} while (chansInZone > 0);
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
chanIdx = ((chanIdx + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone);
|
|
|
|
chansInZone--;
|
|
// Get flag4 only
|
|
codeplugChannelGetDataWithOffsetAndLengthForIndex(currentZone.channels[chanIdx], &scanNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1);
|
|
|
|
if (codeplugChannelGetFlag(&scanNextChannelData, CHANNEL_FLAG_ZONE_SKIP) == 0)
|
|
{
|
|
enabledChannels++;
|
|
}
|
|
|
|
} while (chansInZone > 0);
|
|
}
|
|
}
|
|
|
|
*availableChannels = enabledChannels;
|
|
|
|
return (enabledChannels > 1);
|
|
}
|
|
|
|
static void scanSearchForNextChannel(void)
|
|
{
|
|
int channel = 0;
|
|
|
|
// All Channels virtual zone
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
do
|
|
{
|
|
do
|
|
{
|
|
// rollover (up/down) CODEPLUG_CHANNELS_MIN .. currentZone.NOT_IN_CODEPLUGDATA_highestIndex
|
|
scanNextChannelIndex = ((uiDataGlobal.Scan.direction == 1) ?
|
|
((scanNextChannelIndex % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1) :
|
|
((((scanNextChannelIndex - 1) + currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1));
|
|
} while (!codeplugAllChannelsIndexIsInUse(scanNextChannelIndex));
|
|
|
|
// Check if the channel is skipped.
|
|
// Get flag4 only
|
|
codeplugChannelGetDataWithOffsetAndLengthForIndex(scanNextChannelIndex, &scanNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1);
|
|
|
|
} while ((codeplugChannelGetFlag(&scanNextChannelData, CHANNEL_FLAG_ALL_SKIP) != 0));
|
|
|
|
channel = scanNextChannelIndex;
|
|
codeplugChannelGetDataForIndex(scanNextChannelIndex, &scanNextChannelData);
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
// rollover (up/down) 0 .. (currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1)
|
|
scanNextChannelIndex = ((uiDataGlobal.Scan.direction == 1) ?
|
|
((scanNextChannelIndex + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone) :
|
|
((scanNextChannelIndex + currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone));
|
|
|
|
// Check if the channel is skipped.
|
|
// Get flag4 only
|
|
codeplugChannelGetDataWithOffsetAndLengthForIndex(currentZone.channels[scanNextChannelIndex], &scanNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1);
|
|
|
|
} while ((codeplugChannelGetFlag(&scanNextChannelData, CHANNEL_FLAG_ZONE_SKIP) != 0));
|
|
|
|
channel = currentZone.channels[scanNextChannelIndex];
|
|
codeplugChannelGetDataForIndex(currentZone.channels[scanNextChannelIndex], &scanNextChannelData);
|
|
}
|
|
|
|
//check all nuisance delete entries and skip channel if there is a match
|
|
for (int i = 0; i < MAX_ZONE_SCAN_NUISANCE_CHANNELS; i++)
|
|
{
|
|
if (uiDataGlobal.Scan.nuisanceDelete[i] == -1)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if(uiDataGlobal.Scan.nuisanceDelete[i] == channel)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
scanNextChannelReady = true;
|
|
}
|
|
|
|
static void scanApplyNextChannel(void)
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, scanNextChannelIndex);
|
|
|
|
lastHeardClearLastID();
|
|
|
|
memcpy(&channelScreenChannelData, &scanNextChannelData, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE);
|
|
|
|
uiChannelModeLoadChannelData(true, false);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
// In DIGITAL Slow mode, we need at least 120ms to see the HR-C6000 to start the TS ISR.
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
int dwellTime;
|
|
if(uiDataGlobal.Scan.stepTimeMilliseconds > 150) // if >150ms use DMR Slow mode
|
|
{
|
|
dwellTime = ((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) ? SCAN_DMR_SIMPLEX_SLOW_MIN_DWELL_TIME : SCAN_DMR_DUPLEX_SLOW_MIN_DWELL_TIME);
|
|
}
|
|
else
|
|
{
|
|
dwellTime = ((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) ? SCAN_DMR_SIMPLEX_FAST_MIN_DWELL_TIME : (SCAN_DMR_DUPLEX_FAST_MIN_DWELL_TIME + SCAN_DMR_DUPLEX_FAST_EXTRA_DWELL_TIME));
|
|
}
|
|
|
|
uiDataGlobal.Scan.dwellTime = ((uiDataGlobal.Scan.stepTimeMilliseconds < dwellTime) ? dwellTime : uiDataGlobal.Scan.stepTimeMilliseconds);
|
|
}
|
|
else
|
|
{
|
|
uiDataGlobal.Scan.dwellTime = uiDataGlobal.Scan.stepTimeMilliseconds;
|
|
}
|
|
|
|
uiDataGlobal.Scan.timer.timeout = uiDataGlobal.Scan.dwellTime;
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
scanNextChannelReady = false;
|
|
}
|
|
|
|
void uiChannelModeLoadChannelData(bool useChannelDataInMemory, bool loadVoicePromptAnnouncement)
|
|
{
|
|
bool rxGroupValid = true;
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
bool wasLoadingZone = (currentChannelData->rxFreq == 0U);
|
|
#endif
|
|
int previousSelectedChannelNumber = uiDataGlobal.currentSelectedChannelNumber;
|
|
|
|
uiDataGlobal.currentSelectedChannelNumber = codeplugGetLastUsedChannelNumberInCurrentZone();
|
|
|
|
if (!useChannelDataInMemory)
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 0)
|
|
{
|
|
codeplugChannelGetDataForIndex(uiDataGlobal.currentSelectedChannelNumber, &channelScreenChannelData);
|
|
}
|
|
else
|
|
{
|
|
char buffer[SCREEN_LINE_BUFFER_SIZE];
|
|
|
|
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "! %.*s !", 12, currentLanguage->zone_empty); // Limits the language string to 12 characters.
|
|
|
|
codeplugUtilConvertStringToBuf(buffer, (char *)&channelScreenChannelData.name, 16);
|
|
channelScreenChannelData.chMode = RADIO_MODE_ANALOG;
|
|
channelScreenChannelData.txFreq = channelScreenChannelData.rxFreq = 14400000;
|
|
channelScreenChannelData.txTone = channelScreenChannelData.rxTone = CODEPLUG_CSS_TONE_NONE;
|
|
channelScreenChannelData.flag4 = CODEPLUG_CHANNEL_FLAG4_RX_ONLY;
|
|
channelScreenChannelData.sql = 10U;
|
|
channelScreenChannelData.NOT_IN_CODEPLUG_flag = 0x00;
|
|
channelScreenChannelData.NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = -1;
|
|
}
|
|
}
|
|
|
|
HRC6000ClearActiveDMRID();
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
// This could happen if a MK22 codeplug contains out of band channel(s) (like 220MHz) on an PLATFORM_MD9600.
|
|
if ((trxGetBandFromFrequency(currentChannelData->rxFreq) == FREQUENCY_OUT_OF_BAND) || (trxGetBandFromFrequency(currentChannelData->txFreq) == FREQUENCY_OUT_OF_BAND))
|
|
{
|
|
// Flag it as out of band channel
|
|
if (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND) == 0)
|
|
{
|
|
codeplugChannelSetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND, 1);
|
|
}
|
|
|
|
trxSetFrequency(OUT_OF_BAND_FALLBACK_FREQUENCY, OUT_OF_BAND_FALLBACK_FREQUENCY, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && (uiDataGlobal.reverseRepeaterChannel || codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO))) ? DMR_MODE_DMO : DMR_MODE_AUTO));
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
uint32_t rxFreq = (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->txFreq : currentChannelData->rxFreq);
|
|
uint32_t txFreq = (uiDataGlobal.talkaround ? rxFreq : (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->rxFreq : currentChannelData->txFreq));
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
// Was flagged as out of band channel, it was probably edited and now it's fine, clear the flag
|
|
if (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND) != 0)
|
|
{
|
|
codeplugChannelSetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND, 0);
|
|
}
|
|
#endif
|
|
|
|
// If reverseRepeater mode is enabled (and mode is DIGITAL), force to DMR Active mode.
|
|
trxSetFrequency(rxFreq, txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && (uiDataGlobal.reverseRepeaterChannel || codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO))) ? DMR_MODE_DMO : DMR_MODE_AUTO));
|
|
}
|
|
|
|
trxSetModeAndBandwidth(currentChannelData->chMode, (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_BW_25K) != 0));
|
|
|
|
if (currentChannelData->chMode == RADIO_MODE_ANALOG)
|
|
{
|
|
CodeplugAPRSConfig_t aprsConfig;
|
|
bool aprsConfigUnsetOrQSY = (currentChannelData->aprsConfigIndex == 0) ||
|
|
(codeplugAPRSGetDataForIndex(currentChannelData->aprsConfigIndex, &aprsConfig) && (aprsConfig.txFrequency != 0U));
|
|
|
|
if (aprsConfigUnsetOrQSY == false)
|
|
{
|
|
currentChannelData->rxTone = CODEPLUG_CSS_TONE_NONE;
|
|
currentChannelData->txTone = CODEPLUG_CSS_TONE_NONE;
|
|
codeplugChannelSetFlag(currentChannelData, CHANNEL_FLAG_VOX, 0);
|
|
}
|
|
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
|
|
}
|
|
else
|
|
{
|
|
uint32_t channelDMRId = codeplugChannelGetOptionalDMRID(currentChannelData);
|
|
|
|
if (uiDataGlobal.manualOverrideDMRId == 0)
|
|
{
|
|
if (channelDMRId == 0)
|
|
{
|
|
trxDMRID = uiDataGlobal.userDMRId;
|
|
}
|
|
else
|
|
{
|
|
trxDMRID = channelDMRId;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trxDMRID = uiDataGlobal.manualOverrideDMRId;
|
|
}
|
|
|
|
// Set CC when:
|
|
// - scanning
|
|
// - CC Filter is ON
|
|
// - CC Filter is OFF but not held anymore or loading a new channel (this avoids restoring Channel's CC when releasing the PTT key, or getting out of menus)
|
|
if (uiDataGlobal.Scan.active ||
|
|
((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) ||
|
|
(((nonVolatileSettings.dmrCcTsFilter & DMR_CC_FILTER_PATTERN) == 0) &&
|
|
((HRC6000CCIsHeld() == false) || (previousSelectedChannelNumber != uiDataGlobal.currentSelectedChannelNumber)))))
|
|
{
|
|
trxSetDMRColourCode(currentChannelData->txColor);
|
|
HRC6000ClearColorCodeSynchronisation();
|
|
}
|
|
|
|
if (currentChannelData->rxGroupList != lastLoadedRxGroup)
|
|
{
|
|
rxGroupValid = codeplugRxGroupGetDataForIndex(currentChannelData->rxGroupList, ¤tRxGroupData);
|
|
if (rxGroupValid)
|
|
{
|
|
lastLoadedRxGroup = currentChannelData->rxGroupList;
|
|
}
|
|
else
|
|
{
|
|
lastLoadedRxGroup = -1;// RxGroup Contacts are not valid
|
|
}
|
|
}
|
|
|
|
// Current contact index is out of group list bounds, select first contact
|
|
if (rxGroupValid && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)))
|
|
{
|
|
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0);
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
|
|
// Check if this channel has an Rx Group
|
|
if (rxGroupValid && nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup)
|
|
{
|
|
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData);
|
|
}
|
|
else
|
|
{
|
|
codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData);
|
|
}
|
|
|
|
trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData);
|
|
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
trxTalkGroupOrPcId = codeplugContactGetPackedId(¤tContactData);
|
|
}
|
|
else
|
|
{
|
|
trxTalkGroupOrPcId = nonVolatileSettings.overrideTG;
|
|
}
|
|
}
|
|
|
|
int nextMenu = menuSystemGetPreviouslyPushedMenuNumber(false); // used to determine if this screen has just been loaded after Tx ended (in loadChannelData()))
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
if (((uiDataGlobal.VoicePrompts.inhibitInitial == false) || loadVoicePromptAnnouncement) &&
|
|
((uiDataGlobal.Scan.active == false) ||
|
|
(uiDataGlobal.Scan.active && ((uiDataGlobal.Scan.state == SCAN_STATE_SHORT_PAUSED) || (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)))))
|
|
{
|
|
announceItem((wasLoadingZone ? PROMPT_SEQUENCE_ZONE_NAME_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE : PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE),
|
|
((nextMenu == UI_TX_SCREEN) || (nextMenu == UI_LOCK_SCREEN) || (nextMenu == UI_PRIVATE_CALL) || uiDataGlobal.Scan.active) ? PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY : PROMPT_THRESHOLD_2);
|
|
}
|
|
#else
|
|
(void)nextMenu;
|
|
// Force GD77S to always use the Master power level
|
|
currentChannelData->libreDMR_Power = 0x00;
|
|
#endif
|
|
|
|
if (uiDataGlobal.Scan.active == false)
|
|
{
|
|
checkChannelLocation(currentChannelData);
|
|
|
|
if (useChannelDataInMemory == false)
|
|
{
|
|
aprsBeaconingResetTimers();
|
|
}
|
|
}
|
|
}
|
|
|
|
void uiChannelModeUpdateScreen(int txTimeSecs)
|
|
{
|
|
char nameBuf[NAME_BUFFER_LEN];
|
|
char buffer[SCREEN_LINE_BUFFER_SIZE];
|
|
#if defined(PLATFORM_VARIANT_DM1701)
|
|
bool dblHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
|
|
#endif
|
|
bool isTransmitting = isTransmittingIgnoringAPRSBeaconing();
|
|
|
|
// Only render the header, then wait for the next run
|
|
// Otherwise the screen could remain blank if TG and PC are == 0
|
|
// since uiDataGlobal.displayQSOState won't be set to QSO_DISPLAY_IDLE
|
|
if ((trxGetMode() == RADIO_MODE_DIGITAL) && (HRC6000GetReceivedTgOrPcId() == 0) &&
|
|
((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) || (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA_UPDATE)))
|
|
{
|
|
uiUtilityRedrawHeaderOnly(false, false, false);
|
|
return;
|
|
}
|
|
|
|
// We're currently displaying details, and it shouldn't be overridden by QSO data
|
|
if (uiDataGlobal.displayChannelSettings && ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA)
|
|
|| (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA_UPDATE)))
|
|
{
|
|
// We will not restore the previous QSO Data as a new caller just arose.
|
|
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
}
|
|
|
|
displayClearBuf();
|
|
uiUtilityRenderHeader(false, false,
|
|
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
|
|
false
|
|
#else
|
|
((buttonsRead() & BUTTON_SK1) == BUTTON_SK1)
|
|
#endif
|
|
);
|
|
|
|
switch(uiDataGlobal.displayQSOState)
|
|
{
|
|
case QSO_DISPLAY_DEFAULT_SCREEN:
|
|
lastHeardClearLastID();
|
|
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiDataGlobal.isDisplayingQSOData = false;
|
|
uiDataGlobal.receivedPcId = 0x00;
|
|
|
|
if (isTransmitting)
|
|
{
|
|
int8_t yOverride =
|
|
#if defined(PLATFORM_VARIANT_DM1701)
|
|
// Because of the screen height reduction, we need to move Contact
|
|
// and Channel name slightly higher when double height font is enabled.
|
|
(DISPLAY_Y_POS_TX_TIMER - (dblHeight ? 4 : 0));
|
|
#else
|
|
-1;
|
|
#endif
|
|
|
|
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, " %d ", txTimeSecs);
|
|
uiUtilityDisplayInformation(buffer, DISPLAY_INFO_TX_TIMER, yOverride);
|
|
}
|
|
else
|
|
{
|
|
// Display some channel settings
|
|
if (uiDataGlobal.displayChannelSettings)
|
|
{
|
|
uint32_t rxFreq = (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->txFreq : currentChannelData->rxFreq);
|
|
uint32_t txFreq = (uiDataGlobal.talkaround ? rxFreq : (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->rxFreq : currentChannelData->txFreq));
|
|
|
|
uiUtilityDisplayInformation(NULL, DISPLAY_INFO_CHANNEL_DETAILS, -1);
|
|
|
|
uiUtilityDisplayFrequency(DISPLAY_Y_POS_RX_FREQ, false, false, rxFreq, false, false, 0);
|
|
uiUtilityDisplayFrequency(DISPLAY_Y_POS_TX_FREQ, true, false, txFreq, false, false, 0);
|
|
}
|
|
else
|
|
{
|
|
bool channelIsSkipped = (((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED))) &&
|
|
((CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_ALL_SKIP)) ||
|
|
((CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_ZONE_SKIP))));
|
|
|
|
if (directChannelNumber > 0)
|
|
{
|
|
snprintf(nameBuf, NAME_BUFFER_LEN, "%s %d", currentLanguage->gotoChannel, directChannelNumber);
|
|
}
|
|
else
|
|
{
|
|
if (((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED))) &&
|
|
((nonVolatileSettings.roaming != ROAMING_OFF) || (settingsIsOptionBitSet(BIT_DISPLAY_CHANNEL_DISTANCE) && (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false))))
|
|
{
|
|
if (currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 != -1)
|
|
{
|
|
int decLen;
|
|
char distBuf[SCREEN_LINE_BUFFER_SIZE];
|
|
char zNameBuf[SCREEN_LINE_BUFFER_SIZE];
|
|
uint32_t intPart = (currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 / 10);
|
|
uint32_t decPart = (currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 - (intPart * 10));
|
|
|
|
if (decPart != 0)
|
|
{
|
|
decLen = snprintf(distBuf, SCREEN_LINE_BUFFER_SIZE, "%c %u.%u km", (channelIsSkipped ? '^' : '|'), intPart, decPart);
|
|
}
|
|
else
|
|
{
|
|
decLen = snprintf(distBuf, SCREEN_LINE_BUFFER_SIZE, "%c %u km", (channelIsSkipped ? '^' : '|'), intPart);
|
|
}
|
|
|
|
int zLen = snprintf(zNameBuf, SCREEN_LINE_BUFFER_SIZE, "%s", currentZoneName);
|
|
|
|
// Truncate (with ellipsis) the zone name if the whole string doesn't fit the screen
|
|
if ((zLen + decLen + 1) > (NAME_BUFFER_LEN - 1))
|
|
{
|
|
int nOffset = MAX(0, (zLen - (((decLen + zLen + 1) - (NAME_BUFFER_LEN - 1)) + 3)));
|
|
|
|
memset(zNameBuf + nOffset, '.', 3);
|
|
zNameBuf[nOffset + 3] = 0;
|
|
}
|
|
|
|
snprintf(nameBuf, NAME_BUFFER_LEN, "%s %s", zNameBuf, distBuf);
|
|
}
|
|
else
|
|
{
|
|
snprintf(nameBuf, NAME_BUFFER_LEN, "%s %c --- km", currentZoneName, (channelIsSkipped ? '^' : '|'));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
snprintf(nameBuf, NAME_BUFFER_LEN, "%s Ch%c%d",
|
|
(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) ? currentLanguage->all_channels : currentZoneName),
|
|
(channelIsSkipped ? '^' : ':'),
|
|
(codeplugGetLastUsedChannelInCurrentZone() + (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) ? 0 : 1)));
|
|
}
|
|
}
|
|
|
|
uiUtilityDisplayInformation(nameBuf, DISPLAY_INFO_ZONE, -2);
|
|
}
|
|
}
|
|
|
|
if (!uiDataGlobal.displayChannelSettings)
|
|
{
|
|
int8_t yOverride = (isTransmitting ?
|
|
DISPLAY_Y_POS_CHANNEL_SECOND_LINE
|
|
#if defined(PLATFORM_VARIANT_DM1701)
|
|
// Because of the screen height reduction, we need to move Contact
|
|
// and Channel name slightly higher when double height font is enabled.
|
|
- (dblHeight ? 4 : 0)
|
|
#endif
|
|
: -1);
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
if (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND) != 0)
|
|
{
|
|
snprintf(nameBuf, NAME_BUFFER_LEN, "%s", currentLanguage->out_of_band);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
codeplugUtilConvertBufToString(currentChannelData->name, nameBuf, 16);
|
|
}
|
|
|
|
uiUtilityDisplayInformation(nameBuf, (uiDataGlobal.reverseRepeaterChannel ? DISPLAY_INFO_CHANNEL_INVERTED : DISPLAY_INFO_CHANNEL), yOverride);
|
|
}
|
|
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (!uiDataGlobal.displayChannelSettings)
|
|
{
|
|
int8_t yOverride = 0;
|
|
|
|
if ((trxTransmissionEnabled == false) && (aprsBeaconingIsTransmitting() == false) &&
|
|
(uiDataGlobal.Scan.active == false) &&
|
|
((nonVolatileSettings.lastTalkerOnScreenTimer > 0U) &&
|
|
ticksTimerHasExpired(&uiDataGlobal.DMRLastTalkerOnScreen.timer))
|
|
)
|
|
{
|
|
ticksTimerStart(&uiDataGlobal.DMRLastTalkerOnScreen.timer, (nonVolatileSettings.lastTalkerOnScreenTimer * 1000U));
|
|
uiDataGlobal.DMRLastTalkerOnScreen.visible = (((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) || (uiDataGlobal.lastHeardCount < 1)) ? false : !uiDataGlobal.DMRLastTalkerOnScreen.visible);
|
|
|
|
if (uiDataGlobal.DMRLastTalkerOnScreen.visible)
|
|
{
|
|
if (uiDataGlobal.lastHeardCount > 0)
|
|
{
|
|
bool displayTA = false;
|
|
LinkItem_t *item = LinkHead;
|
|
|
|
switch (nonVolatileSettings.contactDisplayPriority)
|
|
{
|
|
case CONTACT_DISPLAY_PRIO_CC_DB_TA:
|
|
case CONTACT_DISPLAY_PRIO_DB_CC_TA:
|
|
// No contact found in codeplug and DMRIDs, use TA as fallback, if any.
|
|
if ((strncmp(item->contact, "ID:", 3) == 0) && (item->talkerAlias[0] != 0x00))
|
|
{
|
|
displayTA = true;
|
|
}
|
|
break;
|
|
|
|
case CONTACT_DISPLAY_PRIO_TA_CC_DB:
|
|
case CONTACT_DISPLAY_PRIO_TA_DB_CC:
|
|
if (item->talkerAlias[0] != 0x00)
|
|
{
|
|
displayTA = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (displayTA)
|
|
{
|
|
getCallsignOnly(nameBuf, item->talkerAlias);
|
|
}
|
|
else
|
|
{
|
|
getCallsignOnly(nameBuf, item->contact);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto displayContactName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto displayContactName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
displayContactName:
|
|
|
|
if (nonVolatileSettings.overrideTG != 0)
|
|
{
|
|
yOverride = (isTransmitting ?
|
|
DISPLAY_Y_POS_CONTACT_TX_FRAME
|
|
#if defined(PLATFORM_VARIANT_DM1701)
|
|
// Because of the screen height reduction, we need to move Contact
|
|
// and Channel name slightly higher when double height font is enabled.
|
|
- (dblHeight ? 8 : 0)
|
|
#endif
|
|
: -1);
|
|
|
|
uiUtilityBuildTgOrPCDisplayName(nameBuf, SCREEN_LINE_BUFFER_SIZE);
|
|
uiUtilityDisplayInformation(NULL, DISPLAY_INFO_CONTACT_OVERRIDE_FRAME, yOverride);
|
|
}
|
|
else
|
|
{
|
|
codeplugUtilConvertBufToString(currentContactData.name, nameBuf, 16);
|
|
}
|
|
}
|
|
|
|
yOverride = (isTransmitting ?
|
|
DISPLAY_Y_POS_CONTACT_TX
|
|
#if defined(PLATFORM_VARIANT_DM1701)
|
|
// Because of the screen height reduction, we need to move Contact
|
|
// and Channel name slightly higher when double height font is enabled.
|
|
- (dblHeight ? 8 : 0)
|
|
#endif
|
|
: -1);
|
|
|
|
uiUtilityDisplayInformation(nameBuf, DISPLAY_INFO_CONTACT, yOverride);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (ticksTimerIsEnabled(&uiDataGlobal.DMRLastTalkerOnScreen.timer))
|
|
{
|
|
ticksTimerReset(&uiDataGlobal.DMRLastTalkerOnScreen.timer);
|
|
uiDataGlobal.DMRLastTalkerOnScreen.visible = false;
|
|
}
|
|
}
|
|
|
|
displayRender();
|
|
break;
|
|
|
|
case QSO_DISPLAY_CALLER_DATA:
|
|
case QSO_DISPLAY_CALLER_DATA_UPDATE:
|
|
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_CALLER_DATA;
|
|
uiDataGlobal.isDisplayingQSOData = true;
|
|
uiDataGlobal.displayChannelSettings = false;
|
|
uiUtilityRenderQSOData();
|
|
displayRender();
|
|
break;
|
|
|
|
case QSO_DISPLAY_IDLE:
|
|
break;
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_IDLE;
|
|
}
|
|
|
|
static void handleEvent(uiEvent_t *ev)
|
|
{
|
|
#if defined(PLATFORM_GD77S)
|
|
handleEventForGD77S(ev);
|
|
return;
|
|
#else
|
|
|
|
if (uiDataGlobal.Scan.active && (ev->events & KEY_EVENT))
|
|
{
|
|
// Key pressed during scanning
|
|
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)
|
|
{
|
|
// if we are scanning and down key is pressed then enter current channel into nuisance delete array.
|
|
if((uiDataGlobal.Scan.state == SCAN_STATE_PAUSED) &&
|
|
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)
|
|
(ev->keys.key == KEY_STAR)
|
|
#else
|
|
(ev->keys.key == KEY_RIGHT)
|
|
#endif
|
|
)
|
|
{
|
|
// There is two channels available in the Zone, just stop scanning
|
|
if (uiDataGlobal.Scan.nuisanceDeleteIndex == (uiDataGlobal.Scan.availableChannelsCount - 2))
|
|
{
|
|
uiDataGlobal.Scan.lastIteration = true;
|
|
}
|
|
|
|
uiDataGlobal.Scan.nuisanceDelete[uiDataGlobal.Scan.nuisanceDeleteIndex] = uiDataGlobal.currentSelectedChannelNumber;
|
|
uiDataGlobal.Scan.nuisanceDeleteIndex = (uiDataGlobal.Scan.nuisanceDeleteIndex + 1) % MAX_ZONE_SCAN_NUISANCE_CHANNELS;
|
|
uiDataGlobal.Scan.timer.timeout = SCAN_SKIP_CHANNEL_INTERVAL; //force scan to continue;
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
keyboardReset();
|
|
return;
|
|
}
|
|
|
|
// KEY_DOWN/KEY_LEFT reverse the scan direction
|
|
if (
|
|
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)
|
|
(ev->keys.key == KEY_FRONT_DOWN)
|
|
#else
|
|
(ev->keys.key == KEY_LEFT)
|
|
#endif
|
|
)
|
|
{
|
|
uiDataGlobal.Scan.direction *= -1;
|
|
keyboardReset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// stop the scan on any button except:
|
|
// - MD9600: UP without Shift (allows scan to be manually continued), or Right
|
|
// - MDUV380/DM1701/MD2017: UP/ROTARY_INC without SK2 (allows scan to be manually continued)
|
|
// - MK22: UP without Shift (allows scan to be manually continued)
|
|
// or SK2 on its own (allows Backlight to be triggered)
|
|
if ((
|
|
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
((ev->keys.key == KEY_FRONT_UP) || (ev->keys.key == KEY_ROTARY_INCREMENT))
|
|
#elif defined(PLATFORM_MD9600)
|
|
((ev->keys.key == KEY_UP) || (ev->keys.key == KEY_RIGHT))
|
|
#else
|
|
(ev->keys.key == KEY_UP)
|
|
#endif
|
|
&& (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)) == false)
|
|
{
|
|
scanStop(true);
|
|
keyboardReset();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ev->events & FUNCTION_EVENT)
|
|
{
|
|
if (ev->function == FUNC_START_SCANNING)
|
|
{
|
|
directChannelNumber = 0;
|
|
if (uiDataGlobal.Scan.active == false)
|
|
{
|
|
scanStart(false);
|
|
}
|
|
return;
|
|
}
|
|
else if (ev->function == FUNC_REDRAW)
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (handleMonitorMode(ev))
|
|
{
|
|
uiDataGlobal.displayChannelSettings = false;
|
|
return;
|
|
}
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (rebuildVoicePromptOnExtraLongSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t tg = (LinkHead->talkGroupOrPcId & 0xFFFFFF);
|
|
|
|
// If Blue button is pressed during reception it sets the Tx TG to the incoming TG
|
|
if (uiDataGlobal.isDisplayingQSOData && BUTTONCHECK_SHORTUP(ev, BUTTON_SK2) && (trxGetMode() == RADIO_MODE_DIGITAL) &&
|
|
((trxTalkGroupOrPcId != tg) ||
|
|
((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot())) ||
|
|
(trxGetDMRColourCode() != currentChannelData->txColor)))
|
|
{
|
|
lastHeardClearLastID();
|
|
|
|
// Set TS to overriden TS
|
|
if ((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot()))
|
|
{
|
|
trxSetDMRTimeSlot(dmrMonitorCapturedTS, false);
|
|
tsSetManualOverride(CHANNEL_CHANNEL, (dmrMonitorCapturedTS + 1));
|
|
}
|
|
|
|
if (trxTalkGroupOrPcId != tg)
|
|
{
|
|
trxTalkGroupOrPcId = tg;
|
|
settingsSet(nonVolatileSettings.overrideTG, trxTalkGroupOrPcId);
|
|
}
|
|
|
|
currentChannelData->txColor = trxGetDMRColourCode();// Set the CC to the current CC, which may have been determined by the CC finding algorithm in C6000.c
|
|
|
|
announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC,PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY);
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_CALLER_DATA_UPDATE;
|
|
soundSetMelody(MELODY_ACK_BEEP);
|
|
return;
|
|
}
|
|
|
|
// SK2 short tap toggles audio mute. The DMR TG-set handler above consumes the
|
|
// tap (and returns) only while a QSO is on screen, so this won't fire then.
|
|
if (BUTTONCHECK_SHORTUP(ev, BUTTON_SK2) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0) && (monitorModeData.isEnabled == false))
|
|
{
|
|
toggleAudioMute();
|
|
uiChannelModeUpdateScreen(0);
|
|
return;
|
|
}
|
|
|
|
// Display channel settings (RX/TX/etc) while SK1 is pressed
|
|
if ((uiDataGlobal.displayChannelSettings == false) && (monitorModeData.isEnabled == false) && BUTTONCHECK_DOWN(ev, BUTTON_SK1))
|
|
{
|
|
if ((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)))
|
|
{
|
|
int prevQSODisp = uiDataGlobal.displayQSOStatePrev;
|
|
uiDataGlobal.displayChannelSettings = true;
|
|
headerRowIsDirty = true;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
uiDataGlobal.displayQSOStatePrev = prevQSODisp;
|
|
}
|
|
return;
|
|
|
|
}
|
|
else if (uiDataGlobal.displayChannelSettings && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0))
|
|
{
|
|
hidesChannelDetails();
|
|
return;
|
|
}
|
|
|
|
#if !defined(PLATFORM_RD5R)
|
|
if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0))
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
announceItem(PROMPT_SEQUENCE_BATTERY, AUDIO_PROMPT_MODE_VOICE_LEVEL_1);
|
|
}
|
|
else
|
|
{
|
|
// Quick Menu
|
|
menuSystemPushNewMenu(UI_CHANNEL_QUICK_MENU);
|
|
|
|
// Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event
|
|
ev->keys.event |= KEY_MOD_UP;
|
|
ev->keys.key = 127;
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
// End Trick
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (ev->events & KEY_EVENT)
|
|
{
|
|
#if defined(PLATFORM_MD9600)
|
|
if (KEYCHECK_LONGDOWN(ev->keys, KEY_GREEN))
|
|
{
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
uiChannelModeStopScanning();
|
|
}
|
|
|
|
menuSystemPushNewMenu(UI_CHANNEL_QUICK_MENU);
|
|
|
|
// Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event
|
|
ev->keys.event |= KEY_MOD_UP;
|
|
ev->keys.key = 127;
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
// End Trick
|
|
return;
|
|
}
|
|
else
|
|
#endif
|
|
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
if (directChannelNumber > 0)
|
|
{
|
|
if(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
// All Channels virtual zone
|
|
if (codeplugAllChannelsIndexIsInUse(directChannelNumber))
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, directChannelNumber);
|
|
uiChannelModeLoadChannelData(false, true);
|
|
|
|
}
|
|
else
|
|
{
|
|
soundSetMelody(MELODY_ERROR_BEEP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((directChannelNumber - 1) < currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone)
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, (directChannelNumber - 1));
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
else
|
|
{
|
|
soundSetMelody(MELODY_ERROR_BEEP);
|
|
}
|
|
|
|
}
|
|
directChannelNumber = 0;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
else if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
menuSystemPushNewMenu(MENU_CHANNEL_DETAILS);
|
|
}
|
|
else
|
|
{
|
|
menuSystemPushNewMenu(MENU_MAIN_MENU);
|
|
}
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_HASH))
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
// Assignment for SK2 + #
|
|
menuSystemPushNewMenu(MENU_CONTACT_QUICKLIST);
|
|
}
|
|
else
|
|
{
|
|
menuSystemPushNewMenu(MENU_NUMERICAL_ENTRY);
|
|
}
|
|
return;
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_RED) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_RED) == false) && BUTTONCHECK_DOWN(ev, BUTTON_SK1))
|
|
{
|
|
uiChannelModeOrVFOModeThemeDaytimeChange(false, true);
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK1))
|
|
{
|
|
uiChannelModeOrVFOModeThemeDaytimeChange(true, true);
|
|
return;
|
|
}
|
|
else if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) && (uiDataGlobal.tgBeforePcMode != 0))
|
|
{
|
|
settingsSet(nonVolatileSettings.overrideTG, uiDataGlobal.tgBeforePcMode);
|
|
menuPrivateCallClear();
|
|
|
|
updateTrxID();
|
|
uiDataGlobal.displayQSOState= QSO_DISPLAY_DEFAULT_SCREEN;// Force redraw
|
|
uiChannelModeUpdateScreen(0);
|
|
return;// The event has been handled
|
|
}
|
|
|
|
if(directChannelNumber > 0)
|
|
{
|
|
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY);
|
|
|
|
directChannelNumber = 0;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
else
|
|
{
|
|
#if ! (defined(PLATFORM_DM1801) || defined(PLATFORM_RD5R))
|
|
menuSystemSetCurrentMenu(UI_VFO_MODE);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
#if defined(PLATFORM_DM1801) || defined(PLATFORM_RD5R)
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_VFO_MR))
|
|
{
|
|
directChannelNumber = 0;
|
|
menuSystemSetCurrentMenu(UI_VFO_MODE);
|
|
return;
|
|
}
|
|
#endif
|
|
#if defined(PLATFORM_RD5R)
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_VFO_MR) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0))
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
announceItem(PROMPT_SEQUENCE_BATTERY, AUDIO_PROMPT_MODE_VOICE_LEVEL_1);
|
|
}
|
|
else
|
|
{
|
|
menuSystemPushNewMenu(UI_CHANNEL_QUICK_MENU);
|
|
|
|
// Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event
|
|
ev->keys.event |= KEY_MOD_UP;
|
|
ev->keys.key = 127;
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
// End Trick
|
|
}
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_INCREASE) // set as KEY_RIGHT on some platforms
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
|
|
&& BUTTONCHECK_DOWN(ev, BUTTON_SK2)
|
|
#endif
|
|
)
|
|
{
|
|
// Long press allows the 5W+ power setting to be selected immediately
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
|
|
if (increasePowerLevel(true))
|
|
{
|
|
uiUtilityRedrawHeaderOnly(false, false, false);
|
|
}
|
|
#else
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
if (increasePowerLevel(true))
|
|
{
|
|
uiUtilityRedrawHeaderOnly(false, false, false);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_INCREASE)) // set as KEY_RIGHT on some platforms
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
|
|
{
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
handleUpKey(ev);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
if (increasePowerLevel(false))
|
|
{
|
|
uiUtilityRedrawHeaderOnly(false, false, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1)
|
|
{
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1);
|
|
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1))
|
|
{
|
|
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0);
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden
|
|
menuPrivateCallClear();
|
|
updateTrxID();
|
|
// We're in digital mode, RXing, and current talker is already at the top of last heard list,
|
|
// hence immediately display complete contact/TG info on screen
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;//(isQSODataAvailableForCurrentTalker() ? QSO_DISPLAY_CALLER_DATA : QSO_DISPLAY_DEFAULT_SCREEN);
|
|
if (isQSODataAvailableForCurrentTalker())
|
|
{
|
|
(void)addTimerCallback(uiUtilityRenderQSODataAndUpdateScreen, 2000, UI_CHANNEL_MODE, true);
|
|
}
|
|
uiChannelModeUpdateScreen(0);
|
|
announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3);
|
|
}
|
|
else
|
|
{
|
|
if(currentChannelData->sql == 0) //If we were using default squelch level
|
|
{
|
|
currentChannelData->sql = nonVolatileSettings.squelchDefaults[currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point.
|
|
}
|
|
|
|
if (currentChannelData->sql < CODEPLUG_MAX_VARIABLE_SQUELCH)
|
|
{
|
|
currentChannelData->sql++;
|
|
}
|
|
|
|
announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3);
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiNotificationShow(NOTIFICATION_TYPE_SQUELCH, NOTIFICATION_ID_SQUELCH, 1000, NULL, false);
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
|
|
}
|
|
#endif
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_DECREASE)) // set as KEY_LEFT on some platforms
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
if (decreasePowerLevel())
|
|
{
|
|
uiUtilityRedrawHeaderOnly(false, false, false);
|
|
}
|
|
|
|
if (trxGetPowerLevel() == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1)
|
|
{
|
|
// To Do change TG in on same channel freq
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
settingsDecrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1);
|
|
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < 0)
|
|
{
|
|
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE],
|
|
(int16_t) (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1));
|
|
}
|
|
|
|
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden
|
|
menuPrivateCallClear();
|
|
updateTrxID();
|
|
// We're in digital mode, RXing, and current talker is already at the top of last heard list,
|
|
// hence immediately display complete contact/TG info on screen
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;//(isQSODataAvailableForCurrentTalker() ? QSO_DISPLAY_CALLER_DATA : QSO_DISPLAY_DEFAULT_SCREEN);
|
|
if (isQSODataAvailableForCurrentTalker())
|
|
{
|
|
(void)addTimerCallback(uiUtilityRenderQSODataAndUpdateScreen, 2000, UI_CHANNEL_MODE, true);
|
|
}
|
|
uiChannelModeUpdateScreen(0);
|
|
announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC,PROMPT_THRESHOLD_3);
|
|
}
|
|
else
|
|
{
|
|
if(currentChannelData->sql == 0) //If we were using default squelch level
|
|
{
|
|
currentChannelData->sql = nonVolatileSettings.squelchDefaults[currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point.
|
|
}
|
|
|
|
if (currentChannelData->sql > CODEPLUG_MIN_VARIABLE_SQUELCH)
|
|
{
|
|
currentChannelData->sql--;
|
|
}
|
|
|
|
announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3);
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiNotificationShow(NOTIFICATION_TYPE_SQUELCH, NOTIFICATION_ID_SQUELCH, 1000, NULL, false);
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_STAR))
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) // Toggle Channel Mode
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_ANALOG)
|
|
{
|
|
currentChannelData->chMode = RADIO_MODE_DIGITAL;
|
|
uiDataGlobal.VoicePrompts.inhibitInitial = true;// Stop VP playing in loadChannelData
|
|
uiChannelModeLoadChannelData(true, false);
|
|
uiDataGlobal.VoicePrompts.inhibitInitial = false;
|
|
menuChannelExitStatus |= MENU_STATUS_FORCE_FIRST;
|
|
}
|
|
else
|
|
{
|
|
currentChannelData->chMode = RADIO_MODE_ANALOG;
|
|
trxSetModeAndBandwidth(currentChannelData->chMode, (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_BW_25K) != 0));
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
|
|
}
|
|
|
|
announceItem(PROMPT_SEQUENCE_MODE, PROMPT_THRESHOLD_1);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
else
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
// Toggle timeslot
|
|
trxSetDMRTimeSlot(1 - trxGetDMRTimeSlot(), true);
|
|
tsSetManualOverride(CHANNEL_CHANNEL, (trxGetDMRTimeSlot() + 1));
|
|
|
|
if ((nonVolatileSettings.overrideTG == 0) && (currentContactData.reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00)
|
|
{
|
|
tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, true);
|
|
}
|
|
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
lastHeardClearLastID();
|
|
uiDataGlobal.displayQSOState = uiDataGlobal.displayQSOStatePrev;
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
if (trxGetDMRTimeSlot() == 0)
|
|
{
|
|
menuChannelExitStatus |= MENU_STATUS_FORCE_FIRST;
|
|
}
|
|
announceItem(PROMPT_SEQUENCE_TS,PROMPT_THRESHOLD_3);
|
|
}
|
|
else
|
|
{
|
|
uint8_t bw25k = codeplugChannelSetFlag(currentChannelData, CHANNEL_FLAG_BW_25K, !(codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_BW_25K)));
|
|
|
|
if (bw25k)
|
|
{
|
|
nextKeyBeepMelody = (int16_t *)MELODY_KEY_BEEP_FIRST_ITEM;
|
|
}
|
|
|
|
// ToDo announce VP for bandwidth perhaps
|
|
trxSetModeAndBandwidth(RADIO_MODE_ANALOG, (bw25k != 0));
|
|
soundSetMelody(MELODY_NACK_BEEP);
|
|
headerRowIsDirty = true;
|
|
uiDataGlobal.displayQSOState = uiDataGlobal.displayQSOStatePrev;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_STAR) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);
|
|
tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, false);
|
|
|
|
if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup))
|
|
{
|
|
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData);
|
|
}
|
|
else
|
|
{
|
|
codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData);
|
|
}
|
|
|
|
trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData);
|
|
|
|
lastHeardClearLastID();
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_1);
|
|
}
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_HASH) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_HASH) == false))
|
|
{
|
|
uiDataGlobal.talkaround = false; // Talkaround and reverseRepeater are mutually exclusive.
|
|
uiDataGlobal.reverseRepeaterChannel = !uiDataGlobal.reverseRepeaterChannel;
|
|
|
|
uint32_t rxFreq = (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->txFreq : currentChannelData->rxFreq);
|
|
uint32_t txFreq = (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->rxFreq : currentChannelData->txFreq);
|
|
|
|
// If reverseRepeater mode is enabled (and mode is DIGITAL), force to DMR Active mode.
|
|
trxSetFrequency(rxFreq, txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && (uiDataGlobal.reverseRepeaterChannel || codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO))) ? DMR_MODE_DMO : DMR_MODE_AUTO));
|
|
|
|
// Play a special beep when Reverse Repeater is enabled or disabled.
|
|
soundSetMelody((uiDataGlobal.reverseRepeaterChannel ? MELODY_KEY_BEEP_FIRST_ITEM : MELODY_BEEP_LOW_SHORT));
|
|
nextKeyBeepMelody = NULL;
|
|
ev->keys.event &= ~(KEY_MOD_LONG | KEY_MOD_DOWN | KEY_MOD_PRESS | KEY_MOD_UP);
|
|
ev->keys.key = 0;
|
|
|
|
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE, PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
return;
|
|
}
|
|
else if (
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_DECREMENT)
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, KEY_FRONT_DOWN)
|
|
#endif
|
|
#else
|
|
(KEYCHECK_SHORTUP(ev->keys, KEY_DOWN) || KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_DOWN))
|
|
#endif
|
|
)
|
|
{
|
|
uiDataGlobal.talkaround = false;
|
|
handleDownKey(ev);
|
|
return;
|
|
}
|
|
else if (
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_INCREMENT)
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, KEY_FRONT_UP)
|
|
#endif
|
|
#else
|
|
(KEYCHECK_SHORTUP(ev->keys, KEY_UP) || KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_UP))
|
|
#endif
|
|
)
|
|
{
|
|
uiDataGlobal.talkaround = false;
|
|
handleUpKey(ev);
|
|
return;
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys,
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
KEY_FRONT_UP
|
|
#else
|
|
KEY_UP
|
|
#endif
|
|
) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
|
|
{
|
|
if (uiDataGlobal.Scan.active == false)
|
|
{
|
|
scanStart(true);
|
|
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
return;
|
|
#endif
|
|
}
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_0) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_0) == false))
|
|
{
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
bool muted = audioAmpIsMuted();
|
|
|
|
// Clear entered numbers
|
|
if (directChannelNumber != 0)
|
|
{
|
|
directChannelNumber = 0;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
|
|
audioAmpMute(!muted);
|
|
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s: %s", currentLanguage->mute, (muted ? currentLanguage->no : currentLanguage->yes));
|
|
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 1000U, buf, true);
|
|
|
|
if (muted)
|
|
{
|
|
soundSetMelody(MELODY_KEY_BEEP_FIRST_ITEM);
|
|
nextKeyBeepMelody = NULL;
|
|
ev->keys.event &= ~(KEY_MOD_LONG | KEY_MOD_DOWN | KEY_MOD_PRESS | KEY_MOD_UP);
|
|
ev->keys.key = 0;
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->mute);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->no);
|
|
voicePromptsPlay();
|
|
}
|
|
}
|
|
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_8) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_8) == false))
|
|
{
|
|
// Clear entered numbers
|
|
if (directChannelNumber != 0)
|
|
{
|
|
directChannelNumber = 0;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
|
|
if (currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 != -1)
|
|
{
|
|
uint32_t chanLat = ((currentChannelData->locationLat2 << 16) | (currentChannelData->locationLat1 << 8) | currentChannelData->locationLat0);
|
|
uint32_t chanLon = ((currentChannelData->locationLon2 << 16) | (currentChannelData->locationLon1 << 8) | currentChannelData->locationLon0);
|
|
double myLat = settingsLocationGetLatitude();
|
|
double myLon = settingsLocationGetLongitude();
|
|
double repLat = latLonFixed24ToDouble(chanLat);
|
|
double repLon = latLonFixed24ToDouble(chanLon);
|
|
double bearing = courseTo(myLat, myLon, repLat, repLon);
|
|
uiNotificationBearingInfo_t bearingNotifData = { .bearing = bearing, .distanceX10 = currentChannelData->NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 };
|
|
|
|
uiNotificationBearingShow(&bearingNotifData, 2000U, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int keyval = menuGetKeypadKeyValue(ev, true);
|
|
|
|
if ((keyval < 10) && BUTTONCHECK_DOWN(ev, BUTTON_SK1) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == false))
|
|
{
|
|
if (keyval == 1)
|
|
{
|
|
aprsBeaconingToggles();
|
|
}
|
|
else if (keyval == 2)
|
|
{
|
|
switch (aprsBeaconingGetMode())
|
|
{
|
|
case APRS_BEACONING_MODE_MANUAL:
|
|
case APRS_BEACONING_MODE_AUTO:
|
|
case APRS_BEACONING_MODE_SMART_BEACONING:
|
|
if (txInhibitCheckAndWarn() == false)
|
|
{
|
|
aprsBeaconingSendBeacon(false, true);
|
|
}
|
|
break;
|
|
|
|
default: // Ignore other APRS modes.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ((keyval < 10) && (!BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
|
|
{
|
|
directChannelNumber = (directChannelNumber * 10) + keyval;
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
// All Channels virtual zone
|
|
if(directChannelNumber > CODEPLUG_CONTACTS_MAX)
|
|
{
|
|
directChannelNumber = 0;
|
|
soundSetMelody(MELODY_ERROR_BEEP);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(directChannelNumber > currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone)
|
|
{
|
|
directChannelNumber = 0;
|
|
soundSetMelody(MELODY_ERROR_BEEP);
|
|
}
|
|
}
|
|
|
|
if (directChannelNumber > 0)
|
|
{
|
|
voicePromptsInit();
|
|
if (directChannelNumber < 10)
|
|
{
|
|
voicePromptsAppendLanguageString(currentLanguage->gotoChannel);
|
|
}
|
|
voicePromptsAppendPrompt(PROMPT_0 + keyval);
|
|
voicePromptsPlay();
|
|
}
|
|
else
|
|
{
|
|
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
}
|
|
#endif // ! PLATFORM_GD77S
|
|
}
|
|
|
|
#if ! defined(PLATFORM_GD77S)
|
|
static void selectPrevNextZone(bool nextZone)
|
|
{
|
|
int numZones = codeplugZonesGetCount();
|
|
|
|
if (nextZone)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.currentZone, 1);
|
|
|
|
if (nonVolatileSettings.currentZone >= numZones)
|
|
{
|
|
settingsSet(nonVolatileSettings.currentZone, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nonVolatileSettings.currentZone == 0)
|
|
{
|
|
settingsSet(nonVolatileSettings.currentZone, (int16_t) (numZones - 1));
|
|
}
|
|
else
|
|
{
|
|
settingsDecrement(nonVolatileSettings.currentZone, 1);
|
|
}
|
|
}
|
|
|
|
currentChannelData->rxFreq = 0U; // Flag to the Channel screen that the channel data is now invalid and needs to be reloaded
|
|
}
|
|
|
|
static void handleUpKey(uiEvent_t *ev)
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
selectPrevNextZone(true);
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, false);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen redraw
|
|
|
|
if (nonVolatileSettings.currentZone == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int16_t nextChan = -1;
|
|
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
// We are scanning downward
|
|
if (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)
|
|
{
|
|
if (uiDataGlobal.Scan.direction == -1)
|
|
{
|
|
handleDownKey(ev);
|
|
return;
|
|
}
|
|
|
|
// Let's select the next channel
|
|
}
|
|
else
|
|
{
|
|
// We're not paused, we shouldn't change the current channel
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1)
|
|
{
|
|
nextChan = codeplugGetLastUsedChannelInCurrentZone();
|
|
|
|
// All Channels virtual zone
|
|
do
|
|
{
|
|
nextChan = ((nextChan % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1);
|
|
|
|
} while (!codeplugAllChannelsIndexIsInUse(nextChan));
|
|
|
|
if (codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, nextChan) == CODEPLUG_CHANNELS_MIN)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1)
|
|
{
|
|
nextChan = ((codeplugGetLastUsedChannelInCurrentZone() + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone);
|
|
|
|
if (codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, nextChan) == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uiDataGlobal.Scan.active && (nextChan != -1))
|
|
{
|
|
scanNextChannelIndex = nextChan;
|
|
scanNextChannelReady = true;
|
|
}
|
|
|
|
uiDataGlobal.Scan.timer.timeout = (uiDataGlobal.Scan.active ? 0 : 500); // when scanning is running, don't let it pick another channel
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
}
|
|
|
|
lastHeardClearLastID();
|
|
uiChannelModeLoadChannelData(false, true);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
|
|
static void handleDownKey(uiEvent_t *ev)
|
|
{
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
selectPrevNextZone(false);
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, false);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen redraw
|
|
|
|
if (nonVolatileSettings.currentZone == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int16_t prevChan = -1;
|
|
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
// We are scanning downward
|
|
if (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)
|
|
{
|
|
if (uiDataGlobal.Scan.direction == 1)
|
|
{
|
|
handleUpKey(ev);
|
|
return;
|
|
}
|
|
|
|
// Let's select the previous channel
|
|
}
|
|
else
|
|
{
|
|
// We're not paused, we shouldn't change the current channel
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1)
|
|
{
|
|
prevChan = codeplugGetLastUsedChannelInCurrentZone();
|
|
|
|
// All Channels virtual zone
|
|
do
|
|
{
|
|
prevChan = ((((prevChan - 1) + currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1);
|
|
|
|
} while (!codeplugAllChannelsIndexIsInUse(prevChan));
|
|
|
|
if (codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, prevChan) == CODEPLUG_CHANNELS_MIN)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1)
|
|
{
|
|
prevChan = ((codeplugGetLastUsedChannelInCurrentZone() + currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone);
|
|
|
|
if (codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, prevChan) == 0)
|
|
{
|
|
menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uiDataGlobal.Scan.active && (prevChan != -1))
|
|
{
|
|
scanNextChannelIndex = prevChan;
|
|
scanNextChannelReady = true;
|
|
}
|
|
|
|
uiDataGlobal.Scan.timer.timeout = (uiDataGlobal.Scan.active ? 0 : 500); // when scanning is running, don't let it pick another channel
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
}
|
|
|
|
lastHeardClearLastID();
|
|
uiChannelModeLoadChannelData(false, true);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
|
|
#endif // ! PLATFORM_GD77S
|
|
|
|
|
|
// ---------------------------------------- Quick Menu functions -------------------------------------------------------------------
|
|
menuStatus_t uiChannelModeQuickMenu(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
if (isFirstRun)
|
|
{
|
|
if (quickmenuChannelFromVFOHandled)
|
|
{
|
|
quickmenuChannelFromVFOHandled = false;
|
|
menuSystemPopAllAndDisplayRootMenu();
|
|
return MENU_STATUS_SUCCESS;
|
|
}
|
|
|
|
uiChannelModeStopScanning();
|
|
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel = nonVolatileSettings.dmrDestinationFilter;
|
|
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel = nonVolatileSettings.dmrCcTsFilter;
|
|
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel = nonVolatileSettings.analogFilterLevel;
|
|
uiDataGlobal.QuickMenu.tmpTalkaround = uiDataGlobal.talkaround;
|
|
uiDataGlobal.QuickMenu.tmpRoaming = nonVolatileSettings.roaming;
|
|
uiDataGlobal.QuickMenu.tmpAudioMute = audioAmpIsMuted();
|
|
|
|
menuDataGlobal.numItems = NUM_CH_SCREEN_QUICK_MENU_ITEMS;
|
|
|
|
menuDataGlobal.menuOptionsSetQuickkey = 0;
|
|
menuDataGlobal.menuOptionsTimeout = 0;
|
|
menuDataGlobal.newOptionSelected = true;
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->quick_menu);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
|
|
updateQuickMenuScreen(true);
|
|
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
menuQuickChannelExitStatus = MENU_STATUS_SUCCESS;
|
|
|
|
if ((ev->hasEvent) || (menuDataGlobal.menuOptionsTimeout > 0))
|
|
{
|
|
handleQuickMenuEvent(ev);
|
|
}
|
|
}
|
|
|
|
return menuQuickChannelExitStatus;
|
|
}
|
|
|
|
static bool validateOverwriteChannel(void)
|
|
{
|
|
quickmenuChannelFromVFOHandled = true;
|
|
|
|
if (uiDataGlobal.MessageBox.keyPressed == KEY_GREEN)
|
|
{
|
|
CodeplugContact_t vfoContact;
|
|
int8_t vfoTS = -1;
|
|
|
|
uiVFOLoadContact(&vfoContact);
|
|
memcpy(¤tChannelData->rxFreq, &settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxFreq, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE - 16);// Don't copy the name of the vfo, which are in the first 16 bytes
|
|
// Find out which TS was in use.
|
|
if ((nonVolatileSettings.overrideTG == 0) && (vfoContact.reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00)
|
|
{
|
|
vfoTS = ((vfoContact.reserve1 & CODEPLUG_CONTACT_FLAG_TS_OVERRIDE_TIMESLOT_MASK) != 0);
|
|
}
|
|
else
|
|
{
|
|
vfoTS = tsGetManualOverride((Channel_t)nonVolatileSettings.currentVFONumber);
|
|
|
|
// No Override, use the TS from the Channel
|
|
if (vfoTS == 0)
|
|
{
|
|
vfoTS = ((codeplugChannelGetFlag(&settingsVFOChannel[nonVolatileSettings.currentVFONumber], CHANNEL_FLAG_TIMESLOT_TWO) != 0) ? 1 : 0);
|
|
}
|
|
else
|
|
{
|
|
vfoTS--; // convert to real TS
|
|
}
|
|
}
|
|
|
|
codeplugChannelSetFlag(currentChannelData, CHANNEL_FLAG_TIMESLOT_TWO, ((vfoTS == 0) ? 0 : 1));
|
|
codeplugChannelSaveDataForIndex(uiDataGlobal.currentSelectedChannelNumber, currentChannelData);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Shown when channels are locked (read-only / APRS) and an edit is attempted.
|
|
static void channelEditShowLockedMessageBox(void)
|
|
{
|
|
snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s\n%s", currentLanguage->aprs_channel, currentLanguage->locked);
|
|
uiDataGlobal.MessageBox.type = MESSAGEBOX_TYPE_INFO;
|
|
uiDataGlobal.MessageBox.decoration = MESSAGEBOX_DECORATION_FRAME;
|
|
uiDataGlobal.MessageBox.buttons =
|
|
#if defined(PLATFORM_MD9600)
|
|
MESSAGEBOX_BUTTONS_ENT;
|
|
#else
|
|
MESSAGEBOX_BUTTONS_OK;
|
|
#endif
|
|
uiDataGlobal.MessageBox.validatorCallback = NULL;
|
|
menuSystemPushNewMenu(UI_MESSAGE_BOX);
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(currentLanguage->aprs_channel);
|
|
voicePromptsAppendLanguageString(currentLanguage->locked);
|
|
voicePromptsPlay();
|
|
}
|
|
|
|
// Create a blank/default 145.500 MHz FM simplex channel in the first free slot,
|
|
// add it to the current zone, select it and open Channel Details to edit it.
|
|
static void channelEditCreateNewChannel(void)
|
|
{
|
|
int16_t newChannelIndex;
|
|
|
|
for (newChannelIndex = CODEPLUG_CHANNELS_MIN; newChannelIndex <= CODEPLUG_CHANNELS_MAX; newChannelIndex++)
|
|
{
|
|
if (codeplugAllChannelsIndexIsInUse(newChannelIndex) == false)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newChannelIndex > CODEPLUG_CHANNELS_MAX)
|
|
{
|
|
nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP; // codeplug full, no free slot
|
|
return;
|
|
}
|
|
|
|
// Start from the currently loaded channel so all structural fields are valid,
|
|
// then override the essentials to a blank FM simplex default.
|
|
char nameBuf[SCREEN_LINE_BUFFER_SIZE];
|
|
CodeplugChannel_t tempChannel = channelScreenChannelData;
|
|
|
|
snprintf(nameBuf, SCREEN_LINE_BUFFER_SIZE, "%s %d", currentLanguage->new_channel, newChannelIndex);
|
|
memset(&tempChannel.name, 0xFF, sizeof(tempChannel.name)); // codeplug strings are 0xFF-padded
|
|
memcpy(&tempChannel.name, nameBuf, strlen(nameBuf));
|
|
|
|
tempChannel.chMode = RADIO_MODE_ANALOG;
|
|
tempChannel.rxFreq = tempChannel.txFreq = 14550000; // 145.500 MHz
|
|
tempChannel.txTone = tempChannel.rxTone = CODEPLUG_CSS_TONE_NONE;
|
|
tempChannel.sql = 0U; // use the global squelch level
|
|
tempChannel.rxGroupList = 0;
|
|
tempChannel.contact = 0;
|
|
tempChannel.NOT_IN_CODEPLUG_flag = 0x00;
|
|
tempChannel.NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 = -1;
|
|
|
|
if (codeplugChannelSaveDataForIndex(newChannelIndex, &tempChannel) == false)
|
|
{
|
|
nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP;
|
|
return;
|
|
}
|
|
codeplugAllChannelsIndexSetUsed(newChannelIndex); // mark the slot used AFTER the data is written
|
|
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_indexNumber == 0xDEADBEEF)
|
|
{
|
|
uiChannelInitializeCurrentZone();
|
|
}
|
|
|
|
// Add it to the current zone ("All Channels" already includes every used slot).
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, newChannelIndex);
|
|
}
|
|
else
|
|
{
|
|
if (codeplugZoneAddChannelToZoneAndSave(newChannelIndex, ¤tZone))
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1));
|
|
}
|
|
}
|
|
|
|
// Reload the zone so the new channel is immediately navigable without having to
|
|
// re-enter the zone (refreshes the channel count / highest index for real zones,
|
|
// and re-derives them from the channel in-use bitmap for "All Channels").
|
|
uiChannelInitializeCurrentZone();
|
|
|
|
// Select the new channel (kept in memory, so Channel Details edits valid data).
|
|
memcpy(&channelScreenChannelData, &tempChannel, sizeof(tempChannel));
|
|
uiDataGlobal.currentSelectedChannelNumber = newChannelIndex;
|
|
uiDataGlobal.VoicePrompts.inhibitInitial = true;
|
|
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true);
|
|
menuSystemPushNewMenu(MENU_CHANNEL_DETAILS); // jump straight into the editor
|
|
nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
|
|
}
|
|
|
|
// Confirmation callback for deleting the currently-selected channel.
|
|
static bool validateDeleteChannel(void)
|
|
{
|
|
if (uiDataGlobal.MessageBox.keyPressed == KEY_GREEN)
|
|
{
|
|
int deletedChannelIndex = uiDataGlobal.currentSelectedChannelNumber;
|
|
|
|
// Remove the channel from every real zone that references it, so no zone is
|
|
// left pointing at a freed slot.
|
|
int numZones = codeplugZonesGetCount();
|
|
for (int z = 0; z < numZones; z++)
|
|
{
|
|
CodeplugZone_t zoneBuf;
|
|
if (codeplugZoneGetDataForNumber(z, &zoneBuf) && (CODEPLUG_ZONE_IS_ALLCHANNELS(zoneBuf) == false))
|
|
{
|
|
codeplugZoneRemoveChannelFromZoneAndSave(deletedChannelIndex, &zoneBuf);
|
|
}
|
|
}
|
|
|
|
// Free the slot (clears the in-use bitmap bit).
|
|
codeplugAllChannelsIndexSetUnused(deletedChannelIndex);
|
|
|
|
// Point the current zone at a valid remaining channel and force a reload.
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
int firstCh = CODEPLUG_CHANNELS_MIN;
|
|
while ((firstCh <= CODEPLUG_CHANNELS_MAX) && (codeplugAllChannelsIndexIsInUse(firstCh) == false))
|
|
{
|
|
firstCh++;
|
|
}
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, ((firstCh <= CODEPLUG_CHANNELS_MAX) ? firstCh : CODEPLUG_CHANNELS_MIN));
|
|
}
|
|
else
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, 0); // first channel in the zone
|
|
}
|
|
|
|
uiChannelInitializeCurrentZone(); // its channel list/count changed
|
|
channelScreenChannelData.rxFreq = 0; // force the channel screen to reload
|
|
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true);
|
|
nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void updateQuickMenuScreen(bool isFirstRun)
|
|
{
|
|
int mNum = 0;
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
const char *leftSide;// initialise to please the compiler
|
|
const char *rightSideConst;// initialise to please the compiler
|
|
char rightSideVar[SCREEN_LINE_BUFFER_SIZE];
|
|
|
|
|
|
displayClearBuf();
|
|
bool settingOption = uiQuickKeysShowChoices(buf, SCREEN_LINE_BUFFER_SIZE, currentLanguage->quick_menu);
|
|
|
|
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
|
{
|
|
if ((settingOption == false) || (i == 0))
|
|
{
|
|
mNum = menuGetMenuOffset(NUM_CH_SCREEN_QUICK_MENU_ITEMS, i);
|
|
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
|
{
|
|
continue;
|
|
}
|
|
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
|
{
|
|
break;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
rightSideVar[0] = 0;
|
|
rightSideConst = NULL;
|
|
leftSide = NULL;
|
|
|
|
switch(mNum)
|
|
{
|
|
case CH_SCREEN_QUICK_MENU_COPY2VFO:
|
|
rightSideConst = currentLanguage->channelToVfo;
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_COPY_FROM_VFO:
|
|
rightSideConst = currentLanguage->vfoToChannel;
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_FM:
|
|
leftSide = currentLanguage->filter;
|
|
if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel == 0)
|
|
{
|
|
rightSideConst = currentLanguage->none;
|
|
}
|
|
else
|
|
{
|
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%s", ANALOG_FILTER_LEVELS[uiDataGlobal.QuickMenu.tmpAnalogFilterLevel - 1]);
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR:
|
|
leftSide = currentLanguage->dmr_filter;
|
|
if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel == 0)
|
|
{
|
|
rightSideConst = currentLanguage->none;
|
|
}
|
|
else
|
|
{
|
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%s", DMR_DESTINATION_FILTER_LEVELS[uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel - 1]);
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_DMR_CC_SCAN:
|
|
leftSide = currentLanguage->dmr_cc_scan;
|
|
rightSideConst = (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN) ? currentLanguage->off : currentLanguage->on;
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS:
|
|
leftSide = currentLanguage->dmr_ts_filter;
|
|
rightSideConst = (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN) ? currentLanguage->on : currentLanguage->off;
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_TALKAROUND:
|
|
leftSide = currentLanguage->talkaround;
|
|
rightSideConst = ((currentChannelData->txFreq != currentChannelData->rxFreq) ? (uiDataGlobal.QuickMenu.tmpTalkaround ? currentLanguage->on : currentLanguage->off) : currentLanguage->n_a);
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_ROAMING:
|
|
leftSide = currentLanguage->roaming;
|
|
{
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
rightSideConst = currentLanguage->n_a;
|
|
}
|
|
else
|
|
{
|
|
switch (uiDataGlobal.QuickMenu.tmpRoaming)
|
|
{
|
|
case ROAMING_OFF:
|
|
rightSideConst = currentLanguage->off;
|
|
break;
|
|
case ROAMING_MANUAL:
|
|
rightSideConst = currentLanguage->manual;
|
|
break;
|
|
default:
|
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%ukm", ROAMING_DISTANCE_KM_ARRAY[uiDataGlobal.QuickMenu.tmpRoaming - ROAMING_5KM]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_AUDIO_MUTE:
|
|
leftSide = currentLanguage->mute;
|
|
rightSideConst = (uiDataGlobal.QuickMenu.tmpAudioMute ? currentLanguage->yes : currentLanguage->no);
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_NEW_CHANNEL:
|
|
rightSideConst = currentLanguage->new_channel;
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_DELETE_CHANNEL:
|
|
rightSideConst = currentLanguage->delete_channel;
|
|
break;
|
|
default:
|
|
buf[0] = 0;
|
|
}
|
|
|
|
if (leftSide != NULL)
|
|
{
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s:%s", leftSide, (rightSideVar[0] ? rightSideVar : rightSideConst));
|
|
}
|
|
else
|
|
{
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s", (rightSideVar[0] ? rightSideVar : rightSideConst));
|
|
}
|
|
|
|
if (i == 0)
|
|
{
|
|
if (!isFirstRun && (menuDataGlobal.menuOptionsSetQuickkey == 0))
|
|
{
|
|
voicePromptsInit();
|
|
}
|
|
|
|
if ((leftSide != NULL) || menuDataGlobal.newOptionSelected)
|
|
{
|
|
voicePromptsAppendLanguageString(leftSide);
|
|
}
|
|
|
|
if (rightSideVar[0] != 0)
|
|
{
|
|
if (mNum == CH_SCREEN_QUICK_MENU_ROAMING)
|
|
{
|
|
char numBuf[8];
|
|
snprintf(numBuf, 8, "%u", ROAMING_DISTANCE_KM_ARRAY[uiDataGlobal.QuickMenu.tmpRoaming - ROAMING_5KM]);
|
|
voicePromptsAppendString(numBuf);
|
|
voicePromptsAppendPrompt(PROMPT_KILOMETERS);
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendString(rightSideVar);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendLanguageString(rightSideConst);
|
|
}
|
|
|
|
if (menuDataGlobal.menuOptionsTimeout != -1)
|
|
{
|
|
promptsPlayNotAfterTx();
|
|
}
|
|
else
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout = 0;// clear flag indicating that a QuickKey has just been set
|
|
}
|
|
}
|
|
|
|
// QuickKeys
|
|
if (menuDataGlobal.menuOptionsTimeout > 0)
|
|
{
|
|
menuDisplaySettingOption(leftSide, (rightSideVar[0] ? rightSideVar : rightSideConst));
|
|
}
|
|
else
|
|
{
|
|
switch (mNum)
|
|
{
|
|
case CH_SCREEN_QUICK_MENU_FILTER_FM:
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR:
|
|
case CH_SCREEN_QUICK_MENU_DMR_CC_SCAN:
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS:
|
|
case CH_SCREEN_QUICK_MENU_TALKAROUND:
|
|
case CH_SCREEN_QUICK_MENU_ROAMING:
|
|
case CH_SCREEN_QUICK_MENU_AUDIO_MUTE:
|
|
menuDisplayEntry(i, mNum, buf, (strlen(leftSide) + 1), THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_FG_OPTIONS_VALUE, THEME_ITEM_BG);
|
|
break;
|
|
|
|
default:
|
|
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
displayRender();
|
|
}
|
|
|
|
static void handleQuickMenuEvent(uiEvent_t *ev)
|
|
{
|
|
bool isDirty = false;
|
|
bool executingQuickKey = false;
|
|
|
|
if ((menuDataGlobal.menuOptionsTimeout > 0) && (!BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout--;
|
|
if (menuDataGlobal.menuOptionsTimeout == 0)
|
|
{
|
|
// Let the QuickKey's VP playback to ends before
|
|
// going back to the previous menu
|
|
if (voicePromptsIsPlaying())
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout++;
|
|
return;
|
|
}
|
|
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ev->events & FUNCTION_EVENT)
|
|
{
|
|
isDirty = true;
|
|
if (ev->function == FUNC_REDRAW)
|
|
{
|
|
updateQuickMenuScreen(false);
|
|
return;
|
|
}
|
|
else if ((QUICKKEY_TYPE(ev->function) == QUICKKEY_MENU) && (QUICKKEY_ENTRYID(ev->function) < NUM_CH_SCREEN_QUICK_MENU_ITEMS))
|
|
{
|
|
menuDataGlobal.currentItemIndex = QUICKKEY_ENTRYID(ev->function);
|
|
}
|
|
|
|
if ((QUICKKEY_FUNCTIONID(ev->function) != 0))
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout = 1000;
|
|
executingQuickKey = true;
|
|
}
|
|
}
|
|
|
|
if ((ev->events & KEY_EVENT) && (menuDataGlobal.menuOptionsSetQuickkey == 0) && (menuDataGlobal.menuOptionsTimeout == 0))
|
|
{
|
|
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (menuDataGlobal.numItems != 0))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, NUM_CH_SCREEN_QUICK_MENU_ITEMS);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuQuickChannelExitStatus |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, NUM_CH_SCREEN_QUICK_MENU_ITEMS);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuQuickChannelExitStatus |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
quickKeyApply: // branching here when to quickkey was used.
|
|
|
|
switch(menuDataGlobal.currentItemIndex)
|
|
{
|
|
case CH_SCREEN_QUICK_MENU_COPY2VFO:
|
|
{
|
|
int8_t currentTS = trxGetDMRTimeSlot();
|
|
|
|
memcpy(&settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxFreq, ¤tChannelData->rxFreq, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE - 16);// Don't copy the name of channel, which are in the first 16 bytes
|
|
settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxTone = currentChannelData->rxTone;
|
|
settingsVFOChannel[nonVolatileSettings.currentVFONumber].txTone = currentChannelData->txTone;
|
|
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
nonVolatileSettings.overrideTG = trxTalkGroupOrPcId;
|
|
}
|
|
|
|
// Set TS override
|
|
tsSetManualOverride(((Channel_t)nonVolatileSettings.currentVFONumber), currentTS + 1);
|
|
|
|
codeplugChannelSetFlag(&settingsVFOChannel[nonVolatileSettings.currentVFONumber], CHANNEL_FLAG_TIMESLOT_TWO, ((currentTS == 0) ? 0 : 1));
|
|
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_VFO_MODE, true);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case CH_SCREEN_QUICK_MENU_COPY_FROM_VFO:
|
|
{
|
|
bool displayMessageBox = false;
|
|
const char *vp1 = NULL;
|
|
const char *vp2 = NULL;
|
|
|
|
if (settingsIsOptionBitSet(BIT_CHANNELS_ARE_READ_ONLY) == false)
|
|
{
|
|
if (quickmenuChannelFromVFOHandled == false)
|
|
{
|
|
displayMessageBox = true;
|
|
vp1 = currentLanguage->overwrite_qm;
|
|
vp2 = currentLanguage->please_confirm;
|
|
uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_YESNO;
|
|
uiDataGlobal.MessageBox.validatorCallback = validateOverwriteChannel;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
displayMessageBox = true;
|
|
vp1 = currentLanguage->aprs_channel;
|
|
vp2 = currentLanguage->locked;
|
|
uiDataGlobal.MessageBox.buttons =
|
|
#if defined(PLATFORM_MD9600)
|
|
MESSAGEBOX_BUTTONS_ENT;
|
|
#else
|
|
MESSAGEBOX_BUTTONS_OK;
|
|
#endif
|
|
uiDataGlobal.MessageBox.validatorCallback = NULL;
|
|
|
|
}
|
|
|
|
if (displayMessageBox)
|
|
{
|
|
snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s\n%s", vp1, vp2);
|
|
uiDataGlobal.MessageBox.type = MESSAGEBOX_TYPE_INFO;
|
|
uiDataGlobal.MessageBox.decoration = MESSAGEBOX_DECORATION_FRAME;
|
|
|
|
menuSystemPushNewMenu(UI_MESSAGE_BOX);
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(vp1);
|
|
voicePromptsAppendLanguageString(vp2);
|
|
voicePromptsPlay();
|
|
}
|
|
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case CH_SCREEN_QUICK_MENU_NEW_CHANNEL:
|
|
if (settingsIsOptionBitSet(BIT_CHANNELS_ARE_READ_ONLY))
|
|
{
|
|
channelEditShowLockedMessageBox();
|
|
}
|
|
else
|
|
{
|
|
channelEditCreateNewChannel();
|
|
}
|
|
return;
|
|
|
|
case CH_SCREEN_QUICK_MENU_DELETE_CHANNEL:
|
|
{
|
|
if (settingsIsOptionBitSet(BIT_CHANNELS_ARE_READ_ONLY))
|
|
{
|
|
channelEditShowLockedMessageBox();
|
|
return;
|
|
}
|
|
|
|
// Don't allow deleting the last remaining channel (would leave the
|
|
// codeplug with no channels).
|
|
int inUseCount = 0;
|
|
for (int i = CODEPLUG_CHANNELS_MIN; (i <= CODEPLUG_CHANNELS_MAX) && (inUseCount < 2); i++)
|
|
{
|
|
if (codeplugAllChannelsIndexIsInUse(i))
|
|
{
|
|
inUseCount++;
|
|
}
|
|
}
|
|
|
|
uiDataGlobal.MessageBox.type = MESSAGEBOX_TYPE_INFO;
|
|
uiDataGlobal.MessageBox.decoration = MESSAGEBOX_DECORATION_FRAME;
|
|
|
|
if (inUseCount < 2)
|
|
{
|
|
snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s", currentLanguage->keep_one_channel);
|
|
uiDataGlobal.MessageBox.buttons =
|
|
#if defined(PLATFORM_MD9600)
|
|
MESSAGEBOX_BUTTONS_ENT;
|
|
#else
|
|
MESSAGEBOX_BUTTONS_OK;
|
|
#endif
|
|
uiDataGlobal.MessageBox.validatorCallback = NULL;
|
|
menuSystemPushNewMenu(UI_MESSAGE_BOX);
|
|
return;
|
|
}
|
|
|
|
snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s\n%s", currentLanguage->delete_channel, currentLanguage->please_confirm);
|
|
uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_YESNO;
|
|
uiDataGlobal.MessageBox.validatorCallback = validateDeleteChannel;
|
|
menuSystemPushNewMenu(UI_MESSAGE_BOX);
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(currentLanguage->delete_channel);
|
|
voicePromptsAppendLanguageString(currentLanguage->please_confirm);
|
|
voicePromptsPlay();
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// CH_SCREEN_QUICK_MENU_FILTER_FM
|
|
if (nonVolatileSettings.analogFilterLevel != uiDataGlobal.QuickMenu.tmpAnalogFilterLevel)
|
|
{
|
|
settingsSet(nonVolatileSettings.analogFilterLevel, uiDataGlobal.QuickMenu.tmpAnalogFilterLevel);
|
|
trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel);
|
|
}
|
|
|
|
// CH_SCREEN_QUICK_MENU_FILTER_DMR
|
|
if (nonVolatileSettings.dmrDestinationFilter != uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel)
|
|
{
|
|
settingsSet(nonVolatileSettings.dmrDestinationFilter, uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel);
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
HRC6000InitDigitalDmrRx();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
}
|
|
|
|
// CH_SCREEN_QUICK_MENU_FILTER_DMR_CC
|
|
// CH_SCREEN_QUICK_MENU_FILTER_DMR_TS
|
|
if (nonVolatileSettings.dmrCcTsFilter != uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel)
|
|
{
|
|
settingsSet(nonVolatileSettings.dmrCcTsFilter, uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel);
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
HRC6000InitDigitalDmrRx();
|
|
HRC6000ResyncTimeSlot();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
}
|
|
|
|
// CH_SCREEN_QUICK_MENU_TALKAROUND
|
|
if ((currentChannelData->txFreq != currentChannelData->rxFreq) && (uiDataGlobal.talkaround != uiDataGlobal.QuickMenu.tmpTalkaround))
|
|
{
|
|
uiDataGlobal.talkaround = uiDataGlobal.QuickMenu.tmpTalkaround;
|
|
|
|
// Talkaround and reverseRepeater are mutually exclusive.
|
|
if (uiDataGlobal.reverseRepeaterChannel && uiDataGlobal.talkaround)
|
|
{
|
|
uiDataGlobal.reverseRepeaterChannel = false;
|
|
}
|
|
}
|
|
|
|
// CH_SCREEN_QUICK_MENU_ROAMING
|
|
if (nonVolatileSettings.roaming != uiDataGlobal.QuickMenu.tmpRoaming)
|
|
{
|
|
nonVolatileSettings.roaming = uiDataGlobal.QuickMenu.tmpRoaming;
|
|
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, 0);
|
|
currentChannelData->rxFreq = 0U; // force reload of zone
|
|
}
|
|
else if ((nonVolatileSettings.roaming == ROAMING_MANUAL) && (menuDataGlobal.currentItemIndex == CH_SCREEN_QUICK_MENU_ROAMING))
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, 0);
|
|
currentChannelData->rxFreq = 0U; // force reload of zone, hence execute distance sort.
|
|
}
|
|
|
|
// CH_SCREEN_QUICK_MENU_MUTE
|
|
if (audioAmpIsMuted() != uiDataGlobal.QuickMenu.tmpAudioMute)
|
|
{
|
|
audioAmpMute(uiDataGlobal.QuickMenu.tmpAudioMute);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (executingQuickKey)
|
|
{
|
|
updateQuickMenuScreen(false);
|
|
}
|
|
else
|
|
{
|
|
menuSystemPopPreviousMenu();
|
|
}
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
uiChannelModeStopScanning();
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP_NUMBER(ev->keys) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
isDirty = true;
|
|
menuDataGlobal.menuOptionsSetQuickkey = ev->keys.key;
|
|
}
|
|
}
|
|
|
|
if ((ev->events & (KEY_EVENT | FUNCTION_EVENT)) && (menuDataGlobal.menuOptionsSetQuickkey == 0))
|
|
{
|
|
if (KEYCHECK_PRESS(ev->keys, KEY_RIGHT)
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_INCREMENT)
|
|
#endif
|
|
|| (QUICKKEY_FUNCTIONID(ev->function) == FUNC_RIGHT))
|
|
{
|
|
if (menuDataGlobal.menuOptionsTimeout > 0)
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout = 1000;
|
|
}
|
|
isDirty = true;
|
|
menuDataGlobal.newOptionSelected = false;
|
|
|
|
switch(menuDataGlobal.currentItemIndex)
|
|
{
|
|
case CH_SCREEN_QUICK_MENU_FILTER_FM:
|
|
if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel < NUM_ANALOG_FILTER_LEVELS - 1)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel++;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR:
|
|
if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel < NUM_DMR_DESTINATION_FILTER_LEVELS - 1)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel++;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_DMR_CC_SCAN:
|
|
if ((uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN))
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_CC_FILTER_PATTERN;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS:
|
|
if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN))
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_TS_FILTER_PATTERN;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_TALKAROUND:
|
|
if (currentChannelData->txFreq != currentChannelData->rxFreq)
|
|
{
|
|
if (uiDataGlobal.QuickMenu.tmpTalkaround == false)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpTalkaround = true;
|
|
}
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_ROAMING:
|
|
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false)
|
|
{
|
|
#if defined(HAS_GPS)
|
|
if (uiDataGlobal.QuickMenu.tmpRoaming < ((SETTINGS_GPS_MODE_GET(nonVolatileSettings) != GPS_NOT_DETECTED) ? (ROAMING_MAX_NUM - 1) : ROAMING_MANUAL))
|
|
#else
|
|
if (uiDataGlobal.QuickMenu.tmpRoaming < ROAMING_MANUAL)
|
|
#endif
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpRoaming++;
|
|
}
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_AUDIO_MUTE:
|
|
uiDataGlobal.QuickMenu.tmpAudioMute = true;
|
|
break;
|
|
}
|
|
|
|
if (executingQuickKey) // Instantly apply new setting
|
|
{
|
|
goto quickKeyApply;
|
|
}
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, KEY_LEFT)
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_DECREMENT)
|
|
#endif
|
|
|| (QUICKKEY_FUNCTIONID(ev->function) == FUNC_LEFT))
|
|
{
|
|
if (menuDataGlobal.menuOptionsTimeout > 0)
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout = 1000;
|
|
}
|
|
isDirty = true;
|
|
menuDataGlobal.newOptionSelected = false;
|
|
|
|
switch(menuDataGlobal.currentItemIndex)
|
|
{
|
|
case CH_SCREEN_QUICK_MENU_FILTER_FM:
|
|
if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel > ANALOG_FILTER_NONE)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel--;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR:
|
|
if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel > DMR_DESTINATION_FILTER_NONE)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel--;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_DMR_CC_SCAN:
|
|
if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN))
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_CC_FILTER_PATTERN;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS:
|
|
if ((uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN))
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_TS_FILTER_PATTERN;
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_TALKAROUND:
|
|
if (currentChannelData->txFreq != currentChannelData->rxFreq)
|
|
{
|
|
if (uiDataGlobal.QuickMenu.tmpTalkaround)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpTalkaround = false;
|
|
}
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_ROAMING:
|
|
if (!CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
if (uiDataGlobal.QuickMenu.tmpRoaming > ROAMING_OFF)
|
|
{
|
|
uiDataGlobal.QuickMenu.tmpRoaming--;
|
|
//codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, 0);
|
|
//currentChannelData->rxFreq = 0U;// force reload of zone
|
|
}
|
|
}
|
|
break;
|
|
case CH_SCREEN_QUICK_MENU_AUDIO_MUTE:
|
|
uiDataGlobal.QuickMenu.tmpAudioMute = false;
|
|
break;
|
|
}
|
|
|
|
if (executingQuickKey) // Instantly apply new setting
|
|
{
|
|
goto quickKeyApply;
|
|
}
|
|
}
|
|
else if ((ev->keys.event & KEY_MOD_PRESS) && (menuDataGlobal.menuOptionsTimeout > 0))
|
|
{
|
|
menuDataGlobal.menuOptionsTimeout = 0;
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (uiQuickKeysIsStoring(ev))
|
|
{
|
|
uiQuickKeysStore(ev, &menuQuickChannelExitStatus);
|
|
isDirty = true;
|
|
}
|
|
|
|
if (isDirty)
|
|
{
|
|
updateQuickMenuScreen(false);
|
|
}
|
|
}
|
|
|
|
//Scan Mode
|
|
static void scanStart(bool longPressBeep)
|
|
{
|
|
// At least two channels are needed to run a scan process.
|
|
if (canCurrentZoneBeScanned(&uiDataGlobal.Scan.availableChannelsCount) == false)
|
|
{
|
|
menuChannelExitStatus |= MENU_STATUS_ERROR;
|
|
return;
|
|
}
|
|
|
|
if (voicePromptsIsPlaying())
|
|
{
|
|
voicePromptsTerminate();
|
|
}
|
|
|
|
directChannelNumber = 0;
|
|
uiDataGlobal.Scan.direction = 1;
|
|
uiDataGlobal.talkaround = false;
|
|
|
|
// Clear all nuisance delete channels at start of scanning
|
|
for (int i = 0; i < MAX_ZONE_SCAN_NUISANCE_CHANNELS; i++)
|
|
{
|
|
uiDataGlobal.Scan.nuisanceDelete[i] = -1;
|
|
}
|
|
uiDataGlobal.Scan.nuisanceDeleteIndex = 0;
|
|
|
|
uiDataGlobal.Scan.active = true;
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
uiDataGlobal.Scan.lastIteration = false;
|
|
|
|
uiDataGlobal.Scan.stepTimeMilliseconds = settingsGetScanStepTimeMilliseconds();
|
|
|
|
// In DIGITAL (slow) mode, we need at least 120ms to see the HR-C6000 to start the TS ISR.
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
int dwellTime;
|
|
if(uiDataGlobal.Scan.stepTimeMilliseconds > 150) // if >150ms use DMR Slow mode
|
|
{
|
|
dwellTime = ((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) ? SCAN_DMR_SIMPLEX_SLOW_MIN_DWELL_TIME : SCAN_DMR_DUPLEX_SLOW_MIN_DWELL_TIME);
|
|
}
|
|
else
|
|
{
|
|
dwellTime = ((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) ? SCAN_DMR_SIMPLEX_FAST_MIN_DWELL_TIME : (SCAN_DMR_DUPLEX_FAST_MIN_DWELL_TIME + SCAN_DMR_DUPLEX_FAST_EXTRA_DWELL_TIME));
|
|
}
|
|
|
|
uiDataGlobal.Scan.dwellTime = ((uiDataGlobal.Scan.stepTimeMilliseconds < dwellTime) ? dwellTime : uiDataGlobal.Scan.stepTimeMilliseconds);
|
|
}
|
|
else
|
|
{
|
|
uiDataGlobal.Scan.dwellTime = uiDataGlobal.Scan.stepTimeMilliseconds;
|
|
}
|
|
|
|
uiDataGlobal.Scan.timer.timeout = uiDataGlobal.Scan.dwellTime;
|
|
uiDataGlobal.Scan.scanType = SCAN_TYPE_NORMAL_STEP;
|
|
|
|
// Need to set the melody here, otherwise long press will remain silent
|
|
// since beeps aren't allowed while scanning
|
|
if (longPressBeep)
|
|
{
|
|
soundSetMelody(MELODY_KEY_LONG_BEEP);
|
|
}
|
|
|
|
// Set current channel index
|
|
scanNextChannelIndex = codeplugGetLastUsedChannelNumberInCurrentZone();
|
|
scanNextChannelReady = false;
|
|
}
|
|
|
|
static void updateTrxID(void)
|
|
{
|
|
if (nonVolatileSettings.overrideTG != 0)
|
|
{
|
|
trxTalkGroupOrPcId = nonVolatileSettings.overrideTG;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* VK3KYY
|
|
* I can't see a compelling reason to remove the TS override when changing TG
|
|
* The function trxUpdateTsForCurrentChannelWithSpecifiedContact(), used below, is used to handle the TS
|
|
*
|
|
tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);
|
|
*/
|
|
if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup))
|
|
{
|
|
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData);
|
|
}
|
|
else
|
|
{
|
|
codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData);
|
|
}
|
|
|
|
tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, false);
|
|
|
|
trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData);
|
|
trxTalkGroupOrPcId = codeplugContactGetPackedId(¤tContactData);
|
|
}
|
|
lastHeardClearLastID();
|
|
menuPrivateCallClear();
|
|
}
|
|
|
|
static void scanning(void)
|
|
{
|
|
// After initial settling time
|
|
if((uiDataGlobal.Scan.state == SCAN_STATE_SCANNING) && (uiDataGlobal.Scan.timer.timeout > SCAN_SKIP_CHANNEL_INTERVAL) && (uiDataGlobal.Scan.timer.timeout < (uiDataGlobal.Scan.dwellTime - SCAN_FREQ_CHANGE_SETTLING_INTERVAL)))
|
|
{
|
|
//Signal detect for DMR channels
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if(uiDataGlobal.Scan.stepTimeMilliseconds > 150) // if >150ms use DMR Slow mode
|
|
{ // DMR Scan Slow mode for signal detect
|
|
if (((nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN)
|
|
&&
|
|
((slotState != DMR_STATE_IDLE) && ((dmrMonitorCapturedTS != -1) &&
|
|
(((currentRadioDevice->trxDMRModeRx == DMR_MODE_DMO) && (dmrMonitorCapturedTS == trxGetDMRTimeSlot())) ||
|
|
(currentRadioDevice->trxDMRModeRx == DMR_MODE_RMO)))))
|
|
||
|
|
// As soon as the HRC6000 get sync, timeCode != -1 or TS ISR is running
|
|
HRC6000HasGotSync())
|
|
{
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SHORT_PAUSED;
|
|
#if defined(PLATFORM_MD9600)
|
|
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
|
|
#endif
|
|
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
// Reload the channel as voice prompts aren't set while scanning
|
|
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
|
|
{
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
#endif
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh
|
|
uiDataGlobal.Scan.timer.timeout = ((TIMESLOT_DURATION * 12) + TIMESLOT_DURATION) * 4; // (1 superframe + 1 TS) * 4 = TS Sync + incoming audio
|
|
}
|
|
}
|
|
else //DMR Scan fast mode signal detect
|
|
{
|
|
if(trxCarrierDetected(RADIO_DEVICE_PRIMARY))
|
|
{
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
// Reload the channel as voice prompts aren't set while scanning
|
|
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
|
|
{
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
#endif
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh
|
|
|
|
if (((nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN) == 0))
|
|
{
|
|
uiDataGlobal.Scan.timer.timeout = SCAN_SHORT_PAUSE_TIME * 2; //needs longer delay if in DMR mode and TS Filter is off to allow full detection of signal
|
|
}
|
|
else
|
|
{
|
|
uiDataGlobal.Scan.timer.timeout = SCAN_SHORT_PAUSE_TIME; //start short delay to allow full detection of signal
|
|
}
|
|
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SHORT_PAUSED; //state 1 = pause and test for valid signal that produces audio
|
|
#if defined(PLATFORM_MD9600)
|
|
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else //Signal Detect for FM channels
|
|
{
|
|
if(trxCarrierDetected(RADIO_DEVICE_PRIMARY))
|
|
{
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
// Reload the channel as voice prompts aren't set while scanning
|
|
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
|
|
{
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
#endif
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh
|
|
|
|
uiDataGlobal.Scan.timer.timeout = SCAN_SHORT_PAUSE_TIME; //start short delay to allow full detection of signal
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SHORT_PAUSED; //state 1 = pause and test for valid signal that produces audio
|
|
#if defined(PLATFORM_MD9600)
|
|
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only do this once if scan mode is PAUSE do it every time if scan mode is HOLD
|
|
if(((uiDataGlobal.Scan.state == SCAN_STATE_PAUSED) && (nonVolatileSettings.scanModePause == SCAN_MODE_HOLD)) || (uiDataGlobal.Scan.state == SCAN_STATE_SHORT_PAUSED))
|
|
{
|
|
#if defined(PLATFORM_MD9600)
|
|
if (uiDataGlobal.Scan.clickDiscriminator > 0)
|
|
{
|
|
uiDataGlobal.Scan.clickDiscriminator--;
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
if (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF)
|
|
{
|
|
bool needsToUpdateDistanceDisplay = (settingsLocationIsValid() &&
|
|
(settingsIsOptionBitSet(BIT_DISPLAY_CHANNEL_DISTANCE) || (nonVolatileSettings.roaming != ROAMING_OFF))
|
|
#if defined(HAS_GPS)
|
|
&& ((SETTINGS_GPS_MODE_GET(nonVolatileSettings) <= GPS_MODE_OFF) ||
|
|
((SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_ON) && ((gpsData.Status & (GPS_STATUS_HAS_FIX | GPS_STATUS_POSITION_UPDATED)) != 0)))
|
|
#endif
|
|
);
|
|
|
|
if (nonVolatileSettings.scanModePause == SCAN_MODE_STOP)
|
|
{
|
|
uiChannelModeStopScanning();
|
|
|
|
if (needsToUpdateDistanceDisplay)
|
|
{
|
|
checkChannelLocation(currentChannelData);
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
uiDataGlobal.Scan.timer.timeout = nonVolatileSettings.scanDelay * 1000;
|
|
|
|
if ((uiDataGlobal.Scan.state == SCAN_STATE_SHORT_PAUSED) && needsToUpdateDistanceDisplay)
|
|
{
|
|
checkChannelLocation(currentChannelData);
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
}
|
|
|
|
uiDataGlobal.Scan.state = SCAN_STATE_PAUSED;
|
|
}
|
|
}
|
|
#if defined(PLATFORM_MD9600)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(uiDataGlobal.Scan.timer.timeout > 0)
|
|
{
|
|
if (scanNextChannelReady == false)
|
|
{
|
|
scanSearchForNextChannel();
|
|
}
|
|
|
|
uiDataGlobal.Scan.timer.timeout--;
|
|
}
|
|
else
|
|
{
|
|
if (scanNextChannelReady)
|
|
{
|
|
hidesChannelDetails();
|
|
|
|
scanApplyNextChannel();
|
|
|
|
// When less than 2 channel remain in the Zone
|
|
if (uiDataGlobal.Scan.lastIteration)
|
|
{
|
|
scanStop(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
}
|
|
}
|
|
|
|
static void scanStop(bool loadChannel)
|
|
{
|
|
int prevTS = dmrMonitorCapturedTS; // Always -1 on Simplex
|
|
bool wasRXingDigital = ((trxGetMode() == RADIO_MODE_DIGITAL) && (slotState != DMR_STATE_IDLE) && (prevTS != -1));
|
|
|
|
uiChannelModeStopScanning();
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
// It loads the channel data when exiting scan mode (as it was partialy readed)
|
|
if (loadChannel)
|
|
{
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
|
|
// We were receiving something in Digital (Duplex), but since the TS filtering is OFF, we
|
|
// will double check if selected TS stills match the previous one, when scan was paused.
|
|
if (wasRXingDigital && ((nonVolatileSettings.dmrCcTsFilter & DMR_TS_FILTER_PATTERN) == 0))
|
|
{
|
|
int retries = 120;
|
|
|
|
// Wait a bit until the TS is detected
|
|
watchdogRun(false); // Temporary disable the watchdog here, as it could take too much time for it.
|
|
do
|
|
{
|
|
vTaskDelay((10U / portTICK_PERIOD_MS));
|
|
}
|
|
while ((dmrMonitorCapturedTS == -1) && (retries-- > 0));
|
|
watchdogRun(true);
|
|
|
|
// Okay, we can now toggle the TS, if required
|
|
if (((dmrMonitorCapturedTS != -1) && (prevTS != -1)) && (prevTS != dmrMonitorCapturedTS))
|
|
{
|
|
trxSetDMRTimeSlot(trxGetDMRTimeSlot(), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void uiChannelModeStopScanning(void)
|
|
{
|
|
uiDataGlobal.Scan.active = false;
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh
|
|
|
|
#if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own
|
|
// Reload the channel as voice prompts aren't set while scanning
|
|
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD)
|
|
{
|
|
uiChannelModeLoadChannelData(false, true);
|
|
}
|
|
|
|
// We're exiting scan mode, and Roaming is auto-updating, but
|
|
// we don't want to do that right now, as the operator probably
|
|
// stopped scanning for a good reason (Rx || Rx/Tx).
|
|
// Hence we start tracking (location & time) from here.
|
|
if ((nonVolatileSettings.roaming >= ROAMING_5KM) &&
|
|
settingsLocationIsValid() &&
|
|
(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false))
|
|
{
|
|
uiDataGlobal.lastDistanceMillis = ticksGetMillis();
|
|
uiDataGlobal.lastDistanceSortLocationLat = settingsLocationGetLatitude();
|
|
uiDataGlobal.lastDistanceSortLocationLon = settingsLocationGetLongitude();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool uiChannelModeIsScanning(void)
|
|
{
|
|
return uiDataGlobal.Scan.active;
|
|
}
|
|
|
|
void uiChannelModeColdStart(void)
|
|
{
|
|
currentChannelData->rxFreq = 0U; // Force to re-read codeplug data (needed due to "All Channels" translation)
|
|
}
|
|
|
|
// This can also be called from the VFO, on VFO -> New Channel, as currentZone could be uninitialized.
|
|
void uiChannelInitializeCurrentZone(void)
|
|
{
|
|
codeplugZoneGetDataForNumber(nonVolatileSettings.currentZone, ¤tZone);
|
|
codeplugUtilConvertBufToString(currentZone.name, currentZoneName, 16);// need to convert to zero terminated string
|
|
|
|
if ((nonVolatileSettings.roaming != ROAMING_OFF) &&
|
|
settingsLocationIsValid() &&
|
|
(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) == false))
|
|
{
|
|
initSortedChannels();
|
|
uiDataGlobal.lastDistanceMillis = ticksGetMillis();
|
|
}
|
|
}
|
|
|
|
static void quicksortChannelByDistance(channelDistance_t *channelDistances, uint16_t first, uint16_t last)
|
|
{
|
|
channelDistance_t temp;
|
|
uint16_t i, j, pivot;
|
|
|
|
if(first < last)
|
|
{
|
|
pivot = first;
|
|
i = first;
|
|
j = last;
|
|
|
|
while (i < j)
|
|
{
|
|
while ((channelDistances[i].distance <= channelDistances[pivot].distance) && (i < last))
|
|
{
|
|
i++;
|
|
}
|
|
|
|
while (channelDistances[j].distance > channelDistances[pivot].distance)
|
|
{
|
|
j--;
|
|
}
|
|
|
|
if (i < j)
|
|
{
|
|
memcpy(&temp, &channelDistances[i], sizeof(channelDistance_t));
|
|
memcpy(&channelDistances[i], &channelDistances[j], sizeof(channelDistance_t));
|
|
memcpy(&channelDistances[j], &temp, sizeof(channelDistance_t));
|
|
}
|
|
}
|
|
|
|
memcpy(&temp, &channelDistances[pivot], sizeof(channelDistance_t));
|
|
memcpy(&channelDistances[pivot], &channelDistances[j], sizeof(channelDistance_t));
|
|
memcpy(&channelDistances[j], &temp, sizeof(channelDistance_t));
|
|
|
|
if (j > 0)
|
|
{
|
|
quicksortChannelByDistance(channelDistances, first, (j - 1));
|
|
}
|
|
|
|
quicksortChannelByDistance(channelDistances, (j + 1), last);
|
|
}
|
|
}
|
|
|
|
static void initSortedChannels(void)
|
|
{
|
|
if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 0)
|
|
{
|
|
channelDistance_t channelDistances[80];// Temporary storage while sorting a zone
|
|
CodeplugChannel_t channelData;
|
|
|
|
for (uint16_t i = 0; i < currentZone.NOT_IN_CODEPLUGDATA_highestIndex; i++)
|
|
{
|
|
channelDistances[i].index = currentZone.channels[i];
|
|
|
|
codeplugChannelGetDataForIndex(channelDistances[i].index, &channelData);
|
|
checkChannelLocation(&channelData);
|
|
|
|
if ((channelData.NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 != -1) &&
|
|
(channelData.NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10 < 65535) &&
|
|
codeplugChannelGetFlag(&channelData, CHANNEL_FLAG_USE_LOCATION))
|
|
{
|
|
channelDistances[i].distance = channelData.NOT_IN_CODEPLUG_CALCULATED_DISTANCE_X10;
|
|
}
|
|
else
|
|
{
|
|
// Use large positive value for any channel which does not have a location, so that is sorted to be at the end of the list
|
|
// Max real value is the radius of the Earth at the equator 40,075
|
|
// This value is in steps of 0.1km hence 50,000 * 10 is outside of the actual possible range and is used to indicate a channel with no location
|
|
// the index value is added so that each channel with no location, is sorted correctly to retain its position in the zone, relatative to the other channels with no location
|
|
|
|
channelDistances[i].distance = (500000 + i);
|
|
}
|
|
}
|
|
|
|
quicksortChannelByDistance(channelDistances, 0U, (uint16_t)(currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1));
|
|
|
|
for (uint16_t i = 0; i < currentZone.NOT_IN_CODEPLUGDATA_highestIndex; i++)
|
|
{
|
|
currentZone.channels[i] = channelDistances[i].index;
|
|
}
|
|
|
|
uiDataGlobal.lastDistanceSortLocationLat = settingsLocationGetLatitude();
|
|
uiDataGlobal.lastDistanceSortLocationLon = settingsLocationGetLongitude();
|
|
}
|
|
}
|
|
|
|
|
|
#if defined(PLATFORM_GD77S)
|
|
bool uiChannelModeTransmitDTMFContactForGD77S(void)
|
|
{
|
|
if (GD77SParameters.uiMode == GD77S_UIMODE_DTMF_CONTACTS)
|
|
{
|
|
if (GD77SParameters.dtmfListCount > 0)
|
|
{
|
|
// start dtmf sequence
|
|
if(dtmfSequenceIsKeying() == false)
|
|
{
|
|
CodeplugDTMFContact_t dtmfContact;
|
|
|
|
if (voicePromptsIsPlaying())
|
|
{
|
|
voicePromptsTerminate();
|
|
}
|
|
|
|
codeplugDTMFContactGetDataForNumber(GD77SParameters.dtmfListSelected + 1, &dtmfContact);
|
|
dtmfSequencePrepare(dtmfContact.code, true);
|
|
}
|
|
else
|
|
{
|
|
dtmfSequenceStop();
|
|
dtmfSequenceTick(false);
|
|
dtmfSequenceReset();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(currentLanguage->list_empty);
|
|
voicePromptsPlay();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void toggleTimeslotForGD77S(void)
|
|
{
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
// Toggle timeslot
|
|
trxSetDMRTimeSlot(1 - trxGetDMRTimeSlot(), true);
|
|
tsSetManualOverride(CHANNEL_CHANNEL, (trxGetDMRTimeSlot() + 1));
|
|
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
HRC6000ClearActiveDMRID();
|
|
lastHeardClearLastID();
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
|
|
void uiChannelModeHeartBeatActivityForGD77S(uiEvent_t *ev)
|
|
{
|
|
static const uint32_t periods[] = { 6000, 100, 100, 100, 100, 100 };
|
|
static const uint32_t periodsScan[] = { 2000, 50, 2000, 50, 2000, 50 };
|
|
static uint8_t beatRoll = 0;
|
|
static uint32_t mTime = 0;
|
|
|
|
// <paranoid_mode>
|
|
// We use real time GPIO readouts, as LED could be turned on/off by another task.
|
|
// </paranoid_mode>
|
|
if ((LedRead(LED_RED) || LedRead(LED_GREEN)) // Any led is ON
|
|
&& (trxTransmissionEnabled || (uiDataGlobal.DTMFContactList.isKeying) || (ev->buttons & BUTTON_PTT) || (audioAmpGetStatus() & (AUDIO_AMP_CHANNEL_RF | AUDIO_AMP_CHANNEL_BEEP | AUDIO_AMP_CHANNEL_PROMPT)) || trxCarrierDetected() || ev->hasEvent)) // we're transmitting, or receiving, or user interaction.
|
|
{
|
|
// Turn off the red LED, if not transmitting
|
|
if (LedRead(LED_RED) // Red is ON
|
|
&& ((uiDataGlobal.DTMFContactList.isKeying == false) && ((trxTransmissionEnabled == false) || ((ev->buttons & BUTTON_PTT) == 0)))) // No TX
|
|
{
|
|
if ((rxPowerSavingIsRxOn() == false) && (ev->hasEvent == false))
|
|
{
|
|
rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
|
|
}
|
|
|
|
LedWrite(LED_RED, 0);
|
|
}
|
|
|
|
// Turn off the green LED, if not receiving, or no AF output
|
|
if (LedRead(LED_GREEN)) // Green is ON
|
|
{
|
|
if ((trxTransmissionEnabled || (uiDataGlobal.DTMFContactList.isKeying) || (ev->buttons & BUTTON_PTT))
|
|
|| ((trxGetMode() == RADIO_MODE_DIGITAL) && (slotState != DMR_STATE_IDLE))
|
|
|| (((audioAmpGetStatus() & (AUDIO_AMP_CHANNEL_RF | AUDIO_AMP_CHANNEL_BEEP | AUDIO_AMP_CHANNEL_PROMPT)) != 0) || trxCarrierDetected()))
|
|
{
|
|
if ((ev->buttons & BUTTON_PTT) && (trxTransmissionEnabled == false)) // RX Only or Out of Band
|
|
{
|
|
if ((rxPowerSavingIsRxOn() == false) && (ev->hasEvent == false))
|
|
{
|
|
rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((rxPowerSavingIsRxOn() == false) && (ev->hasEvent == false))
|
|
{
|
|
rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
}
|
|
}
|
|
|
|
// Reset pattern sequence
|
|
beatRoll = 0;
|
|
// And update the timer for the next first starting (OFF for 5 seconds) blink sequence.
|
|
mTime = ev->time;
|
|
return;
|
|
}
|
|
|
|
if (!rxPowerSavingIsRxOn())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Nothing is happening, blink
|
|
if (((trxTransmissionEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false) && ((ev->buttons & BUTTON_PTT) == 0))
|
|
&& ((ev->hasEvent == false) && ((audioAmpGetStatus() & (AUDIO_AMP_CHANNEL_RF | AUDIO_AMP_CHANNEL_BEEP | AUDIO_AMP_CHANNEL_PROMPT)) == 0) && (trxCarrierDetected() == false)))
|
|
{
|
|
// Blink both LEDs to have Orange color
|
|
if ((ev->time - mTime) > (uiDataGlobal.Scan.active ? periodsScan[beatRoll] : periods[beatRoll]))
|
|
{
|
|
if ((nonVolatileSettings.ecoLevel > 0) && (ev->hasEvent == false))
|
|
{
|
|
rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
|
|
}
|
|
|
|
mTime = ev->time;
|
|
beatRoll = (beatRoll + 1) % (uiDataGlobal.Scan.active ? (sizeof(periodsScan) / sizeof(periodsScan[0])) : (sizeof(periods) / sizeof(periods[0])));
|
|
LedWrite(LED_RED, (beatRoll % 2));
|
|
LedWrite(LED_GREEN, (beatRoll % 2));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reset pattern sequence
|
|
beatRoll = 0;
|
|
// And update the timer for the next first starting (OFF for 5 seconds) blink sequence.
|
|
mTime = ev->time;
|
|
}
|
|
}
|
|
|
|
static uint16_t getCurrentChannelInCurrentZoneForGD77S(void)
|
|
{
|
|
int16_t chanIdx = codeplugGetLastUsedChannelInCurrentZone();
|
|
return (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) ? chanIdx : (chanIdx + 1));
|
|
}
|
|
|
|
static void checkAndUpdateSelectedChannelForGD77S(uint16_t chanNum, bool forceSpeech)
|
|
{
|
|
bool updateDisplay = false;
|
|
|
|
if(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
|
|
{
|
|
GD77SParameters.channelOutOfBounds = false;
|
|
if (codeplugAllChannelsIndexIsInUse(chanNum))
|
|
{
|
|
if (chanNum != codeplugGetLastUsedChannelInCurrentZone())
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, chanNum);
|
|
uiChannelModeLoadChannelData(false, false);
|
|
updateDisplay = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GD77SParameters.channelOutOfBounds = true;
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_CHANNEL);
|
|
voicePromptsAppendLanguageString(currentLanguage->error);
|
|
voicePromptsPlay();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((chanNum - 1) < currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone)
|
|
{
|
|
GD77SParameters.channelOutOfBounds = false;
|
|
if ((chanNum - 1) != codeplugGetLastUsedChannelInCurrentZone())
|
|
{
|
|
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, (chanNum - 1));
|
|
uiChannelModeLoadChannelData(false, false);
|
|
updateDisplay = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GD77SParameters.channelOutOfBounds = true;
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_CHANNEL);
|
|
voicePromptsAppendLanguageString(currentLanguage->error);
|
|
voicePromptsPlay();
|
|
}
|
|
}
|
|
|
|
// Prevent TXing while an invalid channel is selected
|
|
if (getCurrentChannelInCurrentZoneForGD77S() != chanNum)
|
|
{
|
|
PTTLocked = true;
|
|
}
|
|
else
|
|
{
|
|
if (PTTLocked)
|
|
{
|
|
PTTLocked = false;
|
|
forceSpeech = true;
|
|
}
|
|
}
|
|
|
|
if (updateDisplay || forceSpeech)
|
|
{
|
|
// Remove TS override when a new channel is selected, otherwise it will be set until the zone is changed.
|
|
if (updateDisplay && (tsGetManualOverride(CHANNEL_CHANNEL) != 0))
|
|
{
|
|
tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);
|
|
}
|
|
|
|
if (GD77SParameters.channelOutOfBounds == false)
|
|
{
|
|
char buf[17];
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_CHANNEL);
|
|
voicePromptsAppendInteger(chanNum);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
codeplugUtilConvertBufToString(currentChannelData->name, buf, 16);
|
|
voicePromptsAppendString(buf);
|
|
voicePromptsPlay();
|
|
}
|
|
|
|
if (!forceSpeech)
|
|
{
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void buildSpeechChannelDetailsForGD77S()
|
|
{
|
|
char buf[17];
|
|
|
|
codeplugUtilConvertBufToString(currentChannelData->name, buf, 16);
|
|
voicePromptsAppendString(buf);
|
|
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
announceFrequency();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
announceContactNameTgOrPc(voicePromptsIsPlaying());
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
announceTS();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
announceCC();
|
|
}
|
|
else
|
|
{
|
|
CodeplugCSSTypes_t type = codeplugGetCSSType(currentChannelData->rxTone);
|
|
if ((type & CSS_TYPE_NONE) == 0)
|
|
{
|
|
buildCSSCodeVoicePrompts(currentChannelData->rxTone, type, DIRECTION_RECEIVE, true);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
}
|
|
|
|
type = codeplugGetCSSType(currentChannelData->txTone);
|
|
if ((type & CSS_TYPE_NONE) == 0)
|
|
{
|
|
buildCSSCodeVoicePrompts(currentChannelData->txTone, type, DIRECTION_TRANSMIT, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void buildSpeechUiModeForGD77S(GD77S_UIMODES_t uiMode)
|
|
{
|
|
char buf[17];
|
|
|
|
if (voicePromptsIsPlaying())
|
|
{
|
|
voicePromptsTerminate();
|
|
}
|
|
|
|
switch (uiMode)
|
|
{
|
|
case GD77S_UIMODE_TG_OR_SQUELCH: // Channel
|
|
codeplugUtilConvertBufToString(currentChannelData->name, buf, 16);
|
|
voicePromptsAppendString(buf);
|
|
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
announceTS();
|
|
}
|
|
else
|
|
{
|
|
announceSquelchLevel(voicePromptsIsPlaying());
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_SCAN: // Scan
|
|
voicePromptsAppendLanguageString(currentLanguage->scan);
|
|
voicePromptsAppendLanguageString(uiDataGlobal.Scan.active ? currentLanguage->on : currentLanguage->off);
|
|
break;
|
|
|
|
case GD77S_UIMODE_TS: // Timeslot
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
announceTS();
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_CC: // Color code
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
announceCC();
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_FILTER: // DMR/Analog filter
|
|
voicePromptsAppendLanguageString(currentLanguage->filter);
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (nonVolatileSettings.dmrDestinationFilter == DMR_DESTINATION_FILTER_NONE)
|
|
{
|
|
voicePromptsAppendLanguageString(currentLanguage->none);
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendString(DMR_DESTINATION_FILTER_LEVELS[nonVolatileSettings.dmrDestinationFilter - 1]);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (nonVolatileSettings.analogFilterLevel == ANALOG_FILTER_NONE)
|
|
{
|
|
voicePromptsAppendLanguageString(currentLanguage->none);
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendString((char *)ANALOG_FILTER_LEVELS[nonVolatileSettings.analogFilterLevel - 1]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_DTMF_CONTACTS:
|
|
if (GD77SParameters.dtmfListCount > 0)
|
|
{
|
|
CodeplugDTMFContact_t dtmfContact;
|
|
|
|
codeplugDTMFContactGetDataForNumber(GD77SParameters.dtmfListSelected + 1, &dtmfContact);
|
|
codeplugUtilConvertBufToString(dtmfContact.name, buf, 16);
|
|
voicePromptsAppendString(buf);
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendLanguageString(currentLanguage->list_empty);
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_ZONE: // Zone
|
|
announceZoneName(voicePromptsIsPlaying());
|
|
break;
|
|
|
|
case GD77S_UIMODE_POWER: // Power
|
|
announcePowerLevel(voicePromptsIsPlaying());
|
|
break;
|
|
|
|
case GD77S_UIMODE_ECO:
|
|
announceEcoLevel(voicePromptsIsPlaying());
|
|
break;
|
|
|
|
case GD77S_UIMODE_MAX:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void handleEventForGD77S(uiEvent_t *ev)
|
|
{
|
|
if (ev->events & ROTARY_EVENT)
|
|
{
|
|
if (dtmfSequenceIsKeying())
|
|
{
|
|
dtmfSequenceStop();
|
|
}
|
|
|
|
if (!trxTransmissionEnabled && (ev->rotary > 0))
|
|
{
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
scanStop(false);
|
|
}
|
|
|
|
settingsSet(nonVolatileSettings.overrideTG, 0);
|
|
checkAndUpdateSelectedChannelForGD77S(ev->rotary, false);
|
|
HRC6000ClearActiveDMRID();
|
|
lastHeardClearLastID();
|
|
}
|
|
}
|
|
|
|
if (handleMonitorMode(ev))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (dtmfSequenceIsKeying() && (ev->buttons & (BUTTON_SK1 | BUTTON_SK2 | BUTTON_ORANGE)))
|
|
{
|
|
dtmfSequenceStop();
|
|
}
|
|
|
|
if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && uiDataGlobal.Scan.active)
|
|
{
|
|
scanStop(false);
|
|
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77S_UIMODE_SCAN);
|
|
voicePromptsPlay();
|
|
return;
|
|
}
|
|
|
|
if (BUTTONCHECK_LONGDOWN(ev, BUTTON_ORANGE) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
announceItem(PROMPT_SEQUENCE_BATTERY, PROMPT_THRESHOLD_3);
|
|
return;
|
|
}
|
|
else if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
voicePrompt_t vp = NUM_VOICE_PROMPTS;
|
|
const char *vpString = NULL;
|
|
|
|
GD77SParameters.uiMode = (GD77S_UIMODES_t) (GD77SParameters.uiMode + 1) % GD77S_UIMODE_MAX;
|
|
|
|
//skip over Digital controls if the radio is in Analog mode
|
|
if (trxGetMode() == RADIO_MODE_ANALOG)
|
|
{
|
|
// Analog
|
|
if ((GD77SParameters.uiMode == GD77S_UIMODE_TS) ||
|
|
(GD77SParameters.uiMode == GD77S_UIMODE_CC))
|
|
{
|
|
GD77SParameters.uiMode = GD77S_UIMODE_FILTER;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// digital
|
|
if (GD77SParameters.uiMode == GD77S_UIMODE_DTMF_CONTACTS)
|
|
{
|
|
GD77SParameters.uiMode = GD77S_UIMODE_ZONE;
|
|
}
|
|
}
|
|
|
|
switch (GD77SParameters.uiMode)
|
|
{
|
|
case GD77S_UIMODE_TG_OR_SQUELCH: // Channel Mode
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
vp = PROMPT_CHANNEL_MODE;
|
|
}
|
|
else
|
|
{
|
|
vpString = currentLanguage->squelch;
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_SCAN:
|
|
vp = PROMPT_SCAN_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_TS: // Timeslot Mode
|
|
vp = PROMPT_TIMESLOT_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_CC: // ColorCode Mode
|
|
vp = PROMPT_COLORCODE_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_FILTER: // DMR/Analog Filter
|
|
vp = PROMPT_FILTER_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_DTMF_CONTACTS:
|
|
vpString = currentLanguage->dtmf_contact_list;
|
|
break;
|
|
|
|
case GD77S_UIMODE_ZONE: // Zone Mode
|
|
vp = PROMPT_ZONE_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_POWER: // Power Mode
|
|
vp = PROMPT_POWER_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_ECO:
|
|
vp = PROMPT_ECO_MODE;
|
|
break;
|
|
|
|
case GD77S_UIMODE_MAX:
|
|
break;
|
|
}
|
|
|
|
if ((vp != NUM_VOICE_PROMPTS) || (vpString != NULL))
|
|
{
|
|
voicePromptsInit();
|
|
if (vpString)
|
|
{
|
|
voicePromptsAppendLanguageString(vpString);
|
|
voicePromptsAppendPrompt(PROMPT_MODE);
|
|
}
|
|
else
|
|
{
|
|
voicePromptsAppendPrompt(vp);
|
|
}
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
}
|
|
}
|
|
else if (BUTTONCHECK_LONGDOWN(ev, BUTTON_SK1) && (monitorModeData.isEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
if (GD77SParameters.channelOutOfBounds == false)
|
|
{
|
|
voicePromptsInit();
|
|
buildSpeechChannelDetailsForGD77S();
|
|
voicePromptsPlay();
|
|
}
|
|
}
|
|
else if (BUTTONCHECK_SHORTUP(ev, BUTTON_SK1) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
switch (GD77SParameters.uiMode)
|
|
{
|
|
case GD77S_UIMODE_TG_OR_SQUELCH:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
// Next in TGList
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1);
|
|
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1))
|
|
{
|
|
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0);
|
|
}
|
|
}
|
|
settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden
|
|
menuPrivateCallClear();
|
|
updateTrxID();
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3);
|
|
}
|
|
else
|
|
{
|
|
if(currentChannelData->sql == 0) //If we were using default squelch level
|
|
{
|
|
currentChannelData->sql = nonVolatileSettings.squelchDefaults[currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point.
|
|
}
|
|
|
|
if (currentChannelData->sql < CODEPLUG_MAX_VARIABLE_SQUELCH)
|
|
{
|
|
currentChannelData->sql++;
|
|
}
|
|
|
|
announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3);
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_SCAN:
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
scanStop(false);
|
|
}
|
|
else
|
|
{
|
|
scanStart(false);
|
|
}
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(currentLanguage->scan);
|
|
voicePromptsAppendLanguageString(uiDataGlobal.Scan.active ? currentLanguage->on : currentLanguage->off);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_TS:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
toggleTimeslotForGD77S();
|
|
announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_3);
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_CC:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (currentChannelData->txColor < 15)
|
|
{
|
|
currentChannelData->txColor++;
|
|
trxSetDMRColourCode(currentChannelData->txColor);
|
|
}
|
|
|
|
voicePromptsInit();
|
|
announceCC();
|
|
voicePromptsPlay();
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_FILTER:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (nonVolatileSettings.dmrDestinationFilter < NUM_DMR_DESTINATION_FILTER_LEVELS - 1)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.dmrDestinationFilter, 1);
|
|
HRC6000InitDigitalDmrRx();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nonVolatileSettings.analogFilterLevel < NUM_ANALOG_FILTER_LEVELS - 1)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.analogFilterLevel, 1);
|
|
trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel);
|
|
}
|
|
}
|
|
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_DTMF_CONTACTS:
|
|
// select next DTMF contact and spell it
|
|
if (GD77SParameters.dtmfListCount > 0)
|
|
{
|
|
GD77SParameters.dtmfListSelected = (GD77SParameters.dtmfListSelected + 1) % GD77SParameters.dtmfListCount;
|
|
}
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_ZONE: // Zones
|
|
// No "All Channels" on GD77S
|
|
nonVolatileSettings.currentZone = (nonVolatileSettings.currentZone + 1) % (codeplugZonesGetCount() - 1);
|
|
|
|
settingsSet(nonVolatileSettings.overrideTG, 0); // remove any TG override
|
|
tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);
|
|
currentChannelData->rxFreq = 0U; // Flag to the Channel screen that the channel data is now invalid and needs to be reloaded
|
|
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true);
|
|
checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), false);
|
|
GD77SParameters.uiMode = GD77S_UIMODE_ZONE;
|
|
|
|
announceItem(PROMPT_SEQUENCE_ZONE, PROMPT_THRESHOLD_3);
|
|
break;
|
|
|
|
case GD77S_UIMODE_POWER: // Power
|
|
increasePowerLevel(true);// true = Allow 5W++
|
|
break;
|
|
|
|
case GD77S_UIMODE_ECO:
|
|
if (nonVolatileSettings.ecoLevel < ECO_LEVEL_MAX)
|
|
{
|
|
settingsIncrement(nonVolatileSettings.ecoLevel, 1);
|
|
rxPowerSavingSetLevel(nonVolatileSettings.ecoLevel);
|
|
}
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_MAX:
|
|
break;
|
|
}
|
|
}
|
|
else if (BUTTONCHECK_LONGDOWN(ev, BUTTON_SK2) && (monitorModeData.isEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
uint32_t tg = (LinkHead->talkGroupOrPcId & 0xFFFFFF);
|
|
|
|
// If Blue button is long pressed during reception it sets the Tx TG to the incoming TG
|
|
if (uiDataGlobal.isDisplayingQSOData && BUTTONCHECK_DOWN(ev, BUTTON_SK2) && (trxGetMode() == RADIO_MODE_DIGITAL) &&
|
|
((trxTalkGroupOrPcId != tg) ||
|
|
((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot())) ||
|
|
(trxGetDMRColourCode() != currentChannelData->txColor)))
|
|
{
|
|
lastHeardClearLastID();
|
|
|
|
// Set TS to overriden TS
|
|
if ((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot()))
|
|
{
|
|
trxSetDMRTimeSlot(dmrMonitorCapturedTS, true);
|
|
tsSetManualOverride(CHANNEL_CHANNEL, (dmrMonitorCapturedTS + 1));
|
|
}
|
|
if (trxTalkGroupOrPcId != tg)
|
|
{
|
|
if ((tg >> 24) & PC_CALL_FLAG)
|
|
{
|
|
acceptPrivateCall(tg & 0xffffff, -1);
|
|
}
|
|
else
|
|
{
|
|
trxTalkGroupOrPcId = tg;
|
|
settingsSet(nonVolatileSettings.overrideTG, trxTalkGroupOrPcId);
|
|
}
|
|
}
|
|
|
|
currentChannelData->txColor = trxGetDMRColourCode();// Set the CC to the current CC, which may have been determined by the CC finding algorithm in C6000.c
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendLanguageString(currentLanguage->select_tx);
|
|
voicePromptsPlay();
|
|
return;
|
|
}
|
|
}
|
|
else if (BUTTONCHECK_SHORTUP(ev, BUTTON_SK2) && (uiDataGlobal.DTMFContactList.isKeying == false))
|
|
{
|
|
switch (GD77SParameters.uiMode)
|
|
{
|
|
case GD77S_UIMODE_TG_OR_SQUELCH: // Previous in TGList
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
// To Do change TG in on same channel freq
|
|
if (nonVolatileSettings.overrideTG == 0)
|
|
{
|
|
settingsDecrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1);
|
|
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < 0)
|
|
{
|
|
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], (int16_t) (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1));
|
|
}
|
|
}
|
|
settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden
|
|
menuPrivateCallClear();
|
|
updateTrxID();
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
uiChannelModeUpdateScreen(0);
|
|
announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3);
|
|
}
|
|
else
|
|
{
|
|
if(currentChannelData->sql == 0) //If we were using default squelch level
|
|
{
|
|
currentChannelData->sql = nonVolatileSettings.squelchDefaults[currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point.
|
|
}
|
|
|
|
if (currentChannelData->sql > CODEPLUG_MIN_VARIABLE_SQUELCH)
|
|
{
|
|
currentChannelData->sql--;
|
|
}
|
|
|
|
announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3);
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_SCAN:
|
|
if (uiDataGlobal.Scan.active)
|
|
{
|
|
// if we are scanning and down key is pressed then enter current channel into nuisance delete array.
|
|
if(uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)
|
|
{
|
|
// There is two channels available in the Zone, just stop scanning
|
|
if (uiDataGlobal.Scan.nuisanceDeleteIndex == (uiDataGlobal.Scan.availableChannelsCount - 2))
|
|
{
|
|
uiDataGlobal.Scan.lastIteration = true;
|
|
}
|
|
|
|
uiDataGlobal.Scan.nuisanceDelete[uiDataGlobal.Scan.nuisanceDeleteIndex] = uiDataGlobal.currentSelectedChannelNumber;
|
|
uiDataGlobal.Scan.nuisanceDeleteIndex = (uiDataGlobal.Scan.nuisanceDeleteIndex + 1) % MAX_ZONE_SCAN_NUISANCE_CHANNELS;
|
|
uiDataGlobal.Scan.timer.timeout = SCAN_SKIP_CHANNEL_INTERVAL; //force scan to continue;
|
|
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
|
|
return;
|
|
}
|
|
|
|
// Left key reverses the scan direction
|
|
if (uiDataGlobal.Scan.state == SCAN_STATE_SCANNING)
|
|
{
|
|
uiDataGlobal.Scan.direction *= -1;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_TS:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
toggleTimeslotForGD77S();
|
|
announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_3);
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_CC:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (currentChannelData->txColor > 0)
|
|
{
|
|
currentChannelData->txColor--;
|
|
trxSetDMRColourCode(currentChannelData->txColor);
|
|
}
|
|
|
|
voicePromptsInit();
|
|
announceCC();
|
|
voicePromptsPlay();
|
|
}
|
|
break;
|
|
|
|
case GD77S_UIMODE_FILTER:
|
|
if (trxGetMode() == RADIO_MODE_DIGITAL)
|
|
{
|
|
if (nonVolatileSettings.dmrDestinationFilter > DMR_DESTINATION_FILTER_NONE)
|
|
{
|
|
settingsDecrement(nonVolatileSettings.dmrDestinationFilter, 1);
|
|
HRC6000InitDigitalDmrRx();
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (nonVolatileSettings.analogFilterLevel > ANALOG_FILTER_NONE)
|
|
{
|
|
settingsDecrement(nonVolatileSettings.analogFilterLevel, 1);
|
|
trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel);
|
|
}
|
|
}
|
|
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_DTMF_CONTACTS:
|
|
// select previous DTMF contact and spell it
|
|
if (GD77SParameters.dtmfListCount > 0)
|
|
{
|
|
GD77SParameters.dtmfListSelected = (GD77SParameters.dtmfListSelected + GD77SParameters.dtmfListCount - 1) % GD77SParameters.dtmfListCount;
|
|
}
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_ZONE: // Zones
|
|
// No "All Channels" on GD77S
|
|
nonVolatileSettings.currentZone = (nonVolatileSettings.currentZone + (codeplugZonesGetCount() - 1) - 1) % (codeplugZonesGetCount() - 1);
|
|
|
|
settingsSet(nonVolatileSettings.overrideTG, 0); // remove any TG override
|
|
tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);
|
|
currentChannelData->rxFreq = 0U; // Flag to the Channel screeen that the channel data is now invalid and needs to be reloaded
|
|
|
|
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true);
|
|
checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), false);
|
|
GD77SParameters.uiMode = GD77S_UIMODE_ZONE;
|
|
|
|
announceItem(PROMPT_SEQUENCE_ZONE, PROMPT_THRESHOLD_3);
|
|
break;
|
|
|
|
case GD77S_UIMODE_POWER: // Power
|
|
decreasePowerLevel();
|
|
break;
|
|
|
|
case GD77S_UIMODE_ECO:
|
|
if (nonVolatileSettings.ecoLevel > 0)
|
|
{
|
|
settingsDecrement(nonVolatileSettings.ecoLevel, 1);
|
|
rxPowerSavingSetLevel(nonVolatileSettings.ecoLevel);
|
|
}
|
|
voicePromptsInit();
|
|
buildSpeechUiModeForGD77S(GD77SParameters.uiMode);
|
|
voicePromptsPlay();
|
|
break;
|
|
|
|
case GD77S_UIMODE_MAX:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // PLATFORM_GD77S
|