FreeTRX/MDUV380_firmware/application/source/user_interface/uiVFOMode.c
Marcus Kida f67bb2fade Mute: toggle audio mute with SK2 short-tap, with LED + header indicator
Add a one-tap audio mute on the VFO and Channel screens:

- SK2 short-tap toggles mute via the built-in audioAmpMute()/audioAmpIsMuted().
  The tap is only consumed by the existing "set TX TG to incoming" behaviour
  while a DMR QSO is on screen, so mute works everywhere else.
- Header: a bold "M" is shown just left of the battery while muted (the
  firmware's built-in strike-through over the mode text also still shows).
- LED: flashes red for 0.5s every 2s while muted (main loop), overriding the
  normal RX/TX LED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:41:29 +02:00

3913 lines
135 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 "hardware/HR-C6000.h"
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
#include "hardware/radioHardwareInterface.h"
#endif
#include "functions/trx.h"
#include "functions/rxPowerSaving.h"
#include "user_interface/menuSystem.h"
#include "user_interface/uiUtilities.h"
#include "user_interface/uiLocalisation.h"
#include "utils.h"
typedef enum
{
VFO_SELECTED_FREQUENCY_INPUT_RX,
VFO_SELECTED_FREQUENCY_INPUT_TX
} vfoSelectedFrequencyInput_t;
typedef enum
{
VFO_SCREEN_OPERATION_NORMAL,
VFO_SCREEN_OPERATION_SCAN,
VFO_SCREEN_OPERATION_DUAL_SCAN,
VFO_SCREEN_OPERATION_SWEEP
} vfoScreenOperationMode_t;
typedef enum
{
SWEEP_SETTING_STEP = 0,
SWEEP_SETTING_RSSI,
SWEEP_SETTING_GAIN
} sweepSetting_t;
// internal prototypes
static void handleEvent(uiEvent_t *ev);
static void handleQuickMenuEvent(uiEvent_t *ev);
static void updateQuickMenuScreen(bool isFirstRun);
static void updateFrequency(int frequency, bool announceImmediately);
static void stepFrequency(int increment);
static void toneScan(void);
static void scanning(void);
static void scanInit(void);
static void sweepScanInit(void);
static void sweepScanStep(void);
static void updateTrxID(void );
static void setCurrentFreqToScanLimits(void);
static void handleUpKey(uiEvent_t *ev);
static void handleDownKey(uiEvent_t *ev);
static void vfoSweepUpdateSamples(int offset, bool forceRedraw, int bandwidthRescale);
static void setSweepIncDecSetting(sweepSetting_t type, bool increment);
static void vfoSweepDrawSample(int offset);
static void clearNuisance(void);
static vfoSelectedFrequencyInput_t selectedFreq = VFO_SELECTED_FREQUENCY_INPUT_RX;
static const int SCAN_TONE_INTERVAL = 200;//time between each tone for lowest tone. (higher tones take less time.)
static uint8_t scanToneIndex = 0;
static CodeplugCSSTypes_t toneScanType = CSS_TYPE_CTCSS;
static CodeplugCSSTypes_t toneScanCSS = CSS_TYPE_NONE; // Here, CSS_NONE means *ALL* CSS types
static uint16_t prevCSSTone = (CODEPLUG_CSS_TONE_NONE - 1);
static vfoScreenOperationMode_t screenOperationMode[2] = { VFO_SCREEN_OPERATION_NORMAL, VFO_SCREEN_OPERATION_NORMAL };// For VFO A and B
static menuStatus_t menuVFOExitStatus = MENU_STATUS_SUCCESS;
static menuStatus_t menuQuickVFOExitStatus = MENU_STATUS_SUCCESS;
static bool quickmenuNewChannelHandled = false; // Quickmenu new channel confirmation window
static const int VFO_SWEEP_STEP_TIME = 25;// 25ms
#if defined(PLATFORM_RD5R)
#define VFO_SWEEP_GRAPH_START_Y 8
#define VFO_SWEEP_GRAPH_HEIGHT_Y 30
#elif defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
#define VFO_SWEEP_GRAPH_START_Y 10
#define VFO_SWEEP_GRAPH_HEIGHT_Y 78
#else
#define VFO_SWEEP_GRAPH_START_Y 10
#define VFO_SWEEP_GRAPH_HEIGHT_Y 38
#endif
static uint8_t vfoSweepSamples[VFO_SWEEP_NUM_SAMPLES];
static uint8_t vfoSweepRssiNoiseFloor = VFO_SWEEP_RSSI_NOISE_FLOOR_DEFAULT;
static uint8_t vfoSweepGain = VFO_SWEEP_GAIN_DEFAULT;
static bool vfoSweepSavedBandwidth;
const int VFO_SWEEP_SCAN_FREQ_STEP_TABLE[7] = {125,250,500,1000,2500,5000,10000};
static uint8_t previousVFONumber = 0xFF; // Keep track of the currently loaded channel data
// Public interface
menuStatus_t uiVFOMode(uiEvent_t *ev, bool isFirstRun)
{
static uint32_t m = 0, curm = 0;
if (isFirstRun)
{
#if ! defined(PLATFORM_GD77S)
// We're coming back from the lock screen (hence no channel init is needed, at all).
bool isLockMenu = (menuSystemGetPreviouslyPushedMenuNumber(true) == UI_LOCK_SCREEN);
if (isLockMenu || lockscreenIsRearming)
{
if (isLockMenu)
{
menuSystemGetPreviouslyPushedMenuNumber(false); // Clear the previous lock screen trace
}
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)
{
for(int i = 0; i < VFO_SWEEP_NUM_SAMPLES; i++)
{
vfoSweepDrawSample(i);
}
displayDrawFastVLine((uiDataGlobal.Scan.sweepSampleIndex) % VFO_SWEEP_NUM_SAMPLES, VFO_SWEEP_GRAPH_START_Y, VFO_SWEEP_GRAPH_HEIGHT_Y, true);// draw solid line in the next location
displayDrawFastVLine((uiDataGlobal.Scan.sweepSampleIndex + uiDataGlobal.Scan.sweepSampleIndexIncrement) % VFO_SWEEP_NUM_SAMPLES, VFO_SWEEP_GRAPH_START_Y, VFO_SWEEP_GRAPH_HEIGHT_Y, true);// draw solid line in the next location
displayRenderRows(1, ((8 + VFO_SWEEP_GRAPH_HEIGHT_Y) / 8) + 1);
}
return MENU_STATUS_SUCCESS;
}
#endif
uiDataGlobal.FreqEnter.index = 0;
uiDataGlobal.isDisplayingQSOData = false;
uiDataGlobal.reverseRepeaterVFO = false;
settingsSet(nonVolatileSettings.initialMenuNumber, (uint8_t) UI_VFO_MODE);
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_IDLE;
currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];
currentChannelData->libreDMR_Power = 0x00;// Force channel to the Master power
uiDataGlobal.currentSelectedChannelNumber = CH_DETAILS_VFO_CHANNEL;// This is not a regular channel. Its the special VFO channel!
uiDataGlobal.displayChannelSettings = false;
uiDataGlobal.talkaround = false;
#if defined(PLATFORM_MD9600)
// This could happen if a MK22 codeplug containing 220MHz channel(s) has been flashed.
if ((trxGetBandFromFrequency(currentChannelData->rxFreq) == FREQUENCY_OUT_OF_BAND) || (trxGetBandFromFrequency(currentChannelData->txFreq) == FREQUENCY_OUT_OF_BAND))
{
currentChannelData->rxFreq = OUT_OF_BAND_FALLBACK_FREQUENCY;
currentChannelData->txFreq = OUT_OF_BAND_FALLBACK_FREQUENCY;
}
#endif
radioSetTRxDevice(RADIO_DEVICE_PRIMARY);
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
#if defined(PLATFORM_MD2017)
if (true)
{
radioSetTRxDevice(RADIO_DEVICE_SECONDARY);
CodeplugChannel_t *secondaryChannel = &settingsVFOChannel[1 - nonVolatileSettings.currentVFONumber];
radioSetFrequency(secondaryChannel->rxFreq, false);
trxSetModeAndBandwidth(secondaryChannel->chMode, (codeplugChannelGetFlag(secondaryChannel, CHANNEL_FLAG_BW_25K) != 0));
radioSetTRxDevice(RADIO_DEVICE_PRIMARY);
}
#endif
//Need to load the Rx group if specified even if TG is currently overridden as we may need it later when the left or right button is pressed
if (currentChannelData->rxGroupList != 0)
{
if (currentChannelData->rxGroupList != lastLoadedRxGroup)
{
if (codeplugRxGroupGetDataForIndex(currentChannelData->rxGroupList, &currentRxGroupData))
{
lastLoadedRxGroup = currentChannelData->rxGroupList;
}
else
{
lastLoadedRxGroup = -1;
}
}
}
else
{
memset(&currentRxGroupData, 0xFF, sizeof(CodeplugRxGroup_t));// If the VFO doesnt have an Rx Group ( TG List) the global var needs to be cleared, otherwise it contains the data from the previous screen e.g. Channel screen
lastLoadedRxGroup = -1;
}
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
lastHeardClearLastID();
int nextMenu = menuSystemGetPreviouslyPushedMenuNumber(false); // used to determine if this screen has just been loaded after Tx ended (in loadChannelData()))
uiVFOModeLoadChannelData((((nextMenu == UI_TX_SCREEN) || (nextMenu == UI_LOCK_SCREEN) || (nextMenu == UI_PRIVATE_CALL)) ? false : true));
if ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) && (trxGetMode() == RADIO_MODE_ANALOG))
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
freqEnterReset();
uiVFOModeUpdateScreen(0);
settingsSetVFODirty();
if ((uiDataGlobal.VoicePrompts.inhibitInitial == false) &&
((uiDataGlobal.Scan.active == false) ||
(uiDataGlobal.Scan.active && ((uiDataGlobal.Scan.state == SCAN_STATE_SHORT_PAUSED) || (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)))))
{
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE,
((nextMenu == UI_TX_SCREEN) || (nextMenu == UI_PRIVATE_CALL)) ? PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY : PROMPT_THRESHOLD_2);
}
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)
{
// Refresh on every step if scan boundaries is equal to one frequency step.
uiDataGlobal.Scan.refreshOnEveryStep = ((nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] - nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber]) <= VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)]);
}
// 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;
}
menuVFOExitStatus = MENU_STATUS_SUCCESS;
}
else
{
menuVFOExitStatus = MENU_STATUS_SUCCESS;
#if ! defined(PLATFORM_GD77S)
// Don't go any further while APRS is beaconing
if (aprsBeaconingForcedManualBeaconingTriggered() && aprsBeaconingIsTransmitting())
{
return menuVFOExitStatus;
}
#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));
// We are entering digits, so update the screen as we have a cursor to blink
if ((uiDataGlobal.FreqEnter.index > 0) && ((ev->time - curm) > 300))
{
curm = ev->time;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Redraw will happen just below
}
// is there an incoming DMR signal
if ((uiDataGlobal.displayQSOState != QSO_DISPLAY_IDLE) || updateLHDisplay)
{
if (updateLHDisplay)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
uiVFOModeUpdateScreen(0);
// Force full redraw, due to Notification hidding while sweep scanning
if (uiVFOModeSweepScanning(true))
{
vfoSweepUpdateSamples(0, true, 0);
displayRender();
}
}
else
{
if ((ev->time - m) > RSSI_UPDATE_COUNTER_RELOAD)
{
if (rxPowerSavingIsRxOn())
{
bool doRendering = true;
if (uiDataGlobal.Scan.active && (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP) && (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
{
if (uiVFOModeDualWatchIsScanning())
{
// Header needs to be updated, if Dual Watch is scanning
uiUtilityRedrawHeaderOnly(true, false, ((ev->buttons & BUTTON_SK1) == BUTTON_SK1));
doRendering = false;
}
else if (uiVFOModeSweepScanning(true) == false)
{
uiUtilityDrawRSSIBarGraph();
}
}
// 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
if (doRendering)
{
if (uiNotificationIsVisible())
{
displayRender();
}
else
{
displayRenderRows(((uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)) ? 0 : 1), 2);
}
}
}
m = ev->time;
}
}
if (uiDataGlobal.Scan.toneActive)
{
toneScan();
}
#if ! (defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017))
if (uiDataGlobal.Scan.active)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
scanning();
}
else
{
sweepScanStep();
}
}
#endif
}
else
{
if (ev->hasEvent)
{
// Scanning barrier
if (uiDataGlobal.Scan.toneActive)
{
// Left key (alone) reverse tone scan direction
if ((ev->events & KEY_EVENT) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
{
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 MENU_STATUS_SUCCESS;
}
}
#if defined(PLATFORM_RD5R) // virtual ORANGE button will be implemented later, this CPP will be removed then.
if ((ev->keys.key != 0) && (ev->keys.event & KEY_MOD_UP))
#else
// PTT key is already handled in main().
if (((ev->events & BUTTON_EVENT) && BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE)) ||
((ev->keys.key != 0) && (ev->keys.event & KEY_MOD_UP)))
#endif
{
uiVFOModeStopScanning();
}
return MENU_STATUS_SUCCESS;
}
handleEvent(ev);
}
}
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
if (uiDataGlobal.Scan.active)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
scanning();
}
else
{
sweepScanStep();
}
}
#endif
}
return menuVFOExitStatus;
}
void uiVFOModeUpdateScreen(int txTimeSecs)
{
static bool blink = false;
static uint32_t blinkTime = 0;
char buffer[SCREEN_LINE_BUFFER_SIZE];
bool isTransmitting = isTransmittingIgnoringAPRSBeaconing();
// We don't want QSO info to be displayed while in Sweep scan, or screen redrawing while Sweep is paused
if (uiVFOModeSweepScanning(true) && ((uiDataGlobal.displayQSOState >= QSO_DISPLAY_CALLER_DATA) || (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)))
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_IDLE;
return;
}
// 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(uiVFOModeDualWatchIsScanning(), uiVFOModeSweepScanning(true), false);
return;
}
// We're currently displaying details or entering scan freq limits, and it shouldn't be overridden by QSO data
if ((uiDataGlobal.displayChannelSettings ||
((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN) && (uiDataGlobal.FreqEnter.index != 0)))
&& ((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(uiVFOModeDualWatchIsScanning(), uiVFOModeSweepScanning(true),
#if defined(PLATFORM_MD9600) || defined(CPU_MK22FN512VLL12)
false
#else
((buttonsRead() & BUTTON_SK1) == BUTTON_SK1)
#endif
);
switch(uiDataGlobal.displayQSOState)
{
case QSO_DISPLAY_DEFAULT_SCREEN:
lastHeardClearLastID();
if ((uiDataGlobal.Scan.active &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN) && (uiDataGlobal.Scan.state == SCAN_STATE_SCANNING)))
{
uiUtilityDisplayFrequency(DISPLAY_Y_POS_RX_FREQ, false, false, settingsVFOChannel[CHANNEL_VFO_A].rxFreq, true, true, 1);
uiUtilityDisplayFrequency(DISPLAY_Y_POS_TX_FREQ, false, false, settingsVFOChannel[CHANNEL_VFO_B].rxFreq, true, true, 2);
}
else
{
uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_DEFAULT_SCREEN;
uiDataGlobal.isDisplayingQSOData = false;
uiDataGlobal.receivedPcId = 0x00;
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
if (uiDataGlobal.displayChannelSettings)
{
uint32_t PCorTG = ((nonVolatileSettings.overrideTG != 0) ? nonVolatileSettings.overrideTG : codeplugContactGetPackedId(&currentContactData));
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s %u",
(((PCorTG >> 24) == PC_CALL_FLAG) ? currentLanguage->pc : currentLanguage->tg),
(PCorTG & 0xFFFFFF));
}
else
{
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(buffer, item->talkerAlias);
}
else
{
getCallsignOnly(buffer, item->contact);
}
}
else
{
goto displayContactName;
}
}
else
{
goto displayContactName;
}
}
else
{
displayContactName:
if (nonVolatileSettings.overrideTG != 0)
{
uiUtilityBuildTgOrPCDisplayName(buffer, SCREEN_LINE_BUFFER_SIZE);
uiUtilityDisplayInformation(NULL, DISPLAY_INFO_CONTACT_OVERRIDE_FRAME, (isTransmitting ? DISPLAY_Y_POS_CONTACT_TX_FRAME : -1));
}
else
{
codeplugUtilConvertBufToString(currentContactData.name, buffer, 16);
}
}
}
uiUtilityDisplayInformation(buffer, DISPLAY_INFO_CONTACT, (isTransmitting ? DISPLAY_Y_POS_CONTACT_TX : -1));
}
}
else
{
if (ticksTimerIsEnabled(&uiDataGlobal.DMRLastTalkerOnScreen.timer))
{
ticksTimerReset(&uiDataGlobal.DMRLastTalkerOnScreen.timer);
uiDataGlobal.DMRLastTalkerOnScreen.visible = false;
}
// Display some channel settings
if (uiDataGlobal.displayChannelSettings && (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
uiUtilityDisplayInformation(NULL, DISPLAY_INFO_CHANNEL_DETAILS, -1);
}
if(uiDataGlobal.Scan.toneActive)
{
if (toneScanType == CSS_TYPE_CTCSS)
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "CTCSS %3d.%dHz", currentChannelData->rxTone / 10, currentChannelData->rxTone % 10);
}
else if (toneScanType & CSS_TYPE_DCS)
{
dcsPrintf(buffer, SCREEN_LINE_BUFFER_SIZE, "DCS ", currentChannelData->rxTone);
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s", "TONE ERROR");
}
uiUtilityDisplayInformation(buffer, DISPLAY_INFO_CONTACT, -1);
}
}
if (uiDataGlobal.FreqEnter.index == 0)
{
if (!isTransmitting)
{
uiUtilityDisplayFrequency(((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP) ? DISPLAY_Y_POS_TX_FREQ : DISPLAY_Y_POS_RX_FREQ),
false, (selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_RX),
(uiDataGlobal.reverseRepeaterVFO ? currentChannelData->txFreq : currentChannelData->rxFreq), true,
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN), 0);
}
else
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, " %d ", txTimeSecs);
uiUtilityDisplayInformation(buffer, DISPLAY_INFO_TX_TIMER, -1);
}
if (((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL) ||
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN) ||
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)) || trxTransmissionEnabled)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
uiUtilityDisplayFrequency(DISPLAY_Y_POS_TX_FREQ, true, (selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX || trxTransmissionEnabled),
(uiDataGlobal.reverseRepeaterVFO ? currentChannelData->rxFreq : currentChannelData->txFreq), true, false, 0);
}
}
else
{
// Low/High scanning freqs
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u.%03u", nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber] / 100000, (nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber] - (nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber] / 100000) * 100000)/100);
displayPrintAt(2, DISPLAY_Y_POS_TX_FREQ, buffer, FONT_SIZE_3);
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%u.%03u", nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] / 100000, (nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] - (nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] / 100000) * 100000)/100);
displayPrintAt(DISPLAY_SIZE_X - ((7 * 8) + 2), DISPLAY_Y_POS_TX_FREQ, buffer, FONT_SIZE_3);
// Scanning direction arrow
static const int16_t scanDirArrow[2][6] = {
{ // Down
59 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) - 1),
67 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) - (FONT_SIZE_3_HEIGHT / 4) - 1),
67 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) + (FONT_SIZE_3_HEIGHT / 4) - 1)
}, // Up
{
59 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) + (FONT_SIZE_3_HEIGHT / 4) - 1),
59 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) - (FONT_SIZE_3_HEIGHT / 4) - 1),
67 + DISPLAY_H_OFFSET, (DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT / 2) - 1)
}
};
displayFillTriangle(scanDirArrow[(uiDataGlobal.Scan.direction > 0)][0], scanDirArrow[(uiDataGlobal.Scan.direction > 0)][1],
scanDirArrow[(uiDataGlobal.Scan.direction > 0)][2], scanDirArrow[(uiDataGlobal.Scan.direction > 0)][3],
scanDirArrow[(uiDataGlobal.Scan.direction > 0)][4], scanDirArrow[(uiDataGlobal.Scan.direction > 0)][5], true);
}
}
else // Entering digits
{
int16_t xCursor = -1;
int16_t yCursor = -1;
#if defined(HAS_COLOURS)
bool dblHeight = settingsIsOptionBitSet(BIT_UI_USES_DOUBLE_HEIGHT);
#else
const bool dblHeight = false;
#endif
if ((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL) ||
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN))
{
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE,
#if defined(PLATFORM_RD5R)
"%c%c%c.%c%c%c%c%c",
#else
"%c%c%c.%c%c%c%c%c MHz",
#endif
uiDataGlobal.FreqEnter.digits[0], uiDataGlobal.FreqEnter.digits[1], uiDataGlobal.FreqEnter.digits[2],
uiDataGlobal.FreqEnter.digits[3], uiDataGlobal.FreqEnter.digits[4], uiDataGlobal.FreqEnter.digits[5], uiDataGlobal.FreqEnter.digits[6], uiDataGlobal.FreqEnter.digits[7]);
displayPrintCenteredDoubleHeight((dblHeight ? DISPLAY_Y_POS_RX_FREQ : ((selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX) ? DISPLAY_Y_POS_TX_FREQ : DISPLAY_Y_POS_RX_FREQ)), buffer, FONT_SIZE_3, dblHeight);
// Cursor
if (uiDataGlobal.FreqEnter.index < 8)
{
xCursor = ((DISPLAY_SIZE_X - (strlen(buffer) * 8)) >> 1) + ((uiDataGlobal.FreqEnter.index + ((uiDataGlobal.FreqEnter.index > 2) ? 1 : 0)) * 8);
yCursor = (dblHeight ?
DISPLAY_Y_POS_RX_FREQ :
((selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX) ? DISPLAY_Y_POS_TX_FREQ : DISPLAY_Y_POS_RX_FREQ))
+ ((FONT_SIZE_3_HEIGHT * (dblHeight ? 2 : 1)) - 2);
}
}
else
{
uint16_t hiX = DISPLAY_SIZE_X - ((7 * 8) + 2) - (DISPLAY_H_OFFSET / 2);
int labelsVOffset =
#if defined(PLATFORM_RD5R)
4;
#else
0;
#endif
#if defined(PLATFORM_VARIANT_DM1701)
uint16_t dm1701VOffset = (dblHeight ? 8 : 0);
#else
const uint16_t dm1701VOffset = 0;
#endif
#if defined(HAS_COLOURS)
if (dblHeight)
{
labelsVOffset += (FONT_SIZE_3_HEIGHT / 2);
}
#endif
displayPrintAt(5 + (DISPLAY_H_OFFSET / 2), DISPLAY_Y_POS_RX_FREQ - labelsVOffset - dm1701VOffset, currentLanguage->low, FONT_SIZE_3);
displayDrawFastVLine(0 + (DISPLAY_H_OFFSET / 2), DISPLAY_Y_POS_RX_FREQ - labelsVOffset - dm1701VOffset, DISPLAY_SIZE_Y - (DISPLAY_Y_POS_RX_FREQ - labelsVOffset), true);
displayDrawFastHLine(1 + (DISPLAY_H_OFFSET / 2), DISPLAY_Y_POS_TX_FREQ - (labelsVOffset / 2) - dm1701VOffset, 57, true);
sprintf(buffer, "%c%c%c.%c%c%c", uiDataGlobal.FreqEnter.digits[0], uiDataGlobal.FreqEnter.digits[1], uiDataGlobal.FreqEnter.digits[2],
uiDataGlobal.FreqEnter.digits[3], uiDataGlobal.FreqEnter.digits[4], uiDataGlobal.FreqEnter.digits[5]);
displayPrintAtDoubleHeight(2 + (DISPLAY_H_OFFSET / 2), DISPLAY_Y_POS_TX_FREQ + (DISPLAY_V_EXTRA_PIXELS / 8) - (dblHeight ? (FONT_SIZE_3_HEIGHT / 2) : 0) - dm1701VOffset, buffer, FONT_SIZE_3, dblHeight);
displayPrintAt(73 + ((DISPLAY_H_OFFSET / 2) * 3), DISPLAY_Y_POS_RX_FREQ - labelsVOffset - dm1701VOffset, currentLanguage->high, FONT_SIZE_3);
displayDrawFastVLine(68 + ((DISPLAY_H_OFFSET / 2) * 3), DISPLAY_Y_POS_RX_FREQ - labelsVOffset - dm1701VOffset, DISPLAY_SIZE_Y - (DISPLAY_Y_POS_RX_FREQ - labelsVOffset), true);
displayDrawFastHLine(69 + ((DISPLAY_H_OFFSET / 2) * 3), DISPLAY_Y_POS_TX_FREQ - (labelsVOffset / 2) - dm1701VOffset, 57, true);
sprintf(buffer, "%c%c%c.%c%c%c", uiDataGlobal.FreqEnter.digits[6], uiDataGlobal.FreqEnter.digits[7], uiDataGlobal.FreqEnter.digits[8],
uiDataGlobal.FreqEnter.digits[9], uiDataGlobal.FreqEnter.digits[10], uiDataGlobal.FreqEnter.digits[11]);
displayPrintAtDoubleHeight(hiX, DISPLAY_Y_POS_TX_FREQ + (DISPLAY_V_EXTRA_PIXELS / 8) - (dblHeight ? (FONT_SIZE_3_HEIGHT / 2) : 0) - dm1701VOffset, buffer, FONT_SIZE_3, dblHeight);
// Cursor
if (uiDataGlobal.FreqEnter.index < FREQ_ENTER_DIGITS_MAX)
{
xCursor = ((uiDataGlobal.FreqEnter.index < 6) ? 10 + (DISPLAY_H_OFFSET / 2) : hiX) // X start
+ (((uiDataGlobal.FreqEnter.index < 6) ? (uiDataGlobal.FreqEnter.index - 1) : (uiDataGlobal.FreqEnter.index - 7)) * 8) // Length
+ ((uiDataGlobal.FreqEnter.index > 2 ? (uiDataGlobal.FreqEnter.index > 8 ? 2 : 1) : 0) * 8); // MHz/kHz separator(s)
yCursor = DISPLAY_Y_POS_TX_FREQ + (FONT_SIZE_3_HEIGHT - 2) + (DISPLAY_V_EXTRA_PIXELS / 8) + (dblHeight ? (FONT_SIZE_3_HEIGHT / 2) : 0) - dm1701VOffset;
}
}
if ((xCursor >= 0) && (yCursor >= 0))
{
displayDrawFastHLine(xCursor + 1, yCursor, 6, blink);
if ((ticksGetMillis() - blinkTime) > 500)
{
blinkTime = ticksGetMillis();
blink = !blink;
}
}
}
}
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;
}
bool uiVFOModeIsTXFocused(void)
{
return (selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX);
}
void uiVFOModeStopScanning(void)
{
bool resetAPRS = true;
if (uiDataGlobal.Scan.toneActive)
{
if (prevCSSTone != (CODEPLUG_CSS_TONE_NONE - 1))
{
currentChannelData->rxTone = prevCSSTone;
prevCSSTone = (CODEPLUG_CSS_TONE_NONE - 1);
}
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
uiDataGlobal.Scan.toneActive = false;
trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel);// Restore the filter setting after the tone scan
resetAPRS = false;
}
uiDataGlobal.Scan.active = false;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN)
{
screenOperationMode[CHANNEL_VFO_A] = screenOperationMode[CHANNEL_VFO_B] = VFO_SCREEN_OPERATION_NORMAL;
settingsSet(nonVolatileSettings.currentVFONumber, nonVolatileSettings.currentVFONumber);
rxPowerSavingSetLevel(nonVolatileSettings.ecoLevel);// Level is reduced by 1 when Dual Watch , so re-instate it back to the correct setting
}
else if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)
{
screenOperationMode[nonVolatileSettings.currentVFONumber] = VFO_SCREEN_OPERATION_NORMAL;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
HRC6000ClearColorCodeSynchronisation();
}
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
uiVFOModeUpdateScreen(0); // Needs to redraw the screen now
if (resetAPRS)
{
aprsBeaconingResetTimers();
}
}
static void updateFrequency(int frequency, bool announceImmediately)
{
if (selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX)
{
if (trxGetBandFromFrequency(frequency) != FREQUENCY_OUT_OF_BAND)
{
currentChannelData->txFreq = frequency;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
soundSetMelody(MELODY_ACK_BEEP);
}
}
else
{
int deltaFrequency = frequency - currentChannelData->rxFreq;
if (trxGetBandFromFrequency(frequency) != FREQUENCY_OUT_OF_BAND)
{
currentChannelData->rxFreq = frequency;
currentChannelData->txFreq = currentChannelData->txFreq + deltaFrequency;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
if (trxGetBandFromFrequency(currentChannelData->txFreq) != FREQUENCY_OUT_OF_BAND)
{
soundSetMelody(MELODY_ACK_BEEP);
}
else
{
currentChannelData->txFreq = frequency;
soundSetMelody(MELODY_ERROR_BEEP);
}
}
else
{
soundSetMelody(MELODY_ERROR_BEEP);
}
}
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, announceImmediately);
menuPrivateCallClear();
settingsSetVFODirty();
}
void uiVFOModeLoadChannelData(bool forceAPRSReset)
{
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);
}
if (!uiDataGlobal.Scan.toneActive)
{
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
}
if (uiDataGlobal.Scan.active == false)
{
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
}
}
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) || (previousVFONumber != nonVolatileSettings.currentVFONumber)))))
{
trxSetDMRColourCode(currentChannelData->txColor);
HRC6000ClearColorCodeSynchronisation();
}
if (nonVolatileSettings.overrideTG == 0)
{
uiVFOLoadContact(&currentContactData);
// Check whether the contact data seems valid
if ((currentContactData.name[0] == 0) || (currentContactData.tgNumber == 0) || (currentContactData.tgNumber > 9999999))
{
settingsSet(nonVolatileSettings.overrideTG, 9);// If the VFO does not have an Rx Group list assigned to it. We can't get a TG from the codeplug. So use TG 9.
trxTalkGroupOrPcId = nonVolatileSettings.overrideTG;
trxSetDMRTimeSlot(codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_TIMESLOT_TWO), true);
tsSetContactHasBeenOverriden(((Channel_t)nonVolatileSettings.currentVFONumber), false);
}
else
{
trxTalkGroupOrPcId = codeplugContactGetPackedId(&currentContactData);
trxUpdateTsForCurrentChannelWithSpecifiedContact(&currentContactData);
}
}
else
{
int manTS = tsGetManualOverrideFromCurrentChannel();
trxTalkGroupOrPcId = nonVolatileSettings.overrideTG;
trxSetDMRTimeSlot((manTS ? (manTS - 1) : codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_TIMESLOT_TWO)), true);
}
}
if (uiDataGlobal.Scan.active == false)
{
if (forceAPRSReset || ((previousVFONumber != nonVolatileSettings.currentVFONumber) && (currentChannelData->chMode == RADIO_MODE_ANALOG)))
{
aprsBeaconingResetTimers();
}
}
previousVFONumber = nonVolatileSettings.currentVFONumber;
}
static void checkAndFixIndexInRxGroup(void)
{
if ((currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 0) &&
(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)))
{
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber], 0);
}
}
void uiVFOLoadContact(CodeplugContact_t *contact)
{
// Check if this channel has an Rx Group
if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup))
{
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber]], contact);
}
else
{
/* 2020.10.27 vk3kyy. The Contact should not be forced to none just because the Rx group list is none
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup == 0)
{
currentChannelData->contact = 0;
}*/
codeplugContactGetDataForIndex(currentChannelData->contact, contact);
}
}
static void toggleAnalogBandwidth(void)
{
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));
}
static void handleEvent(uiEvent_t *ev)
{
if (uiDataGlobal.Scan.active && (ev->events & KEY_EVENT))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)
{
// Right key sets the current frequency as a 'nuisance' frequency.
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
&&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_DUAL_SCAN)
&& (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
uiDataGlobal.Scan.nuisanceDelete[uiDataGlobal.Scan.nuisanceDeleteIndex] = currentChannelData->rxFreq;
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;
}
// Left key reverses the scan direction
if (
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380)
(ev->keys.key == KEY_FRONT_DOWN)
#else
(ev->keys.key == KEY_LEFT)
#endif
&&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_DUAL_SCAN)
&& (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
uiDataGlobal.Scan.direction *= -1;
keyboardReset();
return;
}
}
// Stop the scan on any key except UP/ROTARY_INC without SK2 (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)
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|| (ev->keys.key == KEY_ROTARY_INCREMENT)
#endif
) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)) == false)
&&
((((ev->keys.key == KEY_ROTARY_INCREMENT) || (ev->keys.key == KEY_ROTARY_DECREMENT) || (ev->keys.key == KEY_FRONT_UP) || (ev->keys.key == KEY_FRONT_DOWN)
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|| (ev->keys.key == KEY_LEFT) || (ev->keys.key == KEY_RIGHT) || (ev->keys.key == KEY_UP) || (ev->keys.key == KEY_DOWN)
#endif
|| (ev->keys.key == KEY_STAR)) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)) == false)
#else // NOT STM32 PLATFORMS, a.k.a MK22 HTs, and MD9600
(((ev->keys.key == KEY_UP) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)) == false)
&&
((((ev->keys.key == KEY_LEFT) || (ev->keys.key == KEY_RIGHT) || (ev->keys.key == KEY_UP) || (ev->keys.key == KEY_DOWN) || (ev->keys.key == KEY_STAR)) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)) == false)
#endif
)
{
uiVFOModeStopScanning();
keyboardReset();
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
return;
}
}
if (ev->events & FUNCTION_EVENT)
{
if (ev->function == FUNC_START_SCANNING)
{
scanInit();
setCurrentFreqToScanLimits();
uiDataGlobal.Scan.active = true;
return;
}
else if (ev->function == FUNC_REDRAW)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
return;
}
}
if (handleMonitorMode(ev))
{
uiDataGlobal.displayChannelSettings = false;
uiDataGlobal.reverseRepeaterVFO = false;
return;
}
if (ev->events & BUTTON_EVENT)
{
#if ! defined(PLATFORM_RD5R)
// Stop the scan if any button is pressed.
if (uiDataGlobal.Scan.active && BUTTONCHECK_DOWN(ev, BUTTON_ORANGE))
{
uiVFOModeStopScanning();
return;
}
#endif
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_t)nonVolatileSettings.currentVFONumber), (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;
uiVFOModeUpdateScreen(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();
uiVFOModeUpdateScreen(0);
return;
}
if ((uiVFOModeSweepScanning(true) == false) && (monitorModeData.isEnabled == false) && (uiDataGlobal.reverseRepeaterVFO == false) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) && BUTTONCHECK_DOWN(ev, BUTTON_SK2)))
{
int prevQSODisp = -1;
trxSetFrequency(currentChannelData->txFreq, currentChannelData->rxFreq, DMR_MODE_DMO);// Swap Tx and Rx freqs but force DMR Active mode
uiDataGlobal.reverseRepeaterVFO = true;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
if (uiDataGlobal.displayChannelSettings == false)
{
prevQSODisp = uiDataGlobal.displayQSOStatePrev;
uiDataGlobal.displayChannelSettings = true;
headerRowIsDirty = true;
}
uiVFOModeUpdateScreen(0);
if (prevQSODisp != -1)
{
uiDataGlobal.displayQSOStatePrev = prevQSODisp;
}
return;
}
else if (uiDataGlobal.reverseRepeaterVFO && ((BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0) || (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)))
{
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
uiDataGlobal.reverseRepeaterVFO = false;
// We are still displaying channel details (SK1 has been released), force to update the screen
if (uiDataGlobal.displayChannelSettings && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0))
{
uiDataGlobal.displayChannelSettings = false;
}
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
return;
}
// Display channel settings (CTCSS, Squelch) while SK1 is pressed
else if ((uiVFOModeSweepScanning(true) == false) && (monitorModeData.isEnabled == false) && (uiDataGlobal.displayChannelSettings == false) && BUTTONCHECK_DOWN(ev, BUTTON_SK1))
{
int prevQSODisp = uiDataGlobal.displayQSOStatePrev;
uiDataGlobal.displayChannelSettings = true;
headerRowIsDirty = true;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
uiDataGlobal.displayQSOStatePrev = prevQSODisp;
return;
}
else if (uiDataGlobal.displayChannelSettings && BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0)
{
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;
}
}
// Leaving Channel Details disable reverse repeater feature
if (uiDataGlobal.reverseRepeaterVFO)
{
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
uiDataGlobal.reverseRepeaterVFO = false;
}
uiVFOModeUpdateScreen(0);
return;
}
#if !defined(PLATFORM_RD5R)
if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
announceItem(PROMPT_SEQUENCE_BATTERY, AUDIO_PROMPT_MODE_VOICE_LEVEL_1);
}
else
{
menuSystemPushNewMenu(UI_VFO_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;
menuVFOExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
// End Trick
}
return;
}
#endif
}
if (ev->events & KEY_EVENT)
{
int keyval = 99;
if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
menuSystemPushNewMenu(MENU_CHANNEL_DETAILS);
freqEnterReset();
return;
}
else
{
if (uiDataGlobal.FreqEnter.index == 0)
{
menuSystemPushNewMenu(MENU_MAIN_MENU);
return;
}
}
}
if (uiDataGlobal.FreqEnter.index == 0)
{
#if defined(PLATFORM_MD9600)
if (KEYCHECK_LONGDOWN(ev->keys, KEY_GREEN))
{
if (uiDataGlobal.Scan.active)
{
uiVFOModeStopScanning();
}
menuSystemPushNewMenu(UI_VFO_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;
menuVFOExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
// End Trick
return;
}
else
#endif
if (KEYCHECK_LONGDOWN(ev->keys, KEY_HASH) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_HASH) == false))
{
if (uiDataGlobal.Scan.active && (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
uiVFOModeStopScanning();
}
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
sweepScanInit();
soundSetMelody(MELODY_KEY_LONG_BEEP);
}
return;
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_HASH))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
menuSystemPushNewMenu(MENU_CONTACT_QUICKLIST);
}
else
{
menuSystemPushNewMenu(MENU_NUMERICAL_ENTRY);
}
return;
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_STAR))
{
if (uiVFOModeSweepScanning(true) == false)
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
if (trxGetMode() == RADIO_MODE_ANALOG)
{
currentChannelData->chMode = RADIO_MODE_DIGITAL;
checkAndFixIndexInRxGroup();
uiVFOModeLoadChannelData(true);
updateTrxID();
menuVFOExitStatus |= MENU_STATUS_FORCE_FIRST;
}
else
{
currentChannelData->chMode = RADIO_MODE_ANALOG;
trxSetModeAndBandwidth(currentChannelData->chMode, (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_BW_25K) != 0));
}
announceItem(PROMPT_SEQUENCE_MODE, PROMPT_THRESHOLD_1);
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
else
{
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
// Toggle TimeSlot
trxSetDMRTimeSlot(1 - trxGetDMRTimeSlot(), true);
tsSetManualOverride(((Channel_t)nonVolatileSettings.currentVFONumber), (trxGetDMRTimeSlot() + 1));
if ((nonVolatileSettings.overrideTG == 0) && (currentContactData.reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00)
{
tsSetContactHasBeenOverriden(((Channel_t)nonVolatileSettings.currentVFONumber), true);
}
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
lastHeardClearLastID();
uiDataGlobal.displayQSOState = uiDataGlobal.displayQSOStatePrev;
uiVFOModeUpdateScreen(0);
if (trxGetDMRTimeSlot() == 0)
{
menuVFOExitStatus |= MENU_STATUS_FORCE_FIRST;
}
announceItem(PROMPT_SEQUENCE_TS,PROMPT_THRESHOLD_3);
}
else
{
toggleAnalogBandwidth();
uiDataGlobal.displayQSOState = uiDataGlobal.displayQSOStatePrev;
headerRowIsDirty = true;
uiVFOModeUpdateScreen(0);
}
}
}
else
{
if (trxGetMode() == RADIO_MODE_ANALOG)
{
toggleAnalogBandwidth();
}
}
}
else if ((uiVFOModeSweepScanning(true) == false) && KEYCHECK_LONGDOWN(ev->keys, KEY_STAR))
{
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
tsSetManualOverride(((Channel_t)nonVolatileSettings.currentVFONumber), TS_NO_OVERRIDE);
tsSetContactHasBeenOverriden(((Channel_t)nonVolatileSettings.currentVFONumber), false);
// Check if this channel has an Rx Group
if ((currentRxGroupData.name[0] != 0) &&
(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup))
{
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber]], &currentContactData);
}
else
{
codeplugContactGetDataForIndex(currentChannelData->contact, &currentContactData);
}
trxUpdateTsForCurrentChannelWithSpecifiedContact(&currentContactData);
lastHeardClearLastID();
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_1);
}
}
else if(uiVFOModeSweepScanning(true) && // Reset Sweep noise floor or Sweep Gain
((KEYCHECK_SHORTUP(ev->keys, KEY_DOWN) || KEYCHECK_SHORTUP(ev->keys, KEY_UP)
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|| KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_INCREMENT) || KEYCHECK_SHORTUP(ev->keys, KEY_ROTARY_DECREMENT)
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|| KEYCHECK_SHORTUP(ev->keys, KEY_FRONT_UP) || KEYCHECK_SHORTUP(ev->keys, KEY_FRONT_DOWN)
#endif
#endif
)
&& BUTTONCHECK_DOWN(ev, BUTTON_SK1)))
{
vfoSweepRssiNoiseFloor = VFO_SWEEP_RSSI_NOISE_FLOOR_DEFAULT;
vfoSweepGain = VFO_SWEEP_GAIN_DEFAULT;
settingsSet(nonVolatileSettings.vfoSweepSettings, ((uiDataGlobal.Scan.sweepStepSizeIndex << 12) | (vfoSweepRssiNoiseFloor << 7) | vfoSweepGain));
vfoSweepUpdateSamples(0, true, 0);
}
else if (
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || 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
)
{
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) && uiVFOModeSweepScanning(true))
{
setSweepIncDecSetting(SWEEP_SETTING_STEP, false);
headerRowIsDirty = true;
}
else
{
handleDownKey(ev);
}
return;
#else
if (uiVFOModeSweepScanning(true) == false)
{
handleDownKey(ev);
}
else
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
// Sweep noise floor
setSweepIncDecSetting(SWEEP_SETTING_RSSI, false);
}
else
{
// Sweep gain
setSweepIncDecSetting(SWEEP_SETTING_GAIN, false);
}
return;
}
#endif
}
else if (
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
KEYCHECK_LONGDOWN(ev->keys, KEY_FRONT_DOWN)
#else
KEYCHECK_LONGDOWN(ev->keys, KEY_DOWN)
#endif
)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)
{
screenOperationMode[nonVolatileSettings.currentVFONumber] = VFO_SCREEN_OPERATION_NORMAL;
nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
uiVFOModeStopScanning();
return;
}
}
else if (
#if defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || 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
)
{
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) && uiVFOModeSweepScanning(true))
{
setSweepIncDecSetting(SWEEP_SETTING_STEP, true);
headerRowIsDirty = true;
}
else
{
handleUpKey(ev);
}
return;
#else
if (uiVFOModeSweepScanning(true) == false)
{
handleUpKey(ev);
}
else
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
// Sweep noise floor
setSweepIncDecSetting(SWEEP_SETTING_RSSI, true);
}
else
{
// Sweep gain
setSweepIncDecSetting(SWEEP_SETTING_GAIN, true);
}
return;
}
#endif
}
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 ((screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SCAN) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_DUAL_SCAN) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
scanInit();
return;
}
else
{
if ((screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_DUAL_SCAN) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
setCurrentFreqToScanLimits();
if (uiDataGlobal.Scan.active == false)
{
// User maybe has change the mode, update.
// In DIGITAL 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);
}
uiDataGlobal.Scan.dwellTime = ((uiDataGlobal.Scan.stepTimeMilliseconds < dwellTime) ? dwellTime : uiDataGlobal.Scan.stepTimeMilliseconds);
}
else
{
uiDataGlobal.Scan.dwellTime = uiDataGlobal.Scan.stepTimeMilliseconds;
}
clearNuisance();
uiDataGlobal.Scan.active = true;
if (voicePromptsIsPlaying())
{
voicePromptsTerminate();
}
soundSetMelody(MELODY_KEY_LONG_BEEP);
keyboardReset();
}
}
}
}
#if defined(PLATFORM_DM1801)
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_RED) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_RED) == false))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK1))
{
uiChannelModeOrVFOModeThemeDaytimeChange(false, false);
return;
}
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_A_B))
{
#else
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_RED) && (KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_RED) == false))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK1))
{
uiChannelModeOrVFOModeThemeDaytimeChange(false, false);
return;
}
else
#endif
{
settingsSet(nonVolatileSettings.currentVFONumber, (1 - nonVolatileSettings.currentVFONumber));// Switch to other VFO
currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
menuVFOExitStatus = MENU_STATUS_SUCCESS;
menuSystemPopAllAndDisplayRootMenu(); // Force to set all TX/RX settings.
#if defined(PLATFORM_DM1801)
if (nonVolatileSettings.currentVFONumber == 0)
#else
if (nonVolatileSettings.currentVFONumber == 1) // Yes, inverted here, as the beep will apply to other VFO
#endif
{
menuVFOExitStatus |= MENU_STATUS_FORCE_FIRST;
}
else
{
menuVFOExitStatus = MENU_STATUS_SUCCESS;
}
}
return;
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK1))
{
uiChannelModeOrVFOModeThemeDaytimeChange(true, false);
return;
}
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) && (uiDataGlobal.tgBeforePcMode != 0))
{
settingsSet(nonVolatileSettings.overrideTG, uiDataGlobal.tgBeforePcMode);
updateTrxID();
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;// Force redraw
menuPrivateCallClear();
uiVFOModeUpdateScreen(0);
return;// The event has been handled
}
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
if ((trxGetMode() == RADIO_MODE_DIGITAL) && (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF))
{
HRC6000ClearActiveDMRID();
}
menuVFOExitStatus |= MENU_STATUS_FORCE_FIRST;// Audible signal that the Channel screen has been selected
menuSystemSetCurrentMenu(UI_CHANNEL_MODE);
aprsBeaconingResetTimers();
#endif
return;
}
#if defined(PLATFORM_DM1801) || defined(PLATFORM_RD5R)
else if (KEYCHECK_SHORTUP(ev->keys, KEY_VFO_MR))
{
if ((trxGetMode() == RADIO_MODE_DIGITAL) && (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF))
{
HRC6000ClearActiveDMRID();
}
menuVFOExitStatus |= MENU_STATUS_FORCE_FIRST;// Audible signal that the Channel screen has been selected
menuSystemSetCurrentMenu(UI_CHANNEL_MODE);
aprsBeaconingResetTimers();
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_VFO_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;
menuVFOExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
// End Trick
}
return;
}
#endif
else if (KEYCHECK_LONGDOWN(ev->keys, KEY_INCREASE) && BUTTONCHECK_DOWN(ev, BUTTON_SK2)) // set as KEY_RIGHT on some platforms + SK2
{
// Long press allows the 5W+ power setting to be selected immediately (but not while sweep scanning)
if ((uiVFOModeSweepScanning(true) == false) && increasePowerLevel(true))
{
headerRowIsDirty = true;
}
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_INCREASE)) // set as KEY_RIGHT on some platforms
{
if (uiVFOModeSweepScanning(true))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
// In Sweep scan, Right increase RSSI or GAIN
{
setSweepIncDecSetting(SWEEP_SETTING_RSSI, true);
return;
}
else
{
setSweepIncDecSetting(SWEEP_SETTING_GAIN, true);
return;
}
#else
// In Sweep scan, Right increase RX freq
{
setSweepIncDecSetting(SWEEP_SETTING_STEP, false);
headerRowIsDirty = true;
return;
}
else
{
handleUpKey(ev);
}
#endif
}
else
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
if (increasePowerLevel(false))
{
headerRowIsDirty = true;
}
}
else
{
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
if (uiDataGlobal.Scan.active == false)
{
#endif
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup != 0)
{
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1)
{
if (nonVolatileSettings.overrideTG == 0)
{
settingsIncrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber], 1);
checkAndFixIndexInRxGroup();
}
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] == 0)
{
menuVFOExitStatus |= (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_VFO_MODE, true);
}
uiVFOModeUpdateScreen(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);
uiVFOModeUpdateScreen(0);
}
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
}
else
{
handleUpKey(ev); //Up key while scan paused continues the scan
}
#endif
}
}
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_DECREASE)) // set as KEY_LEFT on some platforms
{
if (uiVFOModeSweepScanning(true))
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380)
// In Sweep scan, Right decrease RSSI or GAIN
{
setSweepIncDecSetting(SWEEP_SETTING_RSSI, false);
}
else
{
setSweepIncDecSetting(SWEEP_SETTING_GAIN, false);
}
return;
#else
// In Sweep scan, Left decrease RX freq
{
setSweepIncDecSetting(SWEEP_SETTING_STEP, true);
headerRowIsDirty = true;
return;
}
else
{
handleDownKey(ev);
}
#endif
}
else
{
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
if (decreasePowerLevel())
{
headerRowIsDirty = true;
}
if (trxGetPowerLevel() == 0)
{
menuVFOExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST);
}
}
else
{
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup != 0)
{
if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1)
{
// To Do change TG in on same channel freq
if (nonVolatileSettings.overrideTG == 0)
{
settingsDecrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber], 1);
if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] < 0)
{
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber],
(int16_t) (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1));
}
if(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] == 0)
{
menuVFOExitStatus |= 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_VFO_MODE, true);
}
uiVFOModeUpdateScreen(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);
uiVFOModeUpdateScreen(0);
}
}
}
}
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();
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 // (uiDataGlobal.FreqEnter.index == 0)
{
if (KEYCHECK_PRESS(ev->keys, KEY_DECREASE)) // set as KEY_LEFT on some platforms
{
uiDataGlobal.FreqEnter.index--;
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = '-';
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
{
freqEnterReset();
soundSetMelody(MELODY_NACK_BEEP);
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY);
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL)
{
int newFrequency = freqEnterRead(0, 8, false);
if (trxGetBandFromFrequency(newFrequency) != FREQUENCY_OUT_OF_BAND)
{
updateFrequency(newFrequency, PROMPT_THRESHOLD_3);
HRC6000ClearColorCodeSynchronisation();
freqEnterReset();
}
else
{
menuVFOExitStatus |= MENU_STATUS_ERROR;
}
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
else if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)
{
// Complete frequencies with zeros
// Low
if (uiDataGlobal.FreqEnter.index > 0 && uiDataGlobal.FreqEnter.index < 6)
{
memset(uiDataGlobal.FreqEnter.digits + uiDataGlobal.FreqEnter.index, '0', (6 - uiDataGlobal.FreqEnter.index) - 1);
uiDataGlobal.FreqEnter.index = 5;
keyval = 0;
} // High
else if (uiDataGlobal.FreqEnter.index > 6 && uiDataGlobal.FreqEnter.index < 12)
{
memset(uiDataGlobal.FreqEnter.digits + uiDataGlobal.FreqEnter.index, '0', (6 - (uiDataGlobal.FreqEnter.index - 6)) - 1);
uiDataGlobal.FreqEnter.index = 11;
keyval = 0;
}
else
{
if (uiDataGlobal.FreqEnter.index != 0)
{
menuVFOExitStatus |= MENU_STATUS_ERROR;
}
}
}
}
}
if (uiDataGlobal.FreqEnter.index < ((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL) ? 8 : 12))
{
// GREEN key was pressed while entering scan freq if keyval is != 99
if (keyval == 99)
{
keyval = menuGetKeypadKeyValue(ev, true);
#if !defined(PLATFORM_GD77S)
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;
}
}
return;
}
#endif
}
if ((keyval != 99) &&
// Not first '0' digit in frequencies: we don't support < 100 MHz
((((uiDataGlobal.FreqEnter.index == 0) && (keyval == 0)) == false) &&
(((screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN) && (uiDataGlobal.FreqEnter.index == 6) && (keyval == 0)) == false)) &&
(BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0))
{
voicePromptsInit();
voicePromptsAppendPrompt(PROMPT_0 + keyval);
if ((uiDataGlobal.FreqEnter.index == 2) || (uiDataGlobal.FreqEnter.index == 8))
{
voicePromptsAppendPrompt(PROMPT_POINT);
}
voicePromptsPlay();
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = (char) keyval + '0';
uiDataGlobal.FreqEnter.index++;
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL)
{
if (uiDataGlobal.FreqEnter.index == 8)
{
int newFreq = freqEnterRead(0, 8, false);
if (trxGetBandFromFrequency(newFreq) != FREQUENCY_OUT_OF_BAND)
{
updateFrequency(newFreq, AUDIO_PROMPT_MODE_BEEP);
HRC6000ClearColorCodeSynchronisation();
freqEnterReset();
soundSetMelody(MELODY_ACK_BEEP);
}
else
{
uiDataGlobal.FreqEnter.index--;
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = '-';
soundSetMelody(MELODY_ERROR_BEEP);
menuVFOExitStatus |= MENU_STATUS_ERROR;
}
}
}
else if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)
{
// Check low boundary
if (uiDataGlobal.FreqEnter.index == 6)
{
int fLower = freqEnterRead(0, 6, false) * 100;
if (trxGetBandFromFrequency(fLower) == FREQUENCY_OUT_OF_BAND)
{
uiDataGlobal.FreqEnter.index--;
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = '-';
soundSetMelody(MELODY_ERROR_BEEP);
menuVFOExitStatus |= MENU_STATUS_ERROR;
}
}
else if (uiDataGlobal.FreqEnter.index == FREQ_ENTER_DIGITS_MAX)
{
int fStep = VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)];
int fLower = freqEnterRead(0, 6, false) * 100;
int fUpper = freqEnterRead(6, 12, false) * 100;
// Reorg min/max
if (fLower > fUpper)
{
SAFE_SWAP(fLower, fUpper);
}
// At least on step diff
if ((fUpper - fLower) < fStep)
{
fUpper = fLower + fStep;
}
// Refresh on every step if scan boundaries is equal to one frequency step.
uiDataGlobal.Scan.refreshOnEveryStep = ((fUpper - fLower) <= fStep);
if ((trxGetBandFromFrequency(fLower) != FREQUENCY_OUT_OF_BAND) && (trxGetBandFromFrequency(fUpper) != FREQUENCY_OUT_OF_BAND))
{
settingsSet(nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber], (uint32_t) fLower);
settingsSet(nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber], (uint32_t) fUpper);
freqEnterReset();
soundSetMelody(MELODY_ACK_BEEP);
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
}
else
{
uiDataGlobal.FreqEnter.index--;
uiDataGlobal.FreqEnter.digits[uiDataGlobal.FreqEnter.index] = '-';
soundSetMelody(MELODY_ERROR_BEEP);
menuVFOExitStatus |= MENU_STATUS_ERROR;
}
}
}
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
}
}
}
static void handleUpKey(uiEvent_t *ev)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
// Don't permit to switch from RX/TX while scanning
if ((screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SCAN) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_DUAL_SCAN) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP))
{
selectedFreq = VFO_SELECTED_FREQUENCY_INPUT_RX;
announceItem(PROMPT_SEQUENCE_DIRECTION_RX, PROMPT_THRESHOLD_1);
}
}
else
{
if (uiDataGlobal.Scan.active)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)
{
stepFrequency(VFO_SWEEP_SCAN_FREQ_STEP_TABLE[uiDataGlobal.Scan.sweepStepSizeIndex]);
uiVFOModeUpdateScreen(0);
vfoSweepUpdateSamples(1, false, 0);
}
else
{
stepFrequency(VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)] * uiDataGlobal.Scan.direction);
uiDataGlobal.Scan.timer.timeout = 500;
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
uiVFOModeUpdateScreen(0);
}
}
else
{
stepFrequency(VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)]);
uiVFOModeUpdateScreen(0);
}
}
settingsSetVFODirty();
}
static void handleDownKey(uiEvent_t *ev)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
{
// Don't permit to switch from RX/TX while scanning
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_NORMAL)
{
selectedFreq = VFO_SELECTED_FREQUENCY_INPUT_TX;
announceItem(PROMPT_SEQUENCE_DIRECTION_TX, PROMPT_THRESHOLD_1);
}
}
else
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP)
{
stepFrequency(VFO_SWEEP_SCAN_FREQ_STEP_TABLE[uiDataGlobal.Scan.sweepStepSizeIndex] * -1);
}
else
{
stepFrequency(VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)] * -1);
}
uiVFOModeUpdateScreen(0);
settingsSetVFODirty();
if (uiDataGlobal.Scan.active && (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP))
{
vfoSweepUpdateSamples(-1, false, 0);
}
}
}
static void vfoSweepDrawSample(int offset)
{
int16_t graphHeight = MAX(vfoSweepSamples[offset] - vfoSweepRssiNoiseFloor, 0);
graphHeight = (graphHeight * VFO_SWEEP_GRAPH_HEIGHT_Y) / vfoSweepGain;
graphHeight = MIN(VFO_SWEEP_GRAPH_HEIGHT_Y, graphHeight);
int16_t levelTop = ((VFO_SWEEP_GRAPH_START_Y + VFO_SWEEP_GRAPH_HEIGHT_Y) - graphHeight);
// Draw the level
displayThemeApply(THEME_ITEM_FG_RSSI_BAR, THEME_ITEM_BG);
displayDrawFastVLine(offset, VFO_SWEEP_GRAPH_START_Y, (VFO_SWEEP_GRAPH_HEIGHT_Y - graphHeight), false); // Clear
displayDrawFastVLine(offset, levelTop, graphHeight, true); // Level
// center freq marker
if (offset == (DISPLAY_SIZE_X >> 1))
{
bool markerTopPosition = (graphHeight < ((VFO_SWEEP_GRAPH_HEIGHT_Y / 3) << 1));
int16_t markerStarts = (markerTopPosition ? VFO_SWEEP_GRAPH_START_Y : ((VFO_SWEEP_GRAPH_START_Y + VFO_SWEEP_GRAPH_HEIGHT_Y) - (VFO_SWEEP_GRAPH_HEIGHT_Y / 3)));
int16_t markerEnds = (markerTopPosition ? levelTop : (VFO_SWEEP_GRAPH_START_Y + VFO_SWEEP_GRAPH_HEIGHT_Y));
displayThemeApply(THEME_ITEM_FG_DECORATION, THEME_ITEM_BG);
for (int16_t y = markerStarts; y < markerEnds; y += 2)
{
displaySetPixel(offset, y, true);
displaySetPixel(offset, (y + 1), false);
}
}
displayThemeResetToDefault();
}
static void vfoSweepUpdateSamples(int offset, bool forceRedraw, int bandwidthRescale)
{
const int SHIFT_DISTANCE[7] = {6,6,6,6,8,8,8};
offset *= SHIFT_DISTANCE[uiDataGlobal.Scan.sweepStepSizeIndex];// real offset in samples;
if (offset != 0)
{
if (offset > 0)
{
uiDataGlobal.Scan.sweepSampleIndex = VFO_SWEEP_NUM_SAMPLES - 1 - offset;
memcpy(&vfoSweepSamples[0], &vfoSweepSamples[offset], VFO_SWEEP_NUM_SAMPLES - offset);
memset(&vfoSweepSamples[VFO_SWEEP_NUM_SAMPLES - offset], 0x00, offset);
}
else
{
uiDataGlobal.Scan.sweepSampleIndex = 0;
offset *= -1;
memmove(&vfoSweepSamples[offset], &vfoSweepSamples[0], VFO_SWEEP_NUM_SAMPLES - offset);
memset(&vfoSweepSamples[0], 0x00, offset);
}
}
if (bandwidthRescale != 0)
{
uint8_t tmp[VFO_SWEEP_NUM_SAMPLES];
memset(tmp, 0x00, VFO_SWEEP_NUM_SAMPLES);
int newStartSample = (VFO_SWEEP_NUM_SAMPLES / 2) - (VFO_SWEEP_NUM_SAMPLES / 4);
if (bandwidthRescale > 0)
{
for(int i = 0; i < VFO_SWEEP_NUM_SAMPLES; i+= 2)
{
int average = 0;
for(int j = 0; j < 2; j++ )
{
average += vfoSweepSamples[i + j];
}
volatile int outBufPos = (i / 2) + newStartSample;
tmp[outBufPos] = average / 2;
}
uiDataGlobal.Scan.sweepSampleIndex = ((VFO_SWEEP_NUM_SAMPLES * 3) / 4);// Most efficient is to start filling in from the area revealed on the right side of the screen
}
else
{
int newValue;
for(int i = 0; i < (VFO_SWEEP_NUM_SAMPLES - 1); i++)
{
newValue = (vfoSweepSamples[newStartSample + (i / 2)] + vfoSweepSamples[newStartSample + (i / 2) + 1]) / 2; // use simple 2 point expansion averaging
tmp[i] = newValue;
}
tmp[VFO_SWEEP_NUM_SAMPLES - 1] = newValue;
uiDataGlobal.Scan.sweepSampleIndex = 0;
}
memcpy(vfoSweepSamples, tmp, VFO_SWEEP_NUM_SAMPLES * sizeof(uint8_t));
}
if (forceRedraw || (offset != 0))
{
for(int i = 0; i < VFO_SWEEP_NUM_SAMPLES; i++)
{
vfoSweepDrawSample(i);
}
displayRenderRows(1, ((8 + VFO_SWEEP_GRAPH_HEIGHT_Y) / 8) + 1);
}
if (uiDataGlobal.Scan.state == SCAN_STATE_SCANNING)
{
uiDataGlobal.Scan.scanSweepCurrentFreq = currentChannelData->rxFreq + (VFO_SWEEP_SCAN_RANGE_SAMPLE_STEP_TABLE[uiDataGlobal.Scan.sweepStepSizeIndex] * (uiDataGlobal.Scan.sweepSampleIndex - (VFO_SWEEP_NUM_SAMPLES / 2))) / VFO_SWEEP_PIXELS_PER_STEP;
trxSetFrequency(uiDataGlobal.Scan.scanSweepCurrentFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
ticksTimerStart(&uiDataGlobal.Scan.timer, VFO_SWEEP_STEP_TIME);
}
}
static void setSweepIncDecSetting(sweepSetting_t type, bool increment)
{
bool apply = false;
uint16_t setting = nonVolatileSettings.vfoSweepSettings;
int bandwidthRescaleDirection = 0;
switch (type)
{
case SWEEP_SETTING_STEP:
{
int oldStepIndex = uiDataGlobal.Scan.sweepStepSizeIndex;
if (increment)
{
uiDataGlobal.Scan.sweepStepSizeIndex = SAFE_MIN(((sizeof(VFO_SWEEP_SCAN_RANGE_SAMPLE_STEP_TABLE) / sizeof(VFO_SWEEP_SCAN_RANGE_SAMPLE_STEP_TABLE[0])) - 1), (uiDataGlobal.Scan.sweepStepSizeIndex + 1));
bandwidthRescaleDirection = 1;
}
else
{
uiDataGlobal.Scan.sweepStepSizeIndex = SAFE_MAX(0, (uiDataGlobal.Scan.sweepStepSizeIndex - 1));
bandwidthRescaleDirection = -1;
}
if (oldStepIndex != uiDataGlobal.Scan.sweepStepSizeIndex)
{
apply = true;
setting = (uiDataGlobal.Scan.sweepStepSizeIndex << 12) | (nonVolatileSettings.vfoSweepSettings & 0xFFF);
}
}
break;
case SWEEP_SETTING_RSSI:
{
if (increment)
{
if (vfoSweepRssiNoiseFloor > VFO_SWEEP_RSSI_NOISE_FLOOR_MIN)
{
vfoSweepRssiNoiseFloor--;
apply = true;
}
}
else
{
if (vfoSweepRssiNoiseFloor < VFO_SWEEP_RSSI_NOISE_FLOOR_MAX)
{
vfoSweepRssiNoiseFloor++;
apply = true;
}
}
if (apply)
{
setting &= 0xF07F;
setting |= (vfoSweepRssiNoiseFloor << 7);
}
}
break;
case SWEEP_SETTING_GAIN:
{
if (increment)
{
if (vfoSweepGain > VFO_SWEEP_GAIN_STEP)
{
vfoSweepGain -= VFO_SWEEP_GAIN_STEP;
apply = true;
}
}
else
{
if (vfoSweepGain < VFO_SWEEP_GAIN_MAX)
{
vfoSweepGain += VFO_SWEEP_GAIN_STEP;
apply = true;
}
}
if (apply)
{
setting &= 0xFF80;
setting |= vfoSweepGain;
}
}
break;
}
settingsSet(nonVolatileSettings.vfoSweepSettings, setting);
settingsSaveIfNeeded(true);
if (apply)
{
vfoSweepUpdateSamples(0, true, bandwidthRescaleDirection);
}
}
static void stepFrequency(int increment)
{
int newTxFreq;
int newRxFreq;
if (selectedFreq == VFO_SELECTED_FREQUENCY_INPUT_TX)
{
newTxFreq = currentChannelData->txFreq + increment;
newRxFreq = currentChannelData->rxFreq; // Needed later for the band limited checking
}
else
{
// VFO_SELECTED_FREQUENCY_INPUT_RX
newRxFreq = currentChannelData->rxFreq + increment;
if (!uiDataGlobal.QuickMenu.tmpTxRxLockMode)
{
newTxFreq = currentChannelData->txFreq + increment;
}
else
{
newTxFreq = currentChannelData->txFreq;// Needed later for the band limited checking
}
}
// Out of frequency in the current band, update freq to the next or prev band.
if (trxGetBandFromFrequency(newRxFreq) == FREQUENCY_OUT_OF_BAND)
{
int band = trxGetNextOrPrevBandFromFrequency(newRxFreq, (increment > 0));
if (band != -1)
{
newRxFreq = ((increment > 0) ? RADIO_HARDWARE_FREQUENCY_BANDS[band].minFreq : RADIO_HARDWARE_FREQUENCY_BANDS[band].maxFreq);
newTxFreq = newRxFreq;
}
else
{
soundSetMelody(MELODY_ERROR_BEEP);
return;
}
}
if (trxGetBandFromFrequency(newRxFreq) != FREQUENCY_OUT_OF_BAND)
{
currentChannelData->txFreq = newTxFreq;
currentChannelData->rxFreq = newRxFreq;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
if (screenOperationMode[nonVolatileSettings.currentVFONumber] != VFO_SCREEN_OPERATION_SWEEP)
{
HRC6000ClearColorCodeSynchronisation();
}
if ((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)))
{
announceItem(PROMPT_SEQUENCE_VFO_FREQ_UPDATE, PROMPT_THRESHOLD_3);
}
}
else
{
soundSetMelody(MELODY_ERROR_BEEP);
}
}
// ---------------------------------------- Quick Menu functions -------------------------------------------------------------------
menuStatus_t uiVFOModeQuickMenu(uiEvent_t *ev, bool isFirstRun)
{
if (isFirstRun)
{
if (quickmenuNewChannelHandled)
{
quickmenuNewChannelHandled = false;
menuSystemPopAllAndDisplayRootMenu();
return MENU_STATUS_SUCCESS;
}
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel = nonVolatileSettings.dmrDestinationFilter;
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel = nonVolatileSettings.dmrCcTsFilter;
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel = nonVolatileSettings.analogFilterLevel;
uiDataGlobal.QuickMenu.tmpTxRxLockMode = settingsIsOptionBitSet(BIT_TX_RX_FREQ_LOCK);
uiDataGlobal.QuickMenu.tmpVFONumber = nonVolatileSettings.currentVFONumber;
uiDataGlobal.QuickMenu.tmpToneScanCSS = toneScanCSS;
uiDataGlobal.QuickMenu.tmpAudioMute = audioAmpIsMuted();
menuDataGlobal.numItems = NUM_VFO_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
{
menuQuickVFOExitStatus = MENU_STATUS_SUCCESS;
if (ev->hasEvent || (menuDataGlobal.menuOptionsTimeout > 0))
{
handleQuickMenuEvent(ev);
}
}
return menuQuickVFOExitStatus;
}
static bool validateNewChannel(void)
{
quickmenuNewChannelHandled = true;
if (uiDataGlobal.MessageBox.keyPressed == KEY_GREEN)
{
int16_t newChannelIndex;
//look for empty channel
for (newChannelIndex = CODEPLUG_CHANNELS_MIN; newChannelIndex <= CODEPLUG_CHANNELS_MAX; newChannelIndex++)
{
if (!codeplugAllChannelsIndexIsInUse(newChannelIndex))
{
break;
}
}
if (newChannelIndex <= CODEPLUG_CONTACTS_MAX)
{
int currentTS = trxGetDMRTimeSlot();
char nameBuf[SCREEN_LINE_BUFFER_SIZE];
CodeplugChannel_t tempChannel = channelScreenChannelData;
memcpy(&tempChannel.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
tempChannel.rxTone = currentChannelData->rxTone;
tempChannel.txTone = currentChannelData->txTone;
// Codeplug string aren't NULL terminated.
snprintf(nameBuf, SCREEN_LINE_BUFFER_SIZE, "%s %d", currentLanguage->new_channel, newChannelIndex);
memset(&tempChannel.name, 0xFF, sizeof(tempChannel.name));
memcpy(&tempChannel.name, nameBuf, strlen(nameBuf));
// change the TS on the new channel to whatever the radio is currently set to.
codeplugChannelSetFlag(&tempChannel, CHANNEL_FLAG_TIMESLOT_TWO, ((currentTS != 0) ? 1 : 0));
if (codeplugChannelSaveDataForIndex(newChannelIndex, &tempChannel))
{
codeplugAllChannelsIndexSetUsed(newChannelIndex); //Set channel index as valid
}
// Check if currentZone is initialized
if (currentZone.NOT_IN_CODEPLUGDATA_indexNumber == 0xDEADBEEF)
{
uiChannelInitializeCurrentZone();
}
// check if its real zone and or the virtual zone "All Channels" whose index is -1
if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone))
{
// All Channels virtual zone
settingsSet(nonVolatileSettings.currentZone, (int16_t) (codeplugZonesGetCount() - 1));//set zone to all channels and channel index to free channel found
// Change to the index of the new channel
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, newChannelIndex);
settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber]);
currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone++;
}
else
{
if (codeplugZoneAddChannelToZoneAndSave(newChannelIndex, &currentZone))
{
codeplugSetLastUsedChannelInZone(currentZone.NOT_IN_CODEPLUGDATA_indexNumber, (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1));
}
else
{
// channelScreenChannelData wasn't modified, only a new channel has been added, and it's available in AllZone.
nextKeyBeepMelody = (int16_t *)MELODY_NACK_BEEP;
return true;
}
}
// Channel saving succeeded, now we're sure that channel could
// be used in the channel screen.
memcpy(&channelScreenChannelData, &tempChannel, sizeof(tempChannel));
uiDataGlobal.currentSelectedChannelNumber = newChannelIndex;
channelScreenChannelData.rxFreq = 0; // NOT SURE IF THIS IS NECESSARY... Flag to the Channel screen that the channel data is now invalid and needs to be reloaded
uiDataGlobal.VoicePrompts.inhibitInitial = true;
tsSetManualOverride(((Channel_t) CHANNEL_CHANNEL), (currentTS + 1)); //copy current TS
// Just override TG/PC blindly, if not already set
if (nonVolatileSettings.overrideTG == 0)
{
settingsSet(nonVolatileSettings.overrideTG, trxTalkGroupOrPcId);
}
menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true);
nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;
quickmenuNewChannelHandled = false; // Need to do this, as uiVFOModeQuickMenu() won't be re-entered on the next menu iteration
return true;
}
nextKeyBeepMelody = (int16_t *)MELODY_NACK_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];
int prompt;// For voice prompts
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_VFO_SCREEN_QUICK_MENU_ITEMS, i);
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
{
continue;
}
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
{
break;
}
prompt = -1;// Prompt not used
buf[0] = 0;
rightSideVar[0] = 0;
rightSideConst = NULL;
leftSide = NULL;
switch(mNum)
{
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_RD5R) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
case VFO_SCREEN_QUICK_MENU_VFO_A_B:
sprintf(rightSideVar, "VFO %c", ((uiDataGlobal.QuickMenu.tmpVFONumber == 0) ? 'A' : 'B'));
break;
#endif
case VFO_SCREEN_QUICK_MENU_TX_SWAP_RX:
prompt = PROMPT_VFO_EXCHANGE_TX_RX;
strcpy(rightSideVar, "Tx <--> Rx");
break;
case VFO_SCREEN_QUICK_MENU_BOTH_TO_RX:
prompt = PROMPT_VFO_COPY_RX_TO_TX;
strcpy(rightSideVar, "Rx --> Tx");
break;
case VFO_SCREEN_QUICK_MENU_BOTH_TO_TX:
prompt = PROMPT_VFO_COPY_TX_TO_RX;
strcpy(rightSideVar, "Tx --> Rx");
break;
case VFO_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 VFO_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 VFO_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 VFO_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 VFO_SCREEN_QUICK_MENU_VFO_TO_NEW:
rightSideConst = currentLanguage->vfoToNewChannel;
break;
case VFO_SCREEN_QUICK_MENU_TONE_SCAN:
leftSide = currentLanguage->tone_scan;
if(trxGetMode() == RADIO_MODE_ANALOG)
{
const char *scanCSS[] = { currentLanguage->all, "CTCSS", "DCS", "iDCS" };
uint8_t offset = 0;
if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_NONE)
{
offset = 0;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_CTCSS)
{
offset = 1;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_DCS)
{
offset = 2;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == (CSS_TYPE_DCS | CSS_TYPE_DCS_INVERTED))
{
offset = 3;
}
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%s", scanCSS[offset]);
if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_NONE)
{
rightSideConst = currentLanguage->all;
}
}
else
{
rightSideConst = currentLanguage->n_a;
}
break;
case VFO_SCREEN_QUICK_MENU_DUAL_SCAN:
rightSideConst = currentLanguage->dual_watch;
break;
case VFO_SCREEN_QUICK_MENU_FREQ_BIND_MODE:
leftSide = currentLanguage->vfo_freq_bind_mode;
rightSideConst = (!uiDataGlobal.QuickMenu.tmpTxRxLockMode) ? currentLanguage->on : currentLanguage->off;
break;
case VFO_SCREEN_QUICK_MENU_AUDIO_MUTE:
leftSide = currentLanguage->mute;
rightSideConst = (uiDataGlobal.QuickMenu.tmpAudioMute ? currentLanguage->yes : currentLanguage->no);
break;
default:
strcpy(buf, "");
break;
}
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] != 0) ? rightSideVar : rightSideConst));
}
if (i == 0)
{
if (!isFirstRun && (menuDataGlobal.menuOptionsSetQuickkey == 0))
{
voicePromptsInit();
}
if (prompt != -1)
{
voicePromptsAppendPrompt(prompt);
}
else
{
if ((leftSide != NULL) || menuDataGlobal.newOptionSelected)
{
voicePromptsAppendLanguageString(leftSide);
}
if ((rightSideVar[0] != 0) && (rightSideConst == NULL))
{
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 VFO_SCREEN_QUICK_MENU_FILTER_FM:
case VFO_SCREEN_QUICK_MENU_FILTER_DMR:
case VFO_SCREEN_QUICK_MENU_DMR_CC_SCAN:
case VFO_SCREEN_QUICK_MENU_FILTER_DMR_TS:
case VFO_SCREEN_QUICK_MENU_TONE_SCAN:
case VFO_SCREEN_QUICK_MENU_FREQ_BIND_MODE:
case VFO_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_VFO_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 | BUTTON_EVENT)) && (menuDataGlobal.menuOptionsSetQuickkey == 0) && (menuDataGlobal.menuOptionsTimeout == 0))
{
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (menuDataGlobal.numItems != 0))
{
isDirty = true;
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, NUM_VFO_SCREEN_QUICK_MENU_ITEMS);
menuDataGlobal.newOptionSelected = true;
menuQuickVFOExitStatus |= MENU_STATUS_LIST_TYPE;
}
else if (KEYCHECK_PRESS(ev->keys, KEY_UP))
{
isDirty = true;
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, NUM_VFO_SCREEN_QUICK_MENU_ITEMS);
menuDataGlobal.newOptionSelected = true;
menuQuickVFOExitStatus |= MENU_STATUS_LIST_TYPE;
}
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
{
quickKeyApply: // branching here when to quickkey was used.
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_RD5R) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
if (nonVolatileSettings.currentVFONumber != uiDataGlobal.QuickMenu.tmpVFONumber)
{
settingsSet(nonVolatileSettings.currentVFONumber, uiDataGlobal.QuickMenu.tmpVFONumber);
currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];
}
#endif
toneScanCSS = uiDataGlobal.QuickMenu.tmpToneScanCSS;
switch(menuDataGlobal.currentItemIndex)
{
case VFO_SCREEN_QUICK_MENU_TX_SWAP_RX:
{
int tmpFreq = currentChannelData->txFreq;
currentChannelData->txFreq = currentChannelData->rxFreq;
currentChannelData->rxFreq = tmpFreq;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
}
break;
case VFO_SCREEN_QUICK_MENU_BOTH_TO_RX:
currentChannelData->txFreq = currentChannelData->rxFreq;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
break;
case VFO_SCREEN_QUICK_MENU_BOTH_TO_TX:
currentChannelData->rxFreq = currentChannelData->txFreq;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
break;
case VFO_SCREEN_QUICK_MENU_VFO_TO_NEW:
if (quickmenuNewChannelHandled == false)
{
snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s\n%s", currentLanguage->new_channel, currentLanguage->please_confirm);
uiDataGlobal.MessageBox.type = MESSAGEBOX_TYPE_INFO;
uiDataGlobal.MessageBox.decoration = MESSAGEBOX_DECORATION_FRAME;
uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_YESNO;
uiDataGlobal.MessageBox.validatorCallback = validateNewChannel;
menuSystemPushNewMenu(UI_MESSAGE_BOX);
voicePromptsInit();
voicePromptsAppendLanguageString(currentLanguage->new_channel);
voicePromptsAppendLanguageString(currentLanguage->please_confirm);
voicePromptsPlay();
}
return;
break;
case VFO_SCREEN_QUICK_MENU_TONE_SCAN:
if (trxGetMode() == RADIO_MODE_ANALOG)
{
trxSetAnalogFilterLevel(ANALOG_FILTER_CSS);
bool cssTypesDiffer = false;
CodeplugCSSTypes_t currentCSSType = codeplugGetCSSType(currentChannelData->rxTone);
// Check if the current CSS differs from the one set to scan.
if (((currentCSSType & CSS_TYPE_NONE) == 0) && ((toneScanCSS & CSS_TYPE_NONE) == 0) && (toneScanCSS != currentCSSType))
{
cssTypesDiffer = true;
}
// CTCSS or DCS no CSS
toneScanType = (((toneScanCSS & CSS_TYPE_NONE) == 0) ? toneScanCSS : currentCSSType);
prevCSSTone = currentChannelData->rxTone;
if ((currentCSSType == CSS_TYPE_NONE) || cssTypesDiffer)
{
// CSS type are different, start from index 0
scanToneIndex = 0;
if (toneScanType == CSS_TYPE_NONE)
{
toneScanType = CSS_TYPE_CTCSS;
}
currentChannelData->rxTone = cssGetToneFromIndex(scanToneIndex, toneScanType);
}
else
{
// Get the tone index in the current type array.
scanToneIndex = cssGetToneIndex(currentChannelData->rxTone, toneScanType);
// Set the tone to the next one
cssIncrement(&currentChannelData->rxTone, &scanToneIndex, 1, &toneScanType, true, (toneScanCSS != CSS_TYPE_NONE));
}
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
trxRxAndTxOff(true);
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
trxRxOn(true);
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiDataGlobal.Scan.toneActive = true;
uiDataGlobal.Scan.refreshOnEveryStep = false;
uiDataGlobal.Scan.timer.timeout = ((toneScanType == CSS_TYPE_CTCSS) ? (SCAN_TONE_INTERVAL - (scanToneIndex * 2)) : SCAN_TONE_INTERVAL);
uiDataGlobal.Scan.direction = 1;
}
break;
case VFO_SCREEN_QUICK_MENU_DUAL_SCAN:
uiDataGlobal.Scan.active = true;
uiDataGlobal.Scan.stepTimeMilliseconds = settingsGetScanStepTimeMilliseconds();
uiDataGlobal.Scan.dwellTime = 135;// for Dual Watch, use a larger step time than normally scanning, and which does not synchronise with the DMR 30ms timeslots
uiDataGlobal.Scan.timer.timeout = uiDataGlobal.Scan.dwellTime;
uiDataGlobal.Scan.refreshOnEveryStep = false;
screenOperationMode[CHANNEL_VFO_A] = screenOperationMode[CHANNEL_VFO_B] = VFO_SCREEN_OPERATION_DUAL_SCAN;
uiDataGlobal.VoicePrompts.inhibitInitial = true;
uiDataGlobal.Scan.scanType = SCAN_TYPE_DUAL_WATCH;
int currentPowerSavingLevel = rxPowerSavingGetLevel();
if (currentPowerSavingLevel > 1)
{
rxPowerSavingSetLevel(currentPowerSavingLevel - 1);
}
break;
default:
// VFO_SCREEN_QUICK_MENU_FILTER_FM
if (nonVolatileSettings.analogFilterLevel != uiDataGlobal.QuickMenu.tmpAnalogFilterLevel)
{
settingsSet(nonVolatileSettings.analogFilterLevel, uiDataGlobal.QuickMenu.tmpAnalogFilterLevel);
trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel);
}
// VFO_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);
}
}
// VFO_SCREEN_QUICK_MENU_DMR_CC_FILTER
// VFO_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);
}
}
// VFO_SCREEN_QUICK_MENU_FREQ_BIND_MODE
if (settingsIsOptionBitSet(BIT_TX_RX_FREQ_LOCK) != uiDataGlobal.QuickMenu.tmpTxRxLockMode)
{
settingsSetOptionBit(BIT_TX_RX_FREQ_LOCK, uiDataGlobal.QuickMenu.tmpTxRxLockMode);
}
// VFO_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))
{
uiVFOModeStopScanning();
menuSystemPopPreviousMenu();
return;
}
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_RD5R) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
else if (((ev->events & BUTTON_EVENT) && BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE)) && (menuDataGlobal.currentItemIndex == VFO_SCREEN_QUICK_MENU_VFO_A_B))
#elif defined(PLATFORM_RD5R)
else if (KEYCHECK_SHORTUP(ev->keys, KEY_VFO_MR) && (menuDataGlobal.currentItemIndex == VFO_SCREEN_QUICK_MENU_VFO_A_B))
#endif
{
settingsSet(nonVolatileSettings.currentVFONumber, (1 - uiDataGlobal.QuickMenu.tmpVFONumber));// Switch to other VFO
currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];
menuSystemPopPreviousMenu();
if (nonVolatileSettings.currentVFONumber == 0)
{
// Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event
ev->keys.event |= KEY_MOD_UP;
ev->keys.key = 127;
// End Trick
menuQuickVFOExitStatus |= MENU_STATUS_FORCE_FIRST;
}
return;
}
#endif
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)
{
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_RD5R) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
case VFO_SCREEN_QUICK_MENU_VFO_A_B:
if (uiDataGlobal.QuickMenu.tmpVFONumber == 0)
{
uiDataGlobal.QuickMenu.tmpVFONumber = 1;
}
break;
#endif
case VFO_SCREEN_QUICK_MENU_FILTER_FM:
if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel < NUM_ANALOG_FILTER_LEVELS - 1)
{
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel++;
}
break;
case VFO_SCREEN_QUICK_MENU_FILTER_DMR:
if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel < NUM_DMR_DESTINATION_FILTER_LEVELS - 1)
{
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel++;
}
break;
case VFO_SCREEN_QUICK_MENU_DMR_CC_SCAN:
if (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN)
{
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_CC_FILTER_PATTERN;
}
break;
case VFO_SCREEN_QUICK_MENU_FILTER_DMR_TS:
if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN))
{
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_TS_FILTER_PATTERN;
}
break;
case VFO_SCREEN_QUICK_MENU_TONE_SCAN:
if (trxGetMode() == RADIO_MODE_ANALOG)
{
if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_NONE)
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = CSS_TYPE_CTCSS;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_CTCSS)
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = CSS_TYPE_DCS;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_DCS)
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = (CSS_TYPE_DCS | CSS_TYPE_DCS_INVERTED);
}
}
break;
case VFO_SCREEN_QUICK_MENU_FREQ_BIND_MODE:
uiDataGlobal.QuickMenu.tmpTxRxLockMode = false;
break;
case VFO_SCREEN_QUICK_MENU_AUDIO_MUTE:
uiDataGlobal.QuickMenu.tmpAudioMute = false;
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)
{
#if defined(PLATFORM_GD77) || defined(PLATFORM_GD77S) || defined(PLATFORM_RD5R) || defined(PLATFORM_DM1801A) || defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
case VFO_SCREEN_QUICK_MENU_VFO_A_B:
if (uiDataGlobal.QuickMenu.tmpVFONumber == 1)
{
uiDataGlobal.QuickMenu.tmpVFONumber = 0;
}
menuQuickVFOExitStatus |= MENU_STATUS_FORCE_FIRST;
break;
#endif
case VFO_SCREEN_QUICK_MENU_FILTER_FM:
if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel > ANALOG_FILTER_NONE)
{
uiDataGlobal.QuickMenu.tmpAnalogFilterLevel--;
}
break;
case VFO_SCREEN_QUICK_MENU_FILTER_DMR:
if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel > DMR_DESTINATION_FILTER_NONE)
{
uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel--;
}
break;
case VFO_SCREEN_QUICK_MENU_DMR_CC_SCAN:
if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN))
{
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_CC_FILTER_PATTERN;
}
break;
case VFO_SCREEN_QUICK_MENU_FILTER_DMR_TS:
if (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN)
{
uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_TS_FILTER_PATTERN;
}
break;
case VFO_SCREEN_QUICK_MENU_TONE_SCAN:
if (trxGetMode() == RADIO_MODE_ANALOG)
{
if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_CTCSS)
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = CSS_TYPE_NONE;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == CSS_TYPE_DCS)
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = CSS_TYPE_CTCSS;
}
else if (uiDataGlobal.QuickMenu.tmpToneScanCSS == (CSS_TYPE_DCS | CSS_TYPE_DCS_INVERTED))
{
uiDataGlobal.QuickMenu.tmpToneScanCSS = CSS_TYPE_DCS;
}
}
break;
case VFO_SCREEN_QUICK_MENU_FREQ_BIND_MODE:
uiDataGlobal.QuickMenu.tmpTxRxLockMode = true;
break;
case VFO_SCREEN_QUICK_MENU_AUDIO_MUTE:
uiDataGlobal.QuickMenu.tmpAudioMute = true;
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, &menuQuickVFOExitStatus);
isDirty = true;
}
if (isDirty)
{
updateQuickMenuScreen(false);
}
}
bool uiVFOModeIsScanning(void)
{
return (uiDataGlobal.Scan.toneActive || uiDataGlobal.Scan.active);
}
bool uiVFOModeDualWatchIsScanning(void)
{
return ((menuSystemGetCurrentMenuNumber() == UI_VFO_MODE) && uiDataGlobal.Scan.active &&
(uiDataGlobal.Scan.state == SCAN_STATE_SCANNING) && (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN));
}
bool uiVFOModeSweepScanning(bool includePaused)
{
return ((menuSystemGetCurrentMenuNumber() == UI_VFO_MODE) &&
uiDataGlobal.Scan.active &&
(includePaused ? ((uiDataGlobal.Scan.state == SCAN_STATE_SCANNING) || (uiDataGlobal.Scan.state == SCAN_STATE_PAUSED)) : (uiDataGlobal.Scan.state == SCAN_STATE_SCANNING)) &&
(screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SWEEP));
}
bool uiVFOModeFrequencyScanningIsActiveAndEnabled(uint32_t *lowFreq, uint32_t *highFreq)
{
bool ret = ((menuSystemGetCurrentMenuNumber() == UI_VFO_MODE) && (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN));
if (ret && lowFreq && highFreq)
{
*lowFreq = nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber];
*highFreq = nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber];
}
return ret;
}
static void toneScan(void)
{
if (audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF)
{
currentChannelData->txTone = currentChannelData->rxTone;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
prevCSSTone = (CODEPLUG_CSS_TONE_NONE - 1);
uiDataGlobal.Scan.toneActive = false;
return;
}
if (uiDataGlobal.Scan.timer.timeout > 0)
{
uiDataGlobal.Scan.timer.timeout--;
}
else
{
if (uiDataGlobal.Scan.direction == 1)
{
cssIncrement(&currentChannelData->rxTone, &scanToneIndex, 1, &toneScanType, true, (toneScanCSS != CSS_TYPE_NONE));
}
else
{
cssDecrement(&currentChannelData->rxTone, &scanToneIndex, 1, &toneScanType, true, (toneScanCSS != CSS_TYPE_NONE));
}
trxRxAndTxOff(true);
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
uiDataGlobal.Scan.timer.timeout = ((toneScanType == CSS_TYPE_CTCSS) ? (SCAN_TONE_INTERVAL - (scanToneIndex * 2)) : SCAN_TONE_INTERVAL);
trxRxOn(true);
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
uiVFOModeUpdateScreen(0);
}
}
static void updateTrxID(void)
{
if (nonVolatileSettings.overrideTG != 0)
{
trxTalkGroupOrPcId = nonVolatileSettings.overrideTG;
}
else
{
//tsSetManualOverride(((Channel_t)nonVolatileSettings.currentVFONumber), TS_NO_OVERRIDE);
// Check if this channel has an Rx Group
if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup))
{
codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_VFO_A_MODE + nonVolatileSettings.currentVFONumber]], &currentContactData);
}
else
{
codeplugContactGetDataForIndex(currentChannelData->contact, &currentContactData);
}
trxTalkGroupOrPcId = codeplugContactGetPackedId(&currentContactData);
tsSetContactHasBeenOverriden(((Channel_t)nonVolatileSettings.currentVFONumber), false);
trxUpdateTsForCurrentChannelWithSpecifiedContact(&currentContactData);
}
lastHeardClearLastID();
menuPrivateCallClear();
}
static void setCurrentFreqToScanLimits(void)
{
if((currentChannelData->rxFreq < nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber]) ||
(currentChannelData->rxFreq > nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber])) //if we are not already inside the Low and High Limit freqs then move to the low limit.
{
int offset = currentChannelData->txFreq - currentChannelData->rxFreq;
currentChannelData->rxFreq = nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber];
currentChannelData->txFreq = currentChannelData->rxFreq + offset;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3);
}
}
void uiVFOSweepScanModePause(bool pause, bool forceDigitalOnPause)
{
if (pause)
{
uiDataGlobal.Scan.state = SCAN_STATE_PAUSED;
vfoSweepSavedBandwidth = trxGetBandwidthIs25kHz();
if (forceDigitalOnPause)
{
trxSetModeAndBandwidth(RADIO_MODE_DIGITAL, false);
}
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
vfoSweepUpdateSamples(0, true, 0);// Force redraw to get rid of the cursor (perhaps we should draw it in the middle);
}
else
{
trxTerminateCheckAnalogSquelch(RADIO_DEVICE_PRIMARY);
trxSetModeAndBandwidth(currentChannelData->chMode, vfoSweepSavedBandwidth);
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
LedWrite(LED_GREEN, 0);
LedWrite(LED_RED, 0);
vfoSweepUpdateSamples(0, true, 0);
headerRowIsDirty = true;
}
}
static void sweepScanInit(void)
{
trxTerminateCheckAnalogSquelch(RADIO_DEVICE_PRIMARY);
uiDataGlobal.Scan.active = true;
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
uiDataGlobal.Scan.scanType = SCAN_TYPE_NORMAL_STEP;
uiDataGlobal.VoicePrompts.inhibitInitial = true;
if (voicePromptsIsPlaying())
{
voicePromptsTerminate();
}
if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_LEVEL_2)
{
voicePromptsInit();
voicePromptsAppendPrompt(PROMPT_SWEEP_SCAN_MODE);
voicePromptsPlay();
}
uiDataGlobal.Scan.sweepStepSizeIndex = ((nonVolatileSettings.vfoSweepSettings >> 12) & 0x7);
vfoSweepRssiNoiseFloor = ((nonVolatileSettings.vfoSweepSettings >> 7) & 0x1F);
vfoSweepGain = (nonVolatileSettings.vfoSweepSettings & 0x7F);
screenOperationMode[nonVolatileSettings.currentVFONumber] = VFO_SCREEN_OPERATION_SWEEP;
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
memset(vfoSweepSamples, 0x00, VFO_SWEEP_NUM_SAMPLES * sizeof(uint8_t));
menuSystemPopAllAndDisplaySpecificRootMenu(UI_VFO_MODE, true);
vfoSweepUpdateSamples(0, true, 0);
headerRowIsDirty = true;
// trxCheck*Squelch() won't be called while sweeping, blindly turn the
// green and red LED off, to avoid being lit while scanning.
LedWrite(LED_GREEN, 0);
LedWrite(LED_RED, 0);
}
static void sweepScanStep(void)
{
if (uiDataGlobal.Scan.state != SCAN_STATE_SCANNING)
{
return;
}
if (ticksTimerHasExpired(&uiDataGlobal.Scan.timer))
{
ticksTimerStart(&uiDataGlobal.Scan.timer, VFO_SWEEP_STEP_TIME);
if (uiDataGlobal.Scan.sweepSampleIndex < VFO_SWEEP_NUM_SAMPLES)
{
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) || defined(PLATFORM_MD9600)
radioReadRSSIAndNoiseForBand(currentRadioDevice->trxCurrentBand[TRX_RX_FREQ_BAND]);
#else
radioReadRSSIAndNoise();
#endif
vfoSweepSamples[uiDataGlobal.Scan.sweepSampleIndex] = radioDevices[RADIO_DEVICE_PRIMARY].trxRxSignal;// Need to save the samples so for when the freq is changed and we need to scroll the display
vfoSweepDrawSample(uiDataGlobal.Scan.sweepSampleIndex);
uiDataGlobal.Scan.sweepSampleIndex += uiDataGlobal.Scan.sweepSampleIndexIncrement;
displayThemeApply(THEME_ITEM_FG_RX_FREQ, THEME_ITEM_BG);
displayDrawFastVLine((uiDataGlobal.Scan.sweepSampleIndex) % VFO_SWEEP_NUM_SAMPLES, VFO_SWEEP_GRAPH_START_Y, VFO_SWEEP_GRAPH_HEIGHT_Y, true);// draw solid line in the next location
displayDrawFastVLine((uiDataGlobal.Scan.sweepSampleIndex + uiDataGlobal.Scan.sweepSampleIndexIncrement) % VFO_SWEEP_NUM_SAMPLES, VFO_SWEEP_GRAPH_START_Y, VFO_SWEEP_GRAPH_HEIGHT_Y, true);// draw solid line in the next location
displayThemeResetToDefault();
if (uiNotificationIsVisible())
{
displayRender();
}
else
{
displayRenderRows(1, ((8 + VFO_SWEEP_GRAPH_HEIGHT_Y) / 8) + 1);
}
}
else
{
uiDataGlobal.Scan.sweepSampleIndex = 0;
uiDataGlobal.Scan.sweepSampleIndexIncrement = 1;// go back to normal increment at the end of the special sweep step used just after the graph is zoomed in
}
uiDataGlobal.Scan.scanSweepCurrentFreq = currentChannelData->rxFreq +
(VFO_SWEEP_SCAN_RANGE_SAMPLE_STEP_TABLE[uiDataGlobal.Scan.sweepStepSizeIndex] *
(uiDataGlobal.Scan.sweepSampleIndex -
#if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
(VFO_SWEEP_NUM_SAMPLES / 2)
#else
64
#endif
)) / VFO_SWEEP_PIXELS_PER_STEP;
trxSetFrequency(uiDataGlobal.Scan.scanSweepCurrentFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
}
}
static void scanInit(void)
{
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN)
{
return;
}
uiDataGlobal.Scan.stepTimeMilliseconds = settingsGetScanStepTimeMilliseconds();
// In DIGITAL 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);
}
uiDataGlobal.Scan.dwellTime = ((uiDataGlobal.Scan.stepTimeMilliseconds < dwellTime) ? dwellTime : uiDataGlobal.Scan.stepTimeMilliseconds);
}
else
{
uiDataGlobal.Scan.dwellTime = uiDataGlobal.Scan.stepTimeMilliseconds;
}
uiDataGlobal.Scan.scanType = SCAN_TYPE_NORMAL_STEP;
screenOperationMode[nonVolatileSettings.currentVFONumber] = VFO_SCREEN_OPERATION_SCAN;
uiDataGlobal.Scan.direction = 1;
// If scan limits have not been defined. Set them to the current Rx freq .. +1MHz
if ((nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber] == 0) || (nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] == 0))
{
int limitDown = currentChannelData->rxFreq;
int limitUp = currentChannelData->rxFreq + 100000;
// If the limitUp in not valid, set it to the next band's minFreq
if (trxGetBandFromFrequency(limitUp) == FREQUENCY_OUT_OF_BAND)
{
int band = trxGetNextOrPrevBandFromFrequency(limitUp, true);
if (band != -1)
{
limitUp = RADIO_HARDWARE_FREQUENCY_BANDS[band].minFreq;
}
}
settingsSet(nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber], SAFE_MIN(limitUp, limitDown));
settingsSet(nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber], SAFE_MAX(limitUp, limitDown));
}
// Refresh on every step if scan boundaries is equal to one frequency step.
uiDataGlobal.Scan.refreshOnEveryStep = ((nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber] - nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber]) <= VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)]);
clearNuisance();
selectedFreq = VFO_SELECTED_FREQUENCY_INPUT_RX;
uiDataGlobal.Scan.timer.timeout = 500;
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING;
nextKeyBeepMelody = (int16_t *)MELODY_ACK_BEEP;// Indicate via beep that something different had happened
menuSystemPopAllAndDisplaySpecificRootMenu(UI_VFO_MODE, true);
}
static void scanning(void)
{
static bool scanPaused = false;
static bool voicePromptsAnnounced = true;
if (!rxPowerSavingIsRxOn())
{
uiDataGlobal.Scan.dwellTime = 10000;
uiDataGlobal.Scan.timer.timeout = 0;
return;
}
//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)))
{
// Test for presence of RF Carrier.
if (trxGetMode() == RADIO_MODE_DIGITAL)
{
if(uiDataGlobal.Scan.stepTimeMilliseconds > 150) // if >150ms use DMR Slow mode
{
//DMR Slow MOde
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())
{
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ,
((nonVolatileSettings.scanModePause == SCAN_MODE_STOP) ? AUDIO_PROMPT_MODE_VOICE_LEVEL_3 : PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY));
#if defined(PLATFORM_MD9600)
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
#endif
uiDataGlobal.Scan.state = SCAN_STATE_SHORT_PAUSED;
uiDataGlobal.Scan.timer.timeout = ((TIMESLOT_DURATION * 12) + TIMESLOT_DURATION) * 4; // (1 superframe + 1 TS) * 4 = TS Sync + incoming audio
scanPaused = true;
voicePromptsAnnounced = false;
}
}
else
{
//DMR Fast Mode
if(trxCarrierDetected(RADIO_DEVICE_PRIMARY))
{
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ,
((nonVolatileSettings.scanModePause == SCAN_MODE_STOP) ? AUDIO_PROMPT_MODE_VOICE_LEVEL_3 : PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY));
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
scanPaused = true;
voicePromptsAnnounced = false;
#if defined(PLATFORM_MD9600)
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
#endif
// Force screen redraw in Analog mode, Dual Watch scanning
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
}
}
}
else
{
if(trxCarrierDetected(RADIO_DEVICE_PRIMARY))
{
//FM Mode
announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ,
((nonVolatileSettings.scanModePause == SCAN_MODE_STOP) ? AUDIO_PROMPT_MODE_VOICE_LEVEL_3 : PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY));
if (nonVolatileSettings.scanModePause == SCAN_MODE_STOP)
{
uiVFOModeStopScanning();
// Just update the header (to prevent hidden mode)
displayClearRows(0, 2, false);
uiUtilityRenderHeader(false, false, false);
displayRenderRows(0, 2);
return;
}
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
scanPaused = true;
voicePromptsAnnounced = false;
#if defined(PLATFORM_MD9600)
uiDataGlobal.Scan.clickDiscriminator = CLICK_DISCRIMINATOR;
#endif
// Force screen redraw in Analog mode, Dual Watch scanning
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
}
}
}
}
// 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)
{
if (nonVolatileSettings.scanModePause == SCAN_MODE_STOP)
{
uiVFOModeStopScanning();
// Just update the header (to prevent hidden mode)
displayClearRows(0, 2, false);
uiUtilityRenderHeader(false, false, false);
displayRenderRows(0, 2);
return;
}
else
{
uiDataGlobal.Scan.timer.timeout = nonVolatileSettings.scanDelay * 1000;
uiDataGlobal.Scan.state = SCAN_STATE_PAUSED;
}
if (voicePromptsAnnounced == false)
{
if (nonVolatileSettings.audioPromptMode > AUDIO_PROMPT_MODE_VOICE_LEVEL_2)
{
voicePromptsPlay();
}
voicePromptsAnnounced = true;
}
}
#if defined(PLATFORM_MD9600)
}
#endif
}
if(uiDataGlobal.Scan.timer.timeout > 0)
{
uiDataGlobal.Scan.timer.timeout--;
}
else
{
// We are in Dual Watch scanning mode
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_DUAL_SCAN)
{
// Select and set the next VFO
//
// Note: nonVolatileSettings.currentVFONumber is not changed using settingsSet(), to prevent crazy EEPROM
// writes. uiVFOModeStopScanning() is doing this, when the scanning process ends (for any reason).
nonVolatileSettings.currentVFONumber = (1 - nonVolatileSettings.currentVFONumber);
currentChannelData = &settingsVFOChannel[nonVolatileSettings.currentVFONumber];
currentChannelData->libreDMR_Power = 0x00;// Force channel to the Master power
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
//Need to load the Rx group if specified even if TG is currently overridden as we may need it later when the left or right button is pressed
if (currentChannelData->rxGroupList != 0)
{
if (currentChannelData->rxGroupList != lastLoadedRxGroup)
{
if (codeplugRxGroupGetDataForIndex(currentChannelData->rxGroupList, &currentRxGroupData))
{
lastLoadedRxGroup = currentChannelData->rxGroupList;
}
else
{
lastLoadedRxGroup = -1;
}
}
}
else
{
memset(&currentRxGroupData, 0xFF, sizeof(CodeplugRxGroup_t));// If the VFO doesnt have an Rx Group ( TG List) the global var needs to be cleared, otherwise it contains the data from the previous screen e.g. Channel screen
lastLoadedRxGroup = -1;
}
uiVFOModeLoadChannelData(false);
// In DIGITAL 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);
}
uiDataGlobal.Scan.dwellTime = ((uiDataGlobal.Scan.stepTimeMilliseconds < dwellTime) ? dwellTime : uiDataGlobal.Scan.stepTimeMilliseconds);
}
else
{
uiDataGlobal.Scan.dwellTime = uiDataGlobal.Scan.stepTimeMilliseconds;
}
if (scanPaused)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen redraw on scan resume
scanPaused = false;
}
}
else // Frequency scanning mode
{
uiEvent_t tmpEvent = { .buttons = 0, .keys = NO_KEYCODE, .rotary = 0, .function = 0, .events = NO_EVENT, .hasEvent = 0, .time = 0 };
int fStep = VFO_FREQ_STEP_TABLE[(currentChannelData->VFOflag5 >> 4)];
if (uiDataGlobal.Scan.direction == 1)
{
if(currentChannelData->rxFreq + fStep <= nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber])
{
handleUpKey(&tmpEvent);
}
else
{
int offset = currentChannelData->txFreq - currentChannelData->rxFreq;
currentChannelData->rxFreq = nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber];
currentChannelData->txFreq = currentChannelData->rxFreq + offset;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
HRC6000ClearColorCodeSynchronisation();
}
}
else
{
if(currentChannelData->rxFreq + fStep >= nonVolatileSettings.vfoScanLow[nonVolatileSettings.currentVFONumber])
{
handleUpKey(&tmpEvent);
}
else
{
int offset = currentChannelData->txFreq - currentChannelData->rxFreq;
currentChannelData->rxFreq = nonVolatileSettings.vfoScanHigh[nonVolatileSettings.currentVFONumber];
currentChannelData->txFreq = currentChannelData->rxFreq+offset;
trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
HRC6000ClearColorCodeSynchronisation();
}
}
if (uiDataGlobal.Scan.refreshOnEveryStep)
{
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
}
}
uiDataGlobal.Scan.timer.timeout = uiDataGlobal.Scan.dwellTime;
uiDataGlobal.Scan.state = SCAN_STATE_SCANNING; // Settling and test for carrier presence.
if (screenOperationMode[nonVolatileSettings.currentVFONumber] == VFO_SCREEN_OPERATION_SCAN)
{
//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] == currentChannelData->rxFreq)
{
uiDataGlobal.Scan.timer.timeout = SCAN_SKIP_CHANNEL_INTERVAL;
break;
}
}
}
}
}
}
static void clearNuisance(void)
{
//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;
}