/* * Copyright (C)2019 Roger Clark. VK3KYY / G4KYF * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "functions/codeplug.h" #include "functions/settings.h" #include "functions/trx.h" #include "user_interface/menuSystem.h" #include "user_interface/uiUtilities.h" #include "user_interface/uiLocalisation.h" #include "functions/voicePrompts.h" #include "functions/ticks.h" #if defined(PLATFORM_GD77S) typedef enum { GD77S_UIMODE_TG_OR_SQUELCH, GD77S_UIMODE_SCAN, GD77S_UIMODE_TS, GD77S_UIMODE_CC, GD77S_UIMODE_FILTER, GD77S_UIMODE_DTMF_CONTACTS, GD77S_UIMODE_ZONE, GD77S_UIMODE_POWER, GD77S_UIMODE_MAX } GD77S_UIMODES_t; typedef struct { bool firstRun; GD77S_UIMODES_t uiMode; bool channelOutOfBounds; uint16_t dtmfListSelected; int32_t dtmfListCount; } GD77SParameters_t; static GD77SParameters_t GD77SParameters = { .firstRun = true, .uiMode = GD77S_UIMODE_TG_OR_SQUELCH, .channelOutOfBounds = false, .dtmfListSelected = 0, .dtmfListCount = 0 }; static void buildSpeechUiModeForGD77S(GD77S_UIMODES_t uiMode); static void checkAndUpdateSelectedChannelForGD77S(uint16_t chanNum, bool forceSpeech); static void handleEventForGD77S(uiEvent_t *ev); static uint16_t getCurrentChannelInCurrentZoneForGD77S(void); #else // ! PLATFORM_GD77S static void selectPrevNextZone(bool nextZone); static void handleUpKey(uiEvent_t *ev); #endif // PLATFORM_GD77S static void handleEvent(uiEvent_t *ev); static void loadChannelData(bool useChannelDataInMemory, bool loadVoicePromptAnnouncement); static void updateQuickMenuScreen(bool isFirstRun); static void handleQuickMenuEvent(uiEvent_t *ev); static void scanning(void); static void scanStart(bool longPressBeep); static void scanSearchForNextChannel(void); static void scanApplyNextChannel(void); static void updateTrxID(void); static char currentZoneName[17]; static int directChannelNumber = 0; static struct_codeplugChannel_t channelNextChannelData = { .rxFreq = 0 }; static bool nextChannelReady = false; static int nextChannelIndex = 0; static bool quickmenuChannelFromVFOHandled = false; // Quickmenu new channel confirmation window static menuStatus_t menuChannelExitStatus = MENU_STATUS_SUCCESS; static menuStatus_t menuQuickChannelExitStatus = MENU_STATUS_SUCCESS; menuStatus_t uiChannelMode(uiEvent_t *ev, bool isFirstRun) { static uint32_t m = 0, sqm = 0; if (isFirstRun) { #if ! defined(PLATFORM_GD77S) // GD77S speech can be triggered in main(), so let it ends. voicePromptsTerminate(); #endif settingsSet(nonVolatileSettings.initialMenuNumber, (uint8_t) UI_CHANNEL_MODE);// This menu. uiDataGlobal.displayChannelSettings = false; uiDataGlobal.reverseRepeater = false; nextChannelReady = false; uiDataGlobal.displaySquelch = false; uiDataGlobal.Scan.refreshOnEveryStep = false; // 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 // This mostly happens when getting out of a menu. uiDataGlobal.displayQSOState = (isQSODataAvailableForCurrentTalker() ? QSO_DISPLAY_CALLER_DATA : QSO_DISPLAY_DEFAULT_SCREEN); lastHeardClearLastID(); uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_IDLE; currentChannelData = &channelScreenChannelData;// Need to set this as currentChannelData is used by functions called by loadChannelData() if (currentChannelData->rxFreq != 0) { loadChannelData(true, false); } else { codeplugZoneGetDataForNumber(nonVolatileSettings.currentZone, ¤tZone); codeplugUtilConvertBufToString(currentZone.name, currentZoneName, 16);// need to convert to zero terminated string loadChannelData(false, false); } #if defined(PLATFORM_GD77S) trxSetPowerFromLevel(nonVolatileSettings.txPowerLevel);// ensure the power level is available for the Power announcement //Reset to first UiMode if the radio is in Analog mode and the current UiMode only applies to DMR if ((trxGetMode() == RADIO_MODE_ANALOG) && ((GD77SParameters.uiMode == GD77S_UIMODE_TS ) || (GD77SParameters.uiMode == GD77S_UIMODE_CC ) || (GD77SParameters.uiMode == GD77S_UIMODE_FILTER ))) { GD77SParameters.uiMode = GD77S_UIMODE_TG_OR_SQUELCH; } // Ensure the correct channel is loaded, on the very first run if (GD77SParameters.firstRun) { if (voicePromptsIsPlaying() == false) { GD77SParameters.firstRun = false; checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), true); } GD77SParameters.dtmfListCount = codeplugDTMFContactsGetCount(); } #endif uiChannelModeUpdateScreen(0); if (uiDataGlobal.Scan.active == false) { uiDataGlobal.Scan.state = SCAN_SCANNING; } // Need to do this last, as other things in the screen init, need to know whether the main screen has just changed if (uiDataGlobal.VoicePrompts.inhibitInitial) { uiDataGlobal.VoicePrompts.inhibitInitial = false; } menuChannelExitStatus = MENU_STATUS_SUCCESS; // Due to Orange Quick Menu } else { menuChannelExitStatus = MENU_STATUS_SUCCESS; #if defined(PLATFORM_GD77S) uiChannelModeHeartBeatActivityForGD77S(ev); #endif if (ev->events == NO_EVENT) { #if defined(PLATFORM_GD77S) // Just ensure rotary's selected channel is matching the already loaded one // as rotary selector could be turned while the GD is OFF, or in hotspot mode. if ((uiDataGlobal.Scan.active == false) && ((rotarySwitchGetPosition() != getCurrentChannelInCurrentZoneForGD77S()) || (GD77SParameters.firstRun == true))) { if (voicePromptsIsPlaying() == false) { checkAndUpdateSelectedChannelForGD77S(rotarySwitchGetPosition(), GD77SParameters.firstRun); // Opening channel number announce has not took place yet, probably because it was telling // parameter like new hotspot mode selection. if (GD77SParameters.firstRun) { GD77SParameters.firstRun = false; } } } #endif // is there an incoming DMR signal if (uiDataGlobal.displayQSOState != QSO_DISPLAY_IDLE) { uiChannelModeUpdateScreen(0); } else { // Clear squelch region if (uiDataGlobal.displaySquelch && ((ev->time - sqm) > 1000)) { uiDataGlobal.displaySquelch = false; uiUtilityDisplayInformation(NULL, DISPLAY_INFO_SQUELCH_CLEAR_AREA, -1); ucRenderRows(2, 4); } if ((ev->time - m) > RSSI_UPDATE_COUNTER_RELOAD) { m = ev->time; if (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_PAUSED)) { #if defined(PLATFORM_RD5R) ucClearRows(0, 1, false); #else ucClearRows(0, 2, false); #endif uiUtilityRenderHeader(false); } else { 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 ucRenderRows(((uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_PAUSED)) ? 0 : 1), 2); } } if (uiDataGlobal.Scan.active == true) { scanning(); } } else { if (ev->hasEvent) { if ((trxGetMode() == RADIO_MODE_ANALOG) && (ev->events & KEY_EVENT) && ((ev->keys.key == KEY_LEFT) || (ev->keys.key == KEY_RIGHT))) { sqm = ev->time; } handleEvent(ev); } } #if defined(PLATFORM_GD77S) dtmfSequenceTick(false); #endif } return menuChannelExitStatus; } #if 0 // rename: we have an union declared (fw_sound.c) with the same name. uint16_t byteSwap16(uint16_t in) { return ((in & 0xff << 8) | (in >> 8)); } #endif static bool canCurrentZoneBeScanned(int *availableChannels) { int enabledChannels = 0; int chanIdx = (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) ? nonVolatileSettings.currentChannelIndexInAllZone : currentZone.channels[nonVolatileSettings.currentChannelIndexInZone]); if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1) { int chansInZone = currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone; if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { do { do { chanIdx = ((((chanIdx - 1) + 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1); } while (!codeplugAllChannelsIndexIsInUse(chanIdx)); chansInZone--; // Get flag4 only codeplugChannelGetDataWithOffsetAndLengthForIndex(chanIdx, &channelNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1); if (CODEPLUG_CHANNEL_IS_FLAG_SET(&channelNextChannelData, CODEPLUG_CHANNEL_FLAG_ALL_SKIP) == false) { enabledChannels++; } } while (chansInZone > 0); } else { do { chanIdx = ((chanIdx + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone); chansInZone--; // Get flag4 only codeplugChannelGetDataWithOffsetAndLengthForIndex(currentZone.channels[chanIdx], &channelNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1); if (CODEPLUG_CHANNEL_IS_FLAG_SET(&channelNextChannelData, CODEPLUG_CHANNEL_FLAG_ZONE_SKIP) == false) { enabledChannels++; } } while (chansInZone > 0); } } *availableChannels = enabledChannels; return (enabledChannels > 1); } static void scanSearchForNextChannel(void) { int channel = 0; // All Channels virtual zone if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { do { do { // rollover (up/down) CODEPLUG_CHANNELS_MIN .. currentZone.NOT_IN_CODEPLUGDATA_highestIndex nextChannelIndex = ((uiDataGlobal.Scan.direction == 1) ? ((((nextChannelIndex - 1) + 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1) : ((((nextChannelIndex - 1) + currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1)); } while (!codeplugAllChannelsIndexIsInUse(nextChannelIndex)); // Check if the channel is skipped. // Get flag4 only codeplugChannelGetDataWithOffsetAndLengthForIndex(nextChannelIndex, &channelNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1); } while (CODEPLUG_CHANNEL_IS_FLAG_SET(&channelNextChannelData, CODEPLUG_CHANNEL_FLAG_ALL_SKIP)); channel = nextChannelIndex; codeplugChannelGetDataForIndex(nextChannelIndex, &channelNextChannelData); } else { do { // rollover (up/down) 0 .. (currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1) nextChannelIndex = ((uiDataGlobal.Scan.direction == 1) ? ((nextChannelIndex + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone) : ((nextChannelIndex + currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone)); // Check if the channel is skipped. // Get flag4 only codeplugChannelGetDataWithOffsetAndLengthForIndex(currentZone.channels[nextChannelIndex], &channelNextChannelData, CODEPLUG_CHANNEL_FLAG4_OFFSET, 1); } while (CODEPLUG_CHANNEL_IS_FLAG_SET(&channelNextChannelData, CODEPLUG_CHANNEL_FLAG_ZONE_SKIP)); channel = currentZone.channels[nextChannelIndex]; codeplugChannelGetDataForIndex(currentZone.channels[nextChannelIndex], &channelNextChannelData); } //check all nuisance delete entries and skip channel if there is a match for (int i = 0; i < MAX_ZONE_SCAN_NUISANCE_CHANNELS; i++) { if (uiDataGlobal.Scan.nuisanceDelete[i] == -1) { break; } else { if(uiDataGlobal.Scan.nuisanceDelete[i] == channel) { return; } } } nextChannelReady = true; } static void scanApplyNextChannel(void) { if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, (int16_t) nextChannelIndex); } else { settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) nextChannelIndex); } lastHeardClearLastID(); memcpy(&channelScreenChannelData, &channelNextChannelData, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE); loadChannelData(true, false); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); if ((trxGetMode() == RADIO_MODE_DIGITAL) && (trxDMRModeRx != DMR_MODE_SFR) && (uiDataGlobal.Scan.stepTimeMilliseconds < SCAN_DMR_SIMPLEX_MIN_INTERVAL)) { uiDataGlobal.Scan.timerReload = SCAN_DMR_SIMPLEX_MIN_INTERVAL; } else { uiDataGlobal.Scan.timerReload = uiDataGlobal.Scan.stepTimeMilliseconds; } uiDataGlobal.Scan.timer = uiDataGlobal.Scan.timerReload; uiDataGlobal.Scan.state = SCAN_SCANNING; nextChannelReady = false; } static void loadChannelData(bool useChannelDataInMemory, bool loadVoicePromptAnnouncement) { bool rxGroupValid = true; if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone uiDataGlobal.currentSelectedChannelNumber = nonVolatileSettings.currentChannelIndexInAllZone; } else { uiDataGlobal.currentSelectedChannelNumber = currentZone.channels[nonVolatileSettings.currentChannelIndexInZone]; } if (!useChannelDataInMemory) { if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone codeplugChannelGetDataForIndex(nonVolatileSettings.currentChannelIndexInAllZone, &channelScreenChannelData); } else { codeplugChannelGetDataForIndex(currentZone.channels[nonVolatileSettings.currentChannelIndexInZone], &channelScreenChannelData); } } clearActiveDMRID(); trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, DMR_MODE_AUTO); trxSetModeAndBandwidth(currentChannelData->chMode, ((currentChannelData->flag4 & 0x02) == 0x02)); if (currentChannelData->chMode == RADIO_MODE_ANALOG) { trxSetRxCSS(currentChannelData->rxTone); } else { trxSetDMRColourCode(currentChannelData->txColor); if (currentChannelData->rxGroupList != lastLoadedRxGroup) { rxGroupValid = codeplugRxGroupGetDataForIndex(currentChannelData->rxGroupList, ¤tRxGroupData); if (rxGroupValid) { lastLoadedRxGroup = currentChannelData->rxGroupList; } else { lastLoadedRxGroup = -1;// RxGroup Contacts are not valid } } // Current contact index is out of group list bounds, select first contact if (rxGroupValid && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1))) { settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0); menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } // Check if this channel has an Rx Group if (rxGroupValid && nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup) { codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData); } else { codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData); } trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); if (nonVolatileSettings.overrideTG == 0) { trxTalkGroupOrPcId = currentContactData.tgNumber; if (currentContactData.callType == CONTACT_CALLTYPE_PC) { trxTalkGroupOrPcId |= (PC_CALL_FLAG << 24); } } else { trxTalkGroupOrPcId = nonVolatileSettings.overrideTG; } } #if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own int nextMenu = menuSystemGetPreviouslyPushedMenuNumber(); // used to determine if this screen has just been loaded after Tx ended (in loadChannelData())) if ((!uiDataGlobal.VoicePrompts.inhibitInitial || loadVoicePromptAnnouncement) && ((uiDataGlobal.Scan.active == false) || (uiDataGlobal.Scan.active && ((uiDataGlobal.Scan.state = SCAN_SHORT_PAUSED) || (uiDataGlobal.Scan.state = SCAN_PAUSED))))) { announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE, ((nextMenu == UI_TX_SCREEN) || (nextMenu == UI_PRIVATE_CALL) || uiDataGlobal.Scan.active) ? PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY : PROMPT_THRESHOLD_3); } #else // Force GD77S to always use the Master power level currentChannelData->libreDMR_Power = 0x00; #endif } void uiChannelModeUpdateScreen(int txTimeSecs) { int channelNumber; static const int nameBufferLen = 23; char nameBuf[nameBufferLen]; static const int bufferLen = 17; char buffer[bufferLen]; // Only render the header, then wait for the next run // Otherwise the screen could remain blank if TG and PC are == 0 // since uiDataGlobal.displayQSOState won't be set to QSO_DISPLAY_IDLE if ((trxGetMode() == RADIO_MODE_DIGITAL) && (HRC6000GetReceivedTgOrPcId() == 0) && ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) || (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA_UPDATE))) { uiUtilityRedrawHeaderOnly(false); return; } // We're currently displaying details, and it shouldn't be overridden by QSO data if (uiDataGlobal.displayChannelSettings && ((uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA) || (uiDataGlobal.displayQSOState == QSO_DISPLAY_CALLER_DATA_UPDATE))) { // We will not restore the previous QSO Data as a new caller just arose. uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_DEFAULT_SCREEN; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; } ucClearBuf(); uiUtilityRenderHeader(false); switch(uiDataGlobal.displayQSOState) { case QSO_DISPLAY_DEFAULT_SCREEN: uiDataGlobal.displayQSOStatePrev = QSO_DISPLAY_DEFAULT_SCREEN; uiDataGlobal.isDisplayingQSOData = false; uiDataGlobal.receivedPcId = 0x00; if (trxTransmissionEnabled) { // Squelch is displayed, PTT was pressed // Clear its region if (uiDataGlobal.displaySquelch) { uiDataGlobal.displaySquelch = false; uiUtilityDisplayInformation(NULL, DISPLAY_INFO_SQUELCH_CLEAR_AREA, -1); } snprintf(buffer, bufferLen, " %d ", txTimeSecs); uiUtilityDisplayInformation(buffer, DISPLAY_INFO_TX_TIMER, -1); } else { // Display some channel settings if (uiDataGlobal.displayChannelSettings) { uiUtilityDisplayInformation(NULL, DISPLAY_INFO_TONE_AND_SQUELCH, -1); uiUtilityDisplayFrequency(DISPLAY_Y_POS_RX_FREQ, false, false, (uiDataGlobal.reverseRepeater ? currentChannelData->txFreq : currentChannelData->rxFreq), false, false, 0); uiUtilityDisplayFrequency(DISPLAY_Y_POS_TX_FREQ, true, false, (uiDataGlobal.reverseRepeater ? currentChannelData->rxFreq : currentChannelData->txFreq), false, false, 0); } else { if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone channelNumber = nonVolatileSettings.currentChannelIndexInAllZone; if (directChannelNumber > 0) { snprintf(nameBuf, nameBufferLen, "%s %d", currentLanguage->gotoChannel, directChannelNumber); } else { snprintf(nameBuf, nameBufferLen, "%s Ch:%d",currentLanguage->all_channels, channelNumber); } } else { channelNumber = nonVolatileSettings.currentChannelIndexInZone + 1; if (directChannelNumber > 0) { snprintf(nameBuf, nameBufferLen, "%s %d", currentLanguage->gotoChannel, directChannelNumber); } else { snprintf(nameBuf, nameBufferLen, "%s Ch:%d", currentZoneName, channelNumber); } } uiUtilityDisplayInformation(nameBuf, DISPLAY_INFO_ZONE, -1); /* * Roger Clark. Commented out * functionality to display "Scanning" instead of the channel name (and TG on DMR) whilst scanning * if (uiDataGlobal.Scan.active && (uiDataGlobal.Scan.state == SCAN_SCANNING)) { uiUtilityDisplayInformation(currentLanguage->scanning, DISPLAY_INFO_CONTACT, -1); ucRender(); break; }*/ } } if (!uiDataGlobal.displayChannelSettings) { codeplugUtilConvertBufToString(currentChannelData->name, nameBuf, 16); uiUtilityDisplayInformation(nameBuf, DISPLAY_INFO_CHANNEL, (trxTransmissionEnabled ? DISPLAY_Y_POS_CHANNEL_SECOND_LINE : -1)); } if (trxGetMode() == RADIO_MODE_DIGITAL) { if (uiDataGlobal.displayChannelSettings) { uint32_t PCorTG = ((nonVolatileSettings.overrideTG != 0) ? nonVolatileSettings.overrideTG : currentContactData.tgNumber); snprintf(nameBuf, nameBufferLen, "%s %d", (((PCorTG >> 24) == PC_CALL_FLAG) ? currentLanguage->pc : currentLanguage->tg), (PCorTG & 0xFFFFFF)); } else { if (nonVolatileSettings.overrideTG != 0) { uiUtilityBuildTgOrPCDisplayName(nameBuf, bufferLen); uiUtilityDisplayInformation(NULL, DISPLAY_INFO_CONTACT_OVERRIDE_FRAME, (trxTransmissionEnabled ? DISPLAY_Y_POS_CONTACT_TX_FRAME : -1)); } else { codeplugUtilConvertBufToString(currentContactData.name, nameBuf, 16); } } uiUtilityDisplayInformation(nameBuf, DISPLAY_INFO_CONTACT, (trxTransmissionEnabled ? DISPLAY_Y_POS_CONTACT_TX : -1)); } // Squelch will be cleared later, 1s after last change else if(uiDataGlobal.displaySquelch && !trxTransmissionEnabled && !uiDataGlobal.displayChannelSettings) { strncpy(buffer, currentLanguage->squelch, 9); buffer[8] = 0; // Avoid overlap with bargraph uiUtilityDisplayInformation(buffer, DISPLAY_INFO_SQUELCH, -1); } // SK1 is pressed, we don't want to clear the first info row after 1s if (uiDataGlobal.displayChannelSettings && uiDataGlobal.displaySquelch) { uiDataGlobal.displaySquelch = false; } ucRender(); 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(); ucRender(); break; case QSO_DISPLAY_IDLE: break; } uiDataGlobal.displayQSOState = QSO_DISPLAY_IDLE; } static void handleEvent(uiEvent_t *ev) { #if defined(PLATFORM_GD77S) handleEventForGD77S(ev); return; #else if (uiDataGlobal.Scan.active && (ev->events & KEY_EVENT)) { // Key pressed during scanning if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) { // if we are scanning and down key is pressed then enter current channel into nuisance delete array. if((uiDataGlobal.Scan.state == SCAN_PAUSED) && (ev->keys.key == KEY_RIGHT)) { // There is two channels available in the Zone, just stop scanning if (uiDataGlobal.Scan.nuisanceDeleteIndex == (uiDataGlobal.Scan.availableChannelsCount - 2)) { uiDataGlobal.Scan.lastIteration = true; } uiDataGlobal.Scan.nuisanceDelete[uiDataGlobal.Scan.nuisanceDeleteIndex] = uiDataGlobal.currentSelectedChannelNumber; uiDataGlobal.Scan.nuisanceDeleteIndex = (uiDataGlobal.Scan.nuisanceDeleteIndex + 1) % MAX_ZONE_SCAN_NUISANCE_CHANNELS; uiDataGlobal.Scan.timer = SCAN_SKIP_CHANNEL_INTERVAL; //force scan to continue; uiDataGlobal.Scan.state = SCAN_SCANNING; keyboardReset(); return; } // Left key reverses the scan direction if (ev->keys.key == KEY_LEFT) { uiDataGlobal.Scan.direction *= -1; keyboardReset(); return; } } // stop the scan on any button except UP without Shift (allows scan to be manually continued) // or SK2 on its own (allows Backlight to be triggered) if (((ev->keys.key == KEY_UP) && BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0) == false) { uiChannelModeStopScanning(); keyboardReset(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); loadChannelData(false, true); return; } } if (ev->events & FUNCTION_EVENT) { if (ev->function == FUNC_START_SCANNING) { directChannelNumber = 0; if (uiDataGlobal.Scan.active == false) { scanStart(false); } return; } else if (ev->function == FUNC_REDRAW) { uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); return; } } if ((uiDataGlobal.reverseRepeater == false) && handleMonitorMode(ev)) { return; } if (ev->events & BUTTON_EVENT) { 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); tsSetManualOverride(CHANNEL_CHANNEL, (dmrMonitorCapturedTS + 1)); } if (trxTalkGroupOrPcId != tg) { trxTalkGroupOrPcId = tg; settingsSet(nonVolatileSettings.overrideTG, trxTalkGroupOrPcId); } currentChannelData->txColor = trxGetDMRColourCode();// Set the CC to the current CC, which may have been determined by the CC finding algorithm in C6000.c announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC,PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); soundSetMelody(MELODY_ACK_BEEP); return; } if ((uiDataGlobal.reverseRepeater == false) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))) { trxSetFrequency(currentChannelData->txFreq, currentChannelData->rxFreq, DMR_MODE_DMO);// Swap Tx and Rx freqs but force DMR Active uiDataGlobal.reverseRepeater = true; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); return; } else if ((uiDataGlobal.reverseRepeater == true) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)) { trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, DMR_MODE_AUTO); uiDataGlobal.reverseRepeater = false; // We are still displaying channel details (SK1 has been released), force to update the screen if (uiDataGlobal.displayChannelSettings) { uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } return; } // Display channel settings (RX/TX/etc) while SK1 is pressed else if ((uiDataGlobal.displayChannelSettings == false) && BUTTONCHECK_DOWN(ev, BUTTON_SK1)) { if (uiDataGlobal.Scan.active == false) { int prevQSODisp = uiDataGlobal.displayQSOStatePrev; uiDataGlobal.displayChannelSettings = true; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); uiDataGlobal.displayQSOStatePrev = prevQSODisp; } return; } else if ((uiDataGlobal.displayChannelSettings == true) && (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.reverseRepeater) { trxSetFrequency(currentChannelData->rxFreq, currentChannelData->txFreq, DMR_MODE_AUTO); uiDataGlobal.reverseRepeater = false; } uiChannelModeUpdateScreen(0); return; } #if !defined(PLATFORM_RD5R) if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { announceItem(PROMPT_SEQUENCE_BATTERY, AUDIO_PROMPT_MODE_VOICE_LEVEL_1); } else { // Quick Menu menuSystemPushNewMenu(UI_CHANNEL_QUICK_MENU); // Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event ev->keys.event |= KEY_MOD_UP; ev->keys.key = 127; menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); // End Trick } return; } #endif } if (ev->events & KEY_EVENT) { if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { if (directChannelNumber > 0) { if(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone if (codeplugAllChannelsIndexIsInUse(directChannelNumber)) { settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, (int16_t) directChannelNumber); loadChannelData(false, true); } else { soundSetMelody(MELODY_ERROR_BEEP); } } else { if ((directChannelNumber - 1) < currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone) { settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) (directChannelNumber - 1)); loadChannelData(false, true); } else { soundSetMelody(MELODY_ERROR_BEEP); } } directChannelNumber = 0; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } else if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { menuSystemPushNewMenu(MENU_CHANNEL_DETAILS); } else { menuSystemPushNewMenu(MENU_MAIN_MENU); } return; } else if (KEYCHECK_SHORTUP(ev->keys, KEY_HASH)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) != 0) { menuSystemPushNewMenu(MENU_CONTACT_QUICKLIST); } else { if (trxGetMode() == RADIO_MODE_DIGITAL) { menuSystemPushNewMenu(MENU_NUMERICAL_ENTRY); } } return; } else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2) && (uiDataGlobal.tgBeforePcMode != 0)) { settingsSet(nonVolatileSettings.overrideTG, uiDataGlobal.tgBeforePcMode); menuPrivateCallClear(); updateTrxID(); uiDataGlobal.displayQSOState= QSO_DISPLAY_DEFAULT_SCREEN;// Force redraw uiChannelModeUpdateScreen(0); return;// The event has been handled } if(directChannelNumber > 0) { announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_NEVER_PLAY_IMMEDIATELY); directChannelNumber = 0; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } else { #if defined(PLATFORM_GD77) menuSystemSetCurrentMenu(UI_VFO_MODE); #endif return; } } #if defined(PLATFORM_DM1801) || defined(PLATFORM_RD5R) else if (KEYCHECK_SHORTUP(ev->keys, KEY_VFO_MR)) { directChannelNumber = 0; menuSystemSetCurrentMenu(UI_VFO_MODE); return; } #endif #if defined(PLATFORM_RD5R) else if (KEYCHECK_LONGDOWN(ev->keys, KEY_VFO_MR) && (BUTTONCHECK_DOWN(ev, BUTTON_SK1) == 0)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { announceItem(PROMPT_SEQUENCE_BATTERY, AUDIO_PROMPT_MODE_VOICE_LEVEL_1); } else { menuSystemPushNewMenu(UI_CHANNEL_QUICK_MENU); // Trick to beep (AudioAssist), since ORANGE button doesn't produce any beep event ev->keys.event |= KEY_MOD_UP; ev->keys.key = 127; menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); // End Trick } return; } #endif else if (KEYCHECK_LONGDOWN(ev->keys, KEY_RIGHT)) { // Long press allows the 5W+ power setting to be selected immediately if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { if (increasePowerLevel(true)) { uiUtilityRedrawHeaderOnly(false); } } } else if (KEYCHECK_PRESS(ev->keys, KEY_RIGHT)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { if (increasePowerLevel(false)) { uiUtilityRedrawHeaderOnly(false); } } else { if (trxGetMode() == RADIO_MODE_DIGITAL) { if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1) { if (nonVolatileSettings.overrideTG == 0) { settingsIncrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1); if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)) { settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0); menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } } settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden menuPrivateCallClear(); updateTrxID(); // We're in digital mode, RXing, and current talker is already at the top of last heard list, // hence immediately display complete contact/TG info on screen uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;//(isQSODataAvailableForCurrentTalker() ? QSO_DISPLAY_CALLER_DATA : QSO_DISPLAY_DEFAULT_SCREEN); if (isQSODataAvailableForCurrentTalker()) { addTimerCallback(uiUtilityRenderQSODataAndUpdateScreen, 2000, true); } uiChannelModeUpdateScreen(0); announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC,PROMPT_THRESHOLD_3); } else { if(currentChannelData->sql == 0) //If we were using default squelch level { currentChannelData->sql = nonVolatileSettings.squelchDefaults[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; uiDataGlobal.displaySquelch = true; uiChannelModeUpdateScreen(0); } } } else if (KEYCHECK_PRESS(ev->keys, KEY_LEFT)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { if (decreasePowerLevel()) { uiUtilityRedrawHeaderOnly(false); } if (trxGetPowerLevel() == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } else { if (trxGetMode() == RADIO_MODE_DIGITAL) { if (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup > 1) { // To Do change TG in on same channel freq if (nonVolatileSettings.overrideTG == 0) { settingsDecrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1); if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < 0) { settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], (int16_t) (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)); } if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } } settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden menuPrivateCallClear(); updateTrxID(); // We're in digital mode, RXing, and current talker is already at the top of last heard list, // hence immediately display complete contact/TG info on screen uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;//(isQSODataAvailableForCurrentTalker() ? QSO_DISPLAY_CALLER_DATA : QSO_DISPLAY_DEFAULT_SCREEN); if (isQSODataAvailableForCurrentTalker()) { addTimerCallback(uiUtilityRenderQSODataAndUpdateScreen, 2000, true); } uiChannelModeUpdateScreen(0); announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC,PROMPT_THRESHOLD_3); } else { if(currentChannelData->sql == 0) //If we were using default squelch level { currentChannelData->sql = nonVolatileSettings.squelchDefaults[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; uiDataGlobal.displaySquelch = true; uiChannelModeUpdateScreen(0); } } } else if (KEYCHECK_SHORTUP(ev->keys, KEY_STAR)) { if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) // Toggle Channel Mode { if (trxGetMode() == RADIO_MODE_ANALOG) { currentChannelData->chMode = RADIO_MODE_DIGITAL; uiDataGlobal.VoicePrompts.inhibitInitial = true;// Stop VP playing in loadChannelData loadChannelData(true, false); uiDataGlobal.VoicePrompts.inhibitInitial = false; menuChannelExitStatus |= MENU_STATUS_FORCE_FIRST; } else { currentChannelData->chMode = RADIO_MODE_ANALOG; trxSetModeAndBandwidth(currentChannelData->chMode, ((currentChannelData->flag4 & 0x02) == 0x02)); trxSetRxCSS(currentChannelData->rxTone); } announceItem(PROMPT_SEQUENCE_MODE, PROMPT_THRESHOLD_3); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } else { if (trxGetMode() == RADIO_MODE_DIGITAL) { // Toggle timeslot trxSetDMRTimeSlot(1 - trxGetDMRTimeSlot()); tsSetManualOverride(CHANNEL_CHANNEL, (trxGetDMRTimeSlot() + 1)); if ((nonVolatileSettings.overrideTG == 0) && (currentContactData.reserve1 & 0x01) == 0x00) { tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, true); } // init_digital(); disableAudioAmp(AUDIO_AMP_MODE_RF); clearActiveDMRID(); lastHeardClearLastID(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); if (trxGetDMRTimeSlot() == 0) { menuChannelExitStatus |= MENU_STATUS_FORCE_FIRST; } announceItem(PROMPT_SEQUENCE_TS,PROMPT_THRESHOLD_3); } else { soundSetMelody(MELODY_ERROR_BEEP); } } } else if (KEYCHECK_LONGDOWN(ev->keys, KEY_STAR) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)) { if (trxGetMode() == RADIO_MODE_DIGITAL) { tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE); tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, false); if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup)) { codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData); } else { codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData); } trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); clearActiveDMRID(); lastHeardClearLastID(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } } else if (KEYCHECK_SHORTUP(ev->keys, KEY_DOWN) || KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_DOWN)) { uiDataGlobal.displaySquelch = false; if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { selectPrevNextZone(false); menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, false); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen redraw if (nonVolatileSettings.currentZone == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } return; } else { int16_t prevChan; lastHeardClearLastID(); if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1) { prevChan = nonVolatileSettings.currentChannelIndexInAllZone; // All Channels virtual zone do { prevChan = ((((prevChan - 1) + currentZone.NOT_IN_CODEPLUGDATA_highestIndex - 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1); } while (!codeplugAllChannelsIndexIsInUse(prevChan)); settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, prevChan); if (nonVolatileSettings.currentChannelIndexInAllZone == 1) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } } else { if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1) { prevChan = ((nonVolatileSettings.currentChannelIndexInZone + currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone - 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone); settingsSet(nonVolatileSettings.currentChannelIndexInZone, prevChan); if (nonVolatileSettings.currentChannelIndexInZone == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } } } loadChannelData(false, true); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } else if (KEYCHECK_SHORTUP(ev->keys, KEY_UP) || KEYCHECK_LONGDOWN_REPEAT(ev->keys, KEY_UP)) { handleUpKey(ev); return; } else if (KEYCHECK_LONGDOWN(ev->keys, KEY_UP) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2) == 0)) { if (uiDataGlobal.Scan.active == false) { scanStart(true); } } else { int keyval = menuGetKeypadKeyValue(ev, true); if ((keyval < 10) && (!BUTTONCHECK_DOWN(ev, BUTTON_SK2))) { directChannelNumber = (directChannelNumber * 10) + keyval; if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { // All Channels virtual zone if(directChannelNumber > CODEPLUG_CONTACTS_MAX) { directChannelNumber = 0; soundSetMelody(MELODY_ERROR_BEEP); } } else { if(directChannelNumber > currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone) { directChannelNumber = 0; soundSetMelody(MELODY_ERROR_BEEP); } } if (directChannelNumber > 0) { voicePromptsInit(); if (directChannelNumber < 10) { voicePromptsAppendLanguageString(¤tLanguage->gotoChannel); } voicePromptsAppendPrompt(PROMPT_0 + keyval); voicePromptsPlay(); } else { announceItem(PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ, PROMPT_THRESHOLD_3); } uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } } } #endif // ! PLATFORM_GD77S } #if ! defined(PLATFORM_GD77S) static void selectPrevNextZone(bool nextZone) { int numZones = codeplugZonesGetCount(); if (nextZone) { settingsIncrement(nonVolatileSettings.currentZone, 1); if (nonVolatileSettings.currentZone >= numZones) { settingsSet(nonVolatileSettings.currentZone, 0); } } else { if (nonVolatileSettings.currentZone == 0) { settingsSet(nonVolatileSettings.currentZone, (int16_t) (numZones - 1)); } else { settingsDecrement(nonVolatileSettings.currentZone, 1); } } /* * VK3KYY * I don't understand why these should be reset when changing zones. * Only the change to TG override will be clear to the operator. * * Changing zones should only change the zone. * I can't see a compelling reason to override what the operator has setup just because they change zones * settingsSet(nonVolatileSettings.overrideTG, 0); // remove any TG override tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE);// remove any TS override */ settingsSet(nonVolatileSettings.currentChannelIndexInZone, 0);// Since we are switching zones the channel index should be reset currentChannelData->rxFreq = 0x00; // Flag to the Channel screen that the channel data is now invalid and needs to be reloaded } static void handleUpKey(uiEvent_t *ev) { uiDataGlobal.displaySquelch = false; if (BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { selectPrevNextZone(true); menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, false); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen redraw if (nonVolatileSettings.currentZone == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } return; } else { int16_t nextChan; lastHeardClearLastID(); if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1) { nextChan = nonVolatileSettings.currentChannelIndexInAllZone; // All Channels virtual zone do { nextChan = ((((nextChan - 1) + 1) % currentZone.NOT_IN_CODEPLUGDATA_highestIndex) + 1); if (nextChan == CODEPLUG_CONTACTS_MIN) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } while (!codeplugAllChannelsIndexIsInUse(nextChan)); settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, nextChan); } } else { if (currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone > 1) { nextChan = ((nonVolatileSettings.currentChannelIndexInZone + 1) % currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone); settingsSet(nonVolatileSettings.currentChannelIndexInZone, nextChan); if (nonVolatileSettings.currentChannelIndexInZone == 0) { menuChannelExitStatus |= (MENU_STATUS_LIST_TYPE | MENU_STATUS_FORCE_FIRST); } } } uiDataGlobal.Scan.timer = 500; uiDataGlobal.Scan.state = SCAN_SCANNING; } loadChannelData(false, true); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } #endif // ! PLATFORM_GD77S // Quick Menu functions enum CHANNEL_SCREEN_QUICK_MENU_ITEMS { CH_SCREEN_QUICK_MENU_COPY2VFO = 0, CH_SCREEN_QUICK_MENU_COPY_FROM_VFO, CH_SCREEN_QUICK_MENU_FILTER_FM, CH_SCREEN_QUICK_MENU_FILTER_DMR, CH_SCREEN_QUICK_MENU_FILTER_DMR_CC, CH_SCREEN_QUICK_MENU_FILTER_DMR_TS, NUM_CH_SCREEN_QUICK_MENU_ITEMS };// The last item in the list is used so that we automatically get a total number of items in the list menuStatus_t uiChannelModeQuickMenu(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { if (quickmenuChannelFromVFOHandled) { quickmenuChannelFromVFOHandled = false; menuSystemPopAllAndDisplayRootMenu(); return MENU_STATUS_SUCCESS; } uiChannelModeStopScanning(); uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel = nonVolatileSettings.dmrDestinationFilter; uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel = nonVolatileSettings.dmrCcTsFilter; uiDataGlobal.QuickMenu.tmpAnalogFilterLevel = nonVolatileSettings.analogFilterLevel; menuDataGlobal.endIndex = NUM_CH_SCREEN_QUICK_MENU_ITEMS; voicePromptsInit(); voicePromptsAppendPrompt(PROMPT_SILENCE); voicePromptsAppendPrompt(PROMPT_SILENCE); voicePromptsAppendLanguageString(¤tLanguage->quick_menu); voicePromptsAppendPrompt(PROMPT_SILENCE); voicePromptsAppendPrompt(PROMPT_SILENCE); updateQuickMenuScreen(true); return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } else { menuQuickChannelExitStatus = MENU_STATUS_SUCCESS; if (ev->hasEvent) { handleQuickMenuEvent(ev); } } return menuQuickChannelExitStatus; } static bool validateOverwriteChannel(void) { quickmenuChannelFromVFOHandled = true; if (uiDataGlobal.MessageBox.keyPressed == KEY_GREEN) { struct_codeplugContact_t vfoContact; int8_t vfoTS = -1; uiVFOLoadContact(&vfoContact); memcpy(¤tChannelData->rxFreq, &settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxFreq, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE - 16);// Don't copy the name of the vfo, which are in the first 16 bytes // Find out which TS was in use. if ((nonVolatileSettings.overrideTG == 0) && (vfoContact.reserve1 & 0x01) == 0x00) { vfoTS = ((vfoContact.reserve1 & 0x02) != 0); } else { vfoTS = tsGetManualOverride((Channel_t)nonVolatileSettings.currentVFONumber); // No Override, use the TS from the Channel if (vfoTS == 0) { vfoTS = ((settingsVFOChannel[nonVolatileSettings.currentVFONumber].flag2 & 0x40) != 0); } else { vfoTS--; // convert to real TS } } if (vfoTS == 0) { currentChannelData->flag2 &= 0xBF; } else { currentChannelData->flag2 |= 0x40; } codeplugChannelSaveDataForIndex(uiDataGlobal.currentSelectedChannelNumber, currentChannelData); } return true; } static void updateQuickMenuScreen(bool isFirstRun) { int mNum = 0; static const int bufferLen = 17; char buf[bufferLen]; char * const *leftSide;// initialise to please the compiler char * const *rightSideConst;// initialise to please the compiler char rightSideVar[bufferLen]; ucClearBuf(); menuDisplayTitle(currentLanguage->quick_menu); for(int i =- 1; i <= 1; i++) { mNum = menuGetMenuOffset(NUM_CH_SCREEN_QUICK_MENU_ITEMS, i); buf[0] = 0; rightSideVar[0] = 0; rightSideConst = NULL; leftSide = NULL; switch(mNum) { case CH_SCREEN_QUICK_MENU_COPY2VFO: rightSideConst = (char * const *)¤tLanguage->channelToVfo; break; case CH_SCREEN_QUICK_MENU_COPY_FROM_VFO: rightSideConst = (char * const *)¤tLanguage->vfoToChannel; break; case CH_SCREEN_QUICK_MENU_FILTER_FM: leftSide = (char * const *)¤tLanguage->filter; if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel == 0) { rightSideConst = (char * const *)¤tLanguage->none; } else { snprintf(rightSideVar, bufferLen, "%s", ANALOG_FILTER_LEVELS[uiDataGlobal.QuickMenu.tmpAnalogFilterLevel - 1]); } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR: leftSide = (char * const *)¤tLanguage->dmr_filter; if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel == 0) { rightSideConst = (char * const *)¤tLanguage->none; } else { snprintf(rightSideVar, bufferLen, "%s", DMR_DESTINATION_FILTER_LEVELS[uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel - 1]); } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_CC: leftSide = (char * const *)¤tLanguage->dmr_cc_filter; rightSideConst = (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN)?(char * const *)¤tLanguage->on:(char * const *)¤tLanguage->off; break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS: leftSide = (char * const *)¤tLanguage->dmr_ts_filter; rightSideConst = (uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN)?(char * const *)¤tLanguage->on:(char * const *)¤tLanguage->off; break; default: buf[0] = 0; } if (leftSide != NULL) { snprintf(buf, bufferLen, "%s:%s", *leftSide, (rightSideVar[0] ? rightSideVar : *rightSideConst)); } else { snprintf(buf, bufferLen, "%s", (rightSideVar[0] ? rightSideVar : *rightSideConst)); } if (i == 0) { if (!isFirstRun) { voicePromptsInit(); } if (leftSide != NULL) { voicePromptsAppendLanguageString((const char * const *)leftSide); } if (rightSideVar[0] != 0) { voicePromptsAppendString(rightSideVar); } else { voicePromptsAppendLanguageString((const char * const *)rightSideConst); } promptsPlayNotAfterTx(); } menuDisplayEntry(i, mNum, buf); } ucRender(); } static void handleQuickMenuEvent(uiEvent_t *ev) { bool isDirty = false; if (ev->events & BUTTON_EVENT) { if (repeatVoicePromptOnSK1(ev)) { return; } } if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { uiChannelModeStopScanning(); menuSystemPopPreviousMenu(); return; } else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { switch(menuDataGlobal.currentItemIndex) { case CH_SCREEN_QUICK_MENU_COPY2VFO: { int8_t currentTS = trxGetDMRTimeSlot(); memcpy(&settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxFreq, ¤tChannelData->rxFreq, CODEPLUG_CHANNEL_DATA_STRUCT_SIZE - 16);// Don't copy the name of channel, which are in the first 16 bytes settingsVFOChannel[nonVolatileSettings.currentVFONumber].rxTone = currentChannelData->rxTone; settingsVFOChannel[nonVolatileSettings.currentVFONumber].txTone = currentChannelData->txTone; if (nonVolatileSettings.overrideTG == 0) { nonVolatileSettings.overrideTG = trxTalkGroupOrPcId; } // Set TS override tsSetManualOverride(((Channel_t)nonVolatileSettings.currentVFONumber), currentTS + 1); if (currentTS == 0) { settingsVFOChannel[nonVolatileSettings.currentVFONumber].flag2 &= 0xBF; } else { settingsVFOChannel[nonVolatileSettings.currentVFONumber].flag2 |= 0x40; } menuSystemPopAllAndDisplaySpecificRootMenu(UI_VFO_MODE, true); } break; case CH_SCREEN_QUICK_MENU_COPY_FROM_VFO: if (quickmenuChannelFromVFOHandled == false) { snprintf(uiDataGlobal.MessageBox.message, MESSAGEBOX_MESSAGE_LEN_MAX, "%s\n%s", currentLanguage->overwrite_qm, currentLanguage->please_confirm); uiDataGlobal.MessageBox.type = MESSAGEBOX_TYPE_INFO; uiDataGlobal.MessageBox.decoration = MESSAGEBOX_DECORATION_FRAME; uiDataGlobal.MessageBox.buttons = MESSAGEBOX_BUTTONS_YESNO; uiDataGlobal.MessageBox.validatorCallback = validateOverwriteChannel; menuSystemPushNewMenu(UI_MESSAGE_BOX); voicePromptsInit(); voicePromptsAppendLanguageString(¤tLanguage->overwrite_qm); voicePromptsAppendLanguageString(¤tLanguage->please_confirm); voicePromptsPlay(); } return; break; case CH_SCREEN_QUICK_MENU_FILTER_FM: settingsSet(nonVolatileSettings.analogFilterLevel, (uint8_t) uiDataGlobal.QuickMenu.tmpAnalogFilterLevel); trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel); menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true); break; case CH_SCREEN_QUICK_MENU_FILTER_DMR: settingsSet(nonVolatileSettings.dmrDestinationFilter, (uint8_t) uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel); if (trxGetMode() == RADIO_MODE_DIGITAL) { init_digital_DMR_RX(); disableAudioAmp(AUDIO_AMP_MODE_RF); } menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true); break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_CC: case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS: settingsSet(nonVolatileSettings.dmrCcTsFilter, (uint8_t) uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel); if (trxGetMode() == RADIO_MODE_DIGITAL) { init_digital_DMR_RX(); disableAudioAmp(AUDIO_AMP_MODE_RF); } menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true); break; } return; } else if (KEYCHECK_PRESS(ev->keys, KEY_RIGHT)) { isDirty = true; switch(menuDataGlobal.currentItemIndex) { case CH_SCREEN_QUICK_MENU_FILTER_FM: if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel < NUM_ANALOG_FILTER_LEVELS - 1) { uiDataGlobal.QuickMenu.tmpAnalogFilterLevel++; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR: if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel < NUM_DMR_DESTINATION_FILTER_LEVELS - 1) { uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel++; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_CC: if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN)) { uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_CC_FILTER_PATTERN; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS: if (!(uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN)) { uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel |= DMR_TS_FILTER_PATTERN; } break; } } else { if (KEYCHECK_PRESS(ev->keys, KEY_LEFT)) { isDirty = true; switch(menuDataGlobal.currentItemIndex) { case CH_SCREEN_QUICK_MENU_FILTER_FM: if (uiDataGlobal.QuickMenu.tmpAnalogFilterLevel > ANALOG_FILTER_NONE) { uiDataGlobal.QuickMenu.tmpAnalogFilterLevel--; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR: if (uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel > DMR_DESTINATION_FILTER_NONE) { uiDataGlobal.QuickMenu.tmpDmrDestinationFilterLevel--; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_CC: if ((uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_CC_FILTER_PATTERN)) { uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_CC_FILTER_PATTERN; } break; case CH_SCREEN_QUICK_MENU_FILTER_DMR_TS: if ((uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel & DMR_TS_FILTER_PATTERN)) { uiDataGlobal.QuickMenu.tmpDmrCcTsFilterLevel &= ~DMR_TS_FILTER_PATTERN; } break; } } else { if (KEYCHECK_PRESS(ev->keys, KEY_DOWN)) { isDirty = true; menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, NUM_CH_SCREEN_QUICK_MENU_ITEMS); menuQuickChannelExitStatus |= MENU_STATUS_LIST_TYPE; } else { if (KEYCHECK_PRESS(ev->keys, KEY_UP)) { isDirty = true; menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, NUM_CH_SCREEN_QUICK_MENU_ITEMS); menuQuickChannelExitStatus |= MENU_STATUS_LIST_TYPE; } } } } if (isDirty) { updateQuickMenuScreen(false); } } //Scan Mode static void scanStart(bool longPressBeep) { // At least two channels are needed to run a scan process. if (canCurrentZoneBeScanned(&uiDataGlobal.Scan.availableChannelsCount) == false) { menuChannelExitStatus |= MENU_STATUS_ERROR; return; } if (voicePromptsIsPlaying()) { voicePromptsTerminate(); } directChannelNumber = 0; uiDataGlobal.Scan.direction = 1; // Clear all nuisance delete channels at start of scanning for (int i = 0; i < MAX_ZONE_SCAN_NUISANCE_CHANNELS; i++) { uiDataGlobal.Scan.nuisanceDelete[i] = -1; } uiDataGlobal.Scan.nuisanceDeleteIndex = 0; uiDataGlobal.Scan.active = true; uiDataGlobal.Scan.timer = SCAN_SHORT_PAUSE_TIME; uiDataGlobal.Scan.state = SCAN_SCANNING; uiDataGlobal.Scan.lastIteration = false; uiDataGlobal.Scan.stepTimeMilliseconds = settingsGetScanStepTimeMilliseconds(); if ((trxGetMode() == RADIO_MODE_DIGITAL) && (trxDMRModeRx != DMR_MODE_SFR) && (uiDataGlobal.Scan.stepTimeMilliseconds < SCAN_DMR_SIMPLEX_MIN_INTERVAL)) { uiDataGlobal.Scan.timerReload = SCAN_DMR_SIMPLEX_MIN_INTERVAL; } else { uiDataGlobal.Scan.timerReload = uiDataGlobal.Scan.stepTimeMilliseconds; } uiDataGlobal.Scan.scanType = SCAN_TYPE_NORMAL_STEP; // Need to set the melody here, otherwise long press will remain silent // since beeps aren't allowed while scanning if (longPressBeep) { soundSetMelody(MELODY_KEY_LONG_BEEP); } // Set current channel index if (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { nextChannelIndex = nonVolatileSettings.currentChannelIndexInAllZone; } else { nextChannelIndex = currentZone.channels[nonVolatileSettings.currentChannelIndexInZone]; } nextChannelReady = false; } static void updateTrxID(void) { if (nonVolatileSettings.overrideTG != 0) { trxTalkGroupOrPcId = nonVolatileSettings.overrideTG; } else { /* * VK3KYY * I can't see a compelling reason to remove the TS override when changing TG * The function trxUpdateTsForCurrentChannelWithSpecifiedContact(), used below, is used to handle the TS * tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE); */ if ((currentRxGroupData.name[0] != 0) && (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup)) { codeplugContactGetDataForIndex(currentRxGroupData.contacts[nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE]], ¤tContactData); } else { codeplugContactGetDataForIndex(currentChannelData->contact, ¤tContactData); } tsSetContactHasBeenOverriden(CHANNEL_CHANNEL, false); trxUpdateTsForCurrentChannelWithSpecifiedContact(¤tContactData); trxTalkGroupOrPcId = currentContactData.tgNumber; if (currentContactData.callType == CONTACT_CALLTYPE_PC) { trxTalkGroupOrPcId |= (PC_CALL_FLAG << 24); } } lastHeardClearLastID(); menuPrivateCallClear(); } static void scanning(void) { if((uiDataGlobal.Scan.state == SCAN_SCANNING) && (uiDataGlobal.Scan.timer > SCAN_SKIP_CHANNEL_INTERVAL) && (uiDataGlobal.Scan.timer < (uiDataGlobal.Scan.timerReload - SCAN_FREQ_CHANGE_SETTLING_INTERVAL))) //after initial settling time { //test for presence of RF Carrier. // In FM mode the DMR slot_state will always be DMR_STATE_IDLE if ((slot_state != DMR_STATE_IDLE) && ((dmrMonitorCapturedTS != -1) && (((trxDMRModeRx == DMR_MODE_DMO) && (dmrMonitorCapturedTS == trxGetDMRTimeSlot())) || trxDMRModeRx == DMR_MODE_RMO))) { uiDataGlobal.Scan.state = SCAN_PAUSED; #if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own // Reload the channel as voice prompts aren't set while scanning if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_LEVEL_1) { loadChannelData(false, true); } #endif if (nonVolatileSettings.scanModePause == SCAN_MODE_STOP) { uiChannelModeStopScanning(); return; } else { uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh uiDataGlobal.Scan.timer = nonVolatileSettings.scanDelay * 1000; } } else { if(trxCarrierDetected()) { #if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own // Reload the channel as voice prompts aren't set while scanning if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_LEVEL_1) { loadChannelData(false, true); } #endif if (nonVolatileSettings.scanModePause == SCAN_MODE_STOP) { uiChannelModeStopScanning(); return; } else { uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh uiDataGlobal.Scan.timer = SCAN_SHORT_PAUSE_TIME; //start short delay to allow full detection of signal uiDataGlobal.Scan.state = SCAN_SHORT_PAUSED; //state 1 = pause and test for valid signal that produces audio } } } } // Only do this once if scan mode is PAUSE do it every time if scan mode is HOLD if(((uiDataGlobal.Scan.state == SCAN_PAUSED) && (nonVolatileSettings.scanModePause == SCAN_MODE_HOLD)) || (uiDataGlobal.Scan.state == SCAN_SHORT_PAUSED)) { if (getAudioAmpStatus() & AUDIO_AMP_MODE_RF) { uiDataGlobal.Scan.timer = nonVolatileSettings.scanDelay * 1000; uiDataGlobal.Scan.state = SCAN_PAUSED; } } if(uiDataGlobal.Scan.timer > 0) { if (nextChannelReady == false) { scanSearchForNextChannel(); } uiDataGlobal.Scan.timer--; } else { if (nextChannelReady) { scanApplyNextChannel(); // When less than 2 channel remain in the Zone if (uiDataGlobal.Scan.lastIteration) { uiChannelModeStopScanning(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); return; } } uiDataGlobal.Scan.state = SCAN_SCANNING; //state 0 = settling and test for carrier present. } } void uiChannelModeStopScanning(void) { uiDataGlobal.Scan.active = false; uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; // Force screen refresh #if ! defined(PLATFORM_GD77S) // GD77S handle voice prompts on its own // Reload the channel as voice prompts aren't set while scanning if (nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_LEVEL_1) { loadChannelData(false, true); } #endif } bool uiChannelModeIsScanning(void) { return uiDataGlobal.Scan.active; } void uiChannelModeColdStart(void) { currentChannelData->rxFreq = 0; // Force to re-read codeplug data (needed due to "All Channels" translation) } #if defined(PLATFORM_GD77S) bool uiChannelModeTransmitDTMFContactForGD77S(void) { if (GD77SParameters.uiMode == GD77S_UIMODE_DTMF_CONTACTS) { if (GD77SParameters.dtmfListCount > 0) { // start dtmf sequence if(dtmfSequenceIsKeying() == false) { struct_codeplugDTMFContact_t dtmfContact; if (voicePromptsIsPlaying()) { voicePromptsTerminate(); } codeplugDTMFContactGetDataForNumber(GD77SParameters.dtmfListSelected + 1, &dtmfContact); dtmfSequencePrepare(dtmfContact.code, true); } else { dtmfSequenceStop(); dtmfSequenceTick(false); dtmfSequenceReset(); } } else { voicePromptsInit(); voicePromptsAppendLanguageString(¤tLanguage->empty_list); voicePromptsPlay(); } return true; } return false; } void toggleTimeslotForGD77S(void) { if (trxGetMode() == RADIO_MODE_DIGITAL) { // Toggle timeslot trxSetDMRTimeSlot(1 - trxGetDMRTimeSlot()); tsSetManualOverride(CHANNEL_CHANNEL, (trxGetDMRTimeSlot() + 1)); // init_digital(); disableAudioAmp(AUDIO_AMP_MODE_RF); clearActiveDMRID(); lastHeardClearLastID(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } } void uiChannelModeHeartBeatActivityForGD77S(uiEvent_t *ev) { static const uint32_t periods[] = { 5000, 100, 100, 100, 100, 100 }; static const uint32_t periodsScan[] = { 2000, 50, 2000, 50, 2000, 50 }; static uint8_t beatRoll = 0; static uint32_t mTime = 0; // // We use real time GPIO readouts, as LED could be turned on/off by another task. // if ((LEDs_PinRead(GPIO_LEDred, Pin_LEDred) || LEDs_PinRead(GPIO_LEDgreen, Pin_LEDgreen)) // Any led is ON && (trxTransmissionEnabled || (uiDataGlobal.DTMFContactList.isKeying) || (ev->buttons & BUTTON_PTT) || (getAudioAmpStatus() & (AUDIO_AMP_MODE_RF | AUDIO_AMP_MODE_BEEP | AUDIO_AMP_MODE_PROMPT)) || trxCarrierDetected() || ev->hasEvent)) // we're transmitting, or receiving, or user interaction. { // Turn off the red LED, if not transmitting if (LEDs_PinRead(GPIO_LEDred, Pin_LEDred) // Red is ON && ((uiDataGlobal.DTMFContactList.isKeying == false) && ((trxTransmissionEnabled == false) || ((ev->buttons & BUTTON_PTT) == 0)))) // No TX { LEDs_PinWrite(GPIO_LEDred, Pin_LEDred, 0); } // Turn off the green LED, if not receiving, or no AF output if (LEDs_PinRead(GPIO_LEDgreen, Pin_LEDgreen)) // Green is ON { if ((trxTransmissionEnabled || (uiDataGlobal.DTMFContactList.isKeying) || (ev->buttons & BUTTON_PTT)) || ((trxGetMode() == RADIO_MODE_DIGITAL) && (slot_state != DMR_STATE_IDLE)) || (((getAudioAmpStatus() & (AUDIO_AMP_MODE_RF | AUDIO_AMP_MODE_BEEP | AUDIO_AMP_MODE_PROMPT)) != 0) || trxCarrierDetected())) { if ((ev->buttons & BUTTON_PTT) && (trxTransmissionEnabled == false)) // RX Only or Out of Band { LEDs_PinWrite(GPIO_LEDgreen, Pin_LEDgreen, 0); } } else { LEDs_PinWrite(GPIO_LEDgreen, Pin_LEDgreen, 0); } } // Reset pattern sequence beatRoll = 0; // And update the timer for the next first starting (OFF for 5 seconds) blink sequence. mTime = ev->time; return; } // Nothing is happening, blink if (((trxTransmissionEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false) && ((ev->buttons & BUTTON_PTT) == 0)) && ((ev->hasEvent == false) && ((getAudioAmpStatus() & (AUDIO_AMP_MODE_RF | AUDIO_AMP_MODE_BEEP | AUDIO_AMP_MODE_PROMPT)) == 0) && (trxCarrierDetected() == false))) { // Blink both LEDs to have Orange color if ((ev->time - mTime) > (uiDataGlobal.Scan.active ? periodsScan[beatRoll] : periods[beatRoll])) { mTime = ev->time; beatRoll = (beatRoll + 1) % (uiDataGlobal.Scan.active ? (sizeof(periodsScan) / sizeof(periodsScan[0])) : (sizeof(periods) / sizeof(periods[0]))); LEDs_PinWrite(GPIO_LEDred, Pin_LEDred, (beatRoll % 2)); LEDs_PinWrite(GPIO_LEDgreen, Pin_LEDgreen, (beatRoll % 2)); } } else { // Reset pattern sequence beatRoll = 0; // And update the timer for the next first starting (OFF for 5 seconds) blink sequence. mTime = ev->time; } } static uint16_t getCurrentChannelInCurrentZoneForGD77S(void) { return (CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone) ? nonVolatileSettings.currentChannelIndexInAllZone : nonVolatileSettings.currentChannelIndexInZone + 1); } static void checkAndUpdateSelectedChannelForGD77S(uint16_t chanNum, bool forceSpeech) { bool updateDisplay = false; if(CODEPLUG_ZONE_IS_ALLCHANNELS(currentZone)) { GD77SParameters.channelOutOfBounds = false; if (codeplugAllChannelsIndexIsInUse(chanNum)) { if (chanNum != nonVolatileSettings.currentChannelIndexInAllZone) { settingsSet(nonVolatileSettings.currentChannelIndexInAllZone, (int16_t) chanNum); loadChannelData(false, false); updateDisplay = true; } } else { GD77SParameters.channelOutOfBounds = true; if (voicePromptsIsPlaying() == false) { voicePromptsInit(); voicePromptsAppendPrompt(PROMPT_CHANNEL); voicePromptsAppendLanguageString(¤tLanguage->error); voicePromptsPlay(); } } } else { if ((chanNum - 1) < currentZone.NOT_IN_CODEPLUGDATA_numChannelsInZone) { GD77SParameters.channelOutOfBounds = false; if ((chanNum - 1) != nonVolatileSettings.currentChannelIndexInZone) { settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) (chanNum - 1)); loadChannelData(false, false); updateDisplay = true; } } else { GD77SParameters.channelOutOfBounds = true; if (voicePromptsIsPlaying() == false) { voicePromptsInit(); voicePromptsAppendPrompt(PROMPT_CHANNEL); voicePromptsAppendLanguageString(¤tLanguage->error); voicePromptsPlay(); } } } // Prevent TXing while an invalid channel is selected if (getCurrentChannelInCurrentZoneForGD77S() != chanNum) { PTTLocked = true; } else { if (PTTLocked) { PTTLocked = false; forceSpeech = true; } } if (updateDisplay || forceSpeech) { if (GD77SParameters.channelOutOfBounds == false) { char buf[17]; voicePromptsInit(); voicePromptsAppendPrompt(PROMPT_CHANNEL); voicePromptsAppendInteger(chanNum); voicePromptsAppendPrompt(PROMPT_SILENCE); codeplugUtilConvertBufToString(currentChannelData->name, buf, 16); voicePromptsAppendString(buf); voicePromptsPlay(); } if (!forceSpeech) { uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } } } static void buildSpeechChannelDetailsForGD77S() { char buf[17]; codeplugUtilConvertBufToString(currentChannelData->name, buf, 16); voicePromptsAppendString(buf); voicePromptsAppendPrompt(PROMPT_SILENCE); announceFrequency(); voicePromptsAppendPrompt(PROMPT_SILENCE); if (trxGetMode() == RADIO_MODE_DIGITAL) { announceContactNameTgOrPc(voicePromptsIsPlaying()); voicePromptsAppendPrompt(PROMPT_SILENCE); announceTS(); voicePromptsAppendPrompt(PROMPT_SILENCE); announceCC(); } else { if (currentChannelData->rxTone != CODEPLUG_CSS_NONE) { bool isCTCSS = codeplugChannelToneIsCTCSS(currentChannelData->rxTone); buildCSSCodeVoicePrompts(currentChannelData->rxTone, (isCTCSS ? CSS_CTCSS : ((currentChannelData->rxTone & CODEPLUG_DCS_INVERTED_MASK) ? CSS_DCS_INVERTED : CSS_DCS)), DIRECTION_RECEIVE, true); voicePromptsAppendPrompt(PROMPT_SILENCE); } if (currentChannelData->txTone != CODEPLUG_CSS_NONE) { bool isCTCSS = codeplugChannelToneIsCTCSS(currentChannelData->txTone); buildCSSCodeVoicePrompts(currentChannelData->txTone, (isCTCSS ? CSS_CTCSS : ((currentChannelData->txTone & CODEPLUG_DCS_INVERTED_MASK) ? CSS_DCS_INVERTED : CSS_DCS)), DIRECTION_TRANSMIT, true); } } } static void buildSpeechUiModeForGD77S(GD77S_UIMODES_t uiMode) { char buf[17]; if (voicePromptsIsPlaying()) { voicePromptsTerminate(); } switch (uiMode) { case GD77S_UIMODE_TG_OR_SQUELCH: // Channel codeplugUtilConvertBufToString(currentChannelData->name, buf, 16); voicePromptsAppendString(buf); if (trxGetMode() == RADIO_MODE_DIGITAL) { announceTS(); } else { announceSquelchLevel(voicePromptsIsPlaying()); } break; case GD77S_UIMODE_SCAN: // Scan voicePromptsAppendLanguageString(¤tLanguage->scan); voicePromptsAppendLanguageString(uiDataGlobal.Scan.active ? ¤tLanguage->on : ¤tLanguage->off); break; case GD77S_UIMODE_TS: // Timeslot if (trxGetMode() == RADIO_MODE_DIGITAL) { announceTS(); } break; case GD77S_UIMODE_CC: // Color code if (trxGetMode() == RADIO_MODE_DIGITAL) { announceCC(); } break; case GD77S_UIMODE_FILTER: // DMR/Analog filter voicePromptsAppendLanguageString(¤tLanguage->filter); if (trxGetMode() == RADIO_MODE_DIGITAL) { if (nonVolatileSettings.dmrDestinationFilter == DMR_DESTINATION_FILTER_NONE) { voicePromptsAppendLanguageString(¤tLanguage->none); } else { voicePromptsAppendString((char *)DMR_DESTINATION_FILTER_LEVELS[nonVolatileSettings.dmrDestinationFilter - 1]); } } else { if (nonVolatileSettings.analogFilterLevel == ANALOG_FILTER_NONE) { voicePromptsAppendLanguageString(¤tLanguage->none); } else { voicePromptsAppendString((char *)ANALOG_FILTER_LEVELS[nonVolatileSettings.analogFilterLevel - 1]); } } break; case GD77S_UIMODE_DTMF_CONTACTS: if (GD77SParameters.dtmfListCount > 0) { struct_codeplugDTMFContact_t dtmfContact; codeplugDTMFContactGetDataForNumber(GD77SParameters.dtmfListSelected + 1, &dtmfContact); codeplugUtilConvertBufToString(dtmfContact.name, buf, 16); voicePromptsAppendString(buf); } else { voicePromptsAppendLanguageString(¤tLanguage->empty_list); } break; case GD77S_UIMODE_ZONE: // Zone announceZoneName(voicePromptsIsPlaying()); break; case GD77S_UIMODE_POWER: // Power announcePowerLevel(voicePromptsIsPlaying()); break; case GD77S_UIMODE_MAX: break; } } static void handleEventForGD77S(uiEvent_t *ev) { if (ev->events & ROTARY_EVENT) { if (dtmfSequenceIsKeying()) { dtmfSequenceStop(); } if (!trxTransmissionEnabled && (ev->rotary > 0)) { if (uiDataGlobal.Scan.active) { uiChannelModeStopScanning(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } settingsSet(nonVolatileSettings.overrideTG, 0); checkAndUpdateSelectedChannelForGD77S(ev->rotary, false); clearActiveDMRID(); lastHeardClearLastID(); } } if (handleMonitorMode(ev)) { return; } if (ev->events & BUTTON_EVENT) { if (dtmfSequenceIsKeying() && (ev->buttons & (BUTTON_SK1 | BUTTON_SK2 | BUTTON_ORANGE))) { dtmfSequenceStop(); } if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && uiDataGlobal.Scan.active) { uiChannelModeStopScanning(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); if (voicePromptsIsPlaying()) { voicePromptsTerminate(); } voicePromptsInit(); buildSpeechUiModeForGD77S(GD77S_UIMODE_SCAN); voicePromptsPlay(); return; } if (BUTTONCHECK_LONGDOWN(ev, BUTTON_ORANGE) && (uiDataGlobal.DTMFContactList.isKeying == false)) { announceItem(PROMPT_SEQUENCE_BATTERY, PROMPT_THRESHOLD_3); return; } else if (BUTTONCHECK_SHORTUP(ev, BUTTON_ORANGE) && (uiDataGlobal.DTMFContactList.isKeying == false)) { voicePrompt_t vp = NUM_VOICE_PROMPTS; char * const *vpString = NULL; GD77SParameters.uiMode = (GD77S_UIMODES_t) (GD77SParameters.uiMode + 1) % GD77S_UIMODE_MAX; //skip over Digital controls if the radio is in Analog mode if (trxGetMode() == RADIO_MODE_ANALOG) { // Analog if ((GD77SParameters.uiMode == GD77S_UIMODE_TS) || (GD77SParameters.uiMode == GD77S_UIMODE_CC)) { GD77SParameters.uiMode = GD77S_UIMODE_FILTER; } } else { // digital if (GD77SParameters.uiMode == GD77S_UIMODE_DTMF_CONTACTS) { GD77SParameters.uiMode = GD77S_UIMODE_ZONE; } } switch (GD77SParameters.uiMode) { case GD77S_UIMODE_TG_OR_SQUELCH: // Channel Mode if (trxGetMode() == RADIO_MODE_DIGITAL) { vp = PROMPT_CHANNEL_MODE; } else { vpString = (char * const *)¤tLanguage->squelch; } break; case GD77S_UIMODE_SCAN: vp = PROMPT_SCAN_MODE; break; case GD77S_UIMODE_TS: // Timeslot Mode vp = PROMPT_TIMESLOT_MODE; break; case GD77S_UIMODE_CC: // ColorCode Mode vp = PROMPT_COLORCODE_MODE; break; case GD77S_UIMODE_FILTER: // DMR/Analog Filter vp = PROMPT_FILTER_MODE; break; case GD77S_UIMODE_DTMF_CONTACTS: vpString = (char * const *)¤tLanguage->dtmf_contact_list; break; case GD77S_UIMODE_ZONE: // Zone Mode vp = PROMPT_ZONE_MODE; break; case GD77S_UIMODE_POWER: // Power Mode vp = PROMPT_POWER_MODE; break; case GD77S_UIMODE_MAX: break; } if ((vp != NUM_VOICE_PROMPTS) || (vpString != NULL)) { voicePromptsInit(); if (vpString) { voicePromptsAppendLanguageString((const char * const *)vpString); voicePromptsAppendPrompt(PROMPT_MODE); } else { voicePromptsAppendPrompt(vp); } voicePromptsAppendPrompt(PROMPT_SILENCE); buildSpeechUiModeForGD77S(GD77SParameters.uiMode); voicePromptsPlay(); } } else if (BUTTONCHECK_LONGDOWN(ev, BUTTON_SK1) && (monitorModeData.isEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false)) { if (GD77SParameters.channelOutOfBounds == false) { voicePromptsInit(); buildSpeechChannelDetailsForGD77S(); voicePromptsPlay(); } } else if (BUTTONCHECK_SHORTUP(ev, BUTTON_SK1) && (uiDataGlobal.DTMFContactList.isKeying == false)) { switch (GD77SParameters.uiMode) { case GD77S_UIMODE_TG_OR_SQUELCH: if (trxGetMode() == RADIO_MODE_DIGITAL) { // Next in TGList if (nonVolatileSettings.overrideTG == 0) { settingsIncrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1); if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] > (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)) { settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 0); } } settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden menuPrivateCallClear(); updateTrxID(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3); } else { if(currentChannelData->sql == 0) //If we were using default squelch level { currentChannelData->sql = nonVolatileSettings.squelchDefaults[trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point. } if (currentChannelData->sql < CODEPLUG_MAX_VARIABLE_SQUELCH) { currentChannelData->sql++; } announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3); } break; case GD77S_UIMODE_SCAN: if (uiDataGlobal.Scan.active) { uiChannelModeStopScanning(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); } else { scanStart(false); } voicePromptsInit(); voicePromptsAppendLanguageString(¤tLanguage->scan); voicePromptsAppendLanguageString(uiDataGlobal.Scan.active ? ¤tLanguage->on : ¤tLanguage->off); voicePromptsPlay(); break; case GD77S_UIMODE_TS: if (trxGetMode() == RADIO_MODE_DIGITAL) { toggleTimeslotForGD77S(); announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_3); } break; case GD77S_UIMODE_CC: if (trxGetMode() == RADIO_MODE_DIGITAL) { if (currentChannelData->rxColor < 15) { currentChannelData->rxColor++; trxSetDMRColourCode(currentChannelData->rxColor); } voicePromptsInit(); announceCC(); voicePromptsPlay(); } break; case GD77S_UIMODE_FILTER: if (trxGetMode() == RADIO_MODE_DIGITAL) { if (nonVolatileSettings.dmrDestinationFilter < NUM_DMR_DESTINATION_FILTER_LEVELS - 1) { settingsIncrement(nonVolatileSettings.dmrDestinationFilter, 1); init_digital_DMR_RX(); disableAudioAmp(AUDIO_AMP_MODE_RF); } } else { if (nonVolatileSettings.analogFilterLevel < NUM_ANALOG_FILTER_LEVELS - 1) { settingsIncrement(nonVolatileSettings.analogFilterLevel, 1); trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel); } } voicePromptsInit(); buildSpeechUiModeForGD77S(GD77SParameters.uiMode); voicePromptsPlay(); break; case GD77S_UIMODE_DTMF_CONTACTS: // select next DTMF contact and spell it if (GD77SParameters.dtmfListCount > 0) { GD77SParameters.dtmfListSelected = (GD77SParameters.dtmfListSelected + 1) % GD77SParameters.dtmfListCount; } voicePromptsInit(); buildSpeechUiModeForGD77S(GD77SParameters.uiMode); voicePromptsPlay(); break; case GD77S_UIMODE_ZONE: // Zones // No "All Channels" on GD77S nonVolatileSettings.currentZone = (nonVolatileSettings.currentZone + 1) % (codeplugZonesGetCount() - 1); settingsSet(nonVolatileSettings.overrideTG, 0); // remove any TG override tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE); settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) -2); // Will be updated when reloading the UiChannelMode screen currentChannelData->rxFreq = 0x00; // Flag to the Channel screen that the channel data is now invalid and needs to be reloaded menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true); GD77SParameters.uiMode = GD77S_UIMODE_ZONE; announceItem(PROMPT_SEQUENCE_ZONE, PROMPT_THRESHOLD_3); break; case GD77S_UIMODE_POWER: // Power increasePowerLevel(true);// true = Allow 5W++ break; case GD77S_UIMODE_MAX: break; } } else if (BUTTONCHECK_LONGDOWN(ev, BUTTON_SK2) && (monitorModeData.isEnabled == false) && (uiDataGlobal.DTMFContactList.isKeying == false)) { uint32_t tg = (LinkHead->talkGroupOrPcId & 0xFFFFFF); // If Blue button is long pressed during reception it sets the Tx TG to the incoming TG if (uiDataGlobal.isDisplayingQSOData && BUTTONCHECK_DOWN(ev, BUTTON_SK2) && (trxGetMode() == RADIO_MODE_DIGITAL) && ((trxTalkGroupOrPcId != tg) || ((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot())) || (trxGetDMRColourCode() != currentChannelData->rxColor))) { voicePromptsInit(); voicePromptsAppendLanguageString(¤tLanguage->select_tx); voicePromptsPlay(); lastHeardClearLastID(); // Set TS to overriden TS if ((dmrMonitorCapturedTS != -1) && (dmrMonitorCapturedTS != trxGetDMRTimeSlot())) { trxSetDMRTimeSlot(dmrMonitorCapturedTS); tsSetManualOverride(CHANNEL_CHANNEL, (dmrMonitorCapturedTS + 1)); } if (trxTalkGroupOrPcId != tg) { if ((tg >> 24) & PC_CALL_FLAG) { acceptPrivateCall(tg & 0xffffff, -1); } else { trxTalkGroupOrPcId = tg; settingsSet(nonVolatileSettings.overrideTG, trxTalkGroupOrPcId); } } currentChannelData->rxColor = trxGetDMRColourCode();// Set the CC to the current CC, which may have been determined by the CC finding algorithm in C6000.c uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); return; } } else if (BUTTONCHECK_SHORTUP(ev, BUTTON_SK2) && (uiDataGlobal.DTMFContactList.isKeying == false)) { switch (GD77SParameters.uiMode) { case GD77S_UIMODE_TG_OR_SQUELCH: // Previous in TGList if (trxGetMode() == RADIO_MODE_DIGITAL) { // To Do change TG in on same channel freq if (nonVolatileSettings.overrideTG == 0) { settingsDecrement(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], 1); if (nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE] < 0) { settingsSet(nonVolatileSettings.currentIndexInTRxGroupList[SETTINGS_CHANNEL_MODE], (int16_t) (currentRxGroupData.NOT_IN_CODEPLUG_numTGsInGroup - 1)); } } settingsSet(nonVolatileSettings.overrideTG, 0);// setting the override TG to 0 indicates the TG is not overridden menuPrivateCallClear(); updateTrxID(); uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN; uiChannelModeUpdateScreen(0); announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3); } else { if(currentChannelData->sql == 0) //If we were using default squelch level { currentChannelData->sql = nonVolatileSettings.squelchDefaults[trxCurrentBand[TRX_RX_FREQ_BAND]]; //start the adjustment from that point. } if (currentChannelData->sql > CODEPLUG_MIN_VARIABLE_SQUELCH) { currentChannelData->sql--; } announceItem(PROMPT_SQUENCE_SQUELCH, PROMPT_THRESHOLD_3); } break; case GD77S_UIMODE_SCAN: if (uiDataGlobal.Scan.active) { // if we are scanning and down key is pressed then enter current channel into nuisance delete array. if(uiDataGlobal.Scan.state == SCAN_PAUSED) { // There is two channels available in the Zone, just stop scanning if (uiDataGlobal.Scan.nuisanceDeleteIndex == (uiDataGlobal.Scan.availableChannelsCount - 2)) { uiDataGlobal.Scan.lastIteration = true; } uiDataGlobal.Scan.nuisanceDelete[uiDataGlobal.Scan.nuisanceDeleteIndex] = uiDataGlobal.currentSelectedChannelNumber; uiDataGlobal.Scan.nuisanceDeleteIndex = (uiDataGlobal.Scan.nuisanceDeleteIndex + 1) % MAX_ZONE_SCAN_NUISANCE_CHANNELS; uiDataGlobal.Scan.timer = SCAN_SKIP_CHANNEL_INTERVAL; //force scan to continue; uiDataGlobal.Scan.state = SCAN_SCANNING; return; } // Left key reverses the scan direction if (uiDataGlobal.Scan.state == SCAN_SCANNING) { uiDataGlobal.Scan.direction *= -1; return; } } break; case GD77S_UIMODE_TS: if (trxGetMode() == RADIO_MODE_DIGITAL) { toggleTimeslotForGD77S(); announceItem(PROMPT_SEQUENCE_TS, PROMPT_THRESHOLD_3); } break; case GD77S_UIMODE_CC: if (trxGetMode() == RADIO_MODE_DIGITAL) { if (currentChannelData->rxColor > 0) { currentChannelData->rxColor--; trxSetDMRColourCode(currentChannelData->rxColor); } voicePromptsInit(); announceCC(); voicePromptsPlay(); } break; case GD77S_UIMODE_FILTER: if (trxGetMode() == RADIO_MODE_DIGITAL) { if (nonVolatileSettings.dmrDestinationFilter > DMR_DESTINATION_FILTER_NONE) { settingsDecrement(nonVolatileSettings.dmrDestinationFilter, 1); init_digital_DMR_RX(); disableAudioAmp(AUDIO_AMP_MODE_RF); } } else { if (nonVolatileSettings.analogFilterLevel > ANALOG_FILTER_NONE) { settingsDecrement(nonVolatileSettings.analogFilterLevel, 1); trxSetAnalogFilterLevel(nonVolatileSettings.analogFilterLevel); } } voicePromptsInit(); buildSpeechUiModeForGD77S(GD77SParameters.uiMode); voicePromptsPlay(); break; case GD77S_UIMODE_DTMF_CONTACTS: // select previous DTMF contact and spell it if (GD77SParameters.dtmfListCount > 0) { GD77SParameters.dtmfListSelected = (GD77SParameters.dtmfListSelected + GD77SParameters.dtmfListCount - 1) % GD77SParameters.dtmfListCount; } voicePromptsInit(); buildSpeechUiModeForGD77S(GD77SParameters.uiMode); voicePromptsPlay(); break; case GD77S_UIMODE_ZONE: // Zones // No "All Channels" on GD77S nonVolatileSettings.currentZone = (nonVolatileSettings.currentZone + (codeplugZonesGetCount() - 1) - 1) % (codeplugZonesGetCount() - 1); settingsSet(nonVolatileSettings.overrideTG, 0); // remove any TG override tsSetManualOverride(CHANNEL_CHANNEL, TS_NO_OVERRIDE); settingsSet(nonVolatileSettings.currentChannelIndexInZone, (int16_t) -2); // Will be updated when reloading the UiChannelMode screen currentChannelData->rxFreq = 0x00; // Flag to the Channel screeen that the channel data is now invalid and needs to be reloaded menuSystemPopAllAndDisplaySpecificRootMenu(UI_CHANNEL_MODE, true); GD77SParameters.uiMode = GD77S_UIMODE_ZONE; announceItem(PROMPT_SEQUENCE_ZONE, PROMPT_THRESHOLD_3); break; case GD77S_UIMODE_POWER: // Power decreasePowerLevel(); break; case GD77S_UIMODE_MAX: break; } } } } #endif // PLATFORM_GD77S