/* * 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, ¤tRxGroupData)) { lastLoadedRxGroup = currentChannelData->rxGroupList; } else { lastLoadedRxGroup = -1; } } } else { memset(¤tRxGroupData, 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(¤tContactData)); 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(¤tContactData); // 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(¤tContactData); trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); } } 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]], ¤tContactData); } else { codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData); } trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); 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, ¤tZone)) { 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(¤tChannelData->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(¤tChannelData->rxTone, &scanToneIndex, 1, &toneScanType, true, (toneScanCSS != CSS_TYPE_NONE)); } else { cssDecrement(¤tChannelData->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]], ¤tContactData); } else { codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData); } trxTalkGroupOrPcId = codeplugContactGetPackedId(¤tContactData); tsSetContactHasBeenOverriden(((Channel_t)nonVolatileSettings.currentVFONumber), false); trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); } 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, ¤tRxGroupData)) { lastLoadedRxGroup = currentChannelData->rxGroupList; } else { lastLoadedRxGroup = -1; } } } else { memset(¤tRxGroupData, 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; }