0
mirror of https://github.com/OneOfEleven/uv-k5-firmware-custom.git synced 2025-06-19 06:39:49 +03:00

Initial commit

This commit is contained in:
OneOfEleven
2023-09-09 08:03:56 +01:00
parent 92305117f1
commit 54441e27d9
3388 changed files with 582553 additions and 0 deletions

302
app/action.c Normal file
View File

@ -0,0 +1,302 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "app/action.h"
#include "app/app.h"
#include "app/dtmf.h"
#include "app/fm.h"
#include "app/scanner.h"
#include "audio.h"
#include "bsp/dp32g030/gpio.h"
#include "driver/bk1080.h"
#include "driver/bk4819.h"
#include "driver/gpio.h"
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
static void ACTION_FlashLight(void)
{
switch (gFlashLightState)
{
case 0:
gFlashLightState++;
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_FLASHLIGHT);
break;
case 1:
gFlashLightState++;
break;
default:
gFlashLightState = 0;
GPIO_ClearBit(&GPIOC->DATA, GPIOC_PIN_FLASHLIGHT);
}
}
void ACTION_Power(void)
{
if (++gTxVfo->OUTPUT_POWER > OUTPUT_POWER_HIGH)
gTxVfo->OUTPUT_POWER = OUTPUT_POWER_LOW;
gRequestSaveChannel = 1;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_POWER;
#endif
gRequestDisplayScreen = gScreenToDisplay;
}
static void ACTION_Monitor(void)
{
if (gCurrentFunction != FUNCTION_MONITOR)
{
RADIO_SelectVfos();
#ifndef DISABLE_NOAA
if (gRxVfo->CHANNEL_SAVE >= NOAA_CHANNEL_FIRST && gIsNoaaMode)
gNoaaChannel = gRxVfo->CHANNEL_SAVE - NOAA_CHANNEL_FIRST;
#endif
RADIO_SetupRegisters(true);
APP_StartListening(FUNCTION_MONITOR);
return;
}
if (gScanState != SCAN_OFF)
{
ScanPauseDelayIn10msec = 500;
gScheduleScanListen = false;
gScanPauseMode = true;
}
#ifndef DISABLE_NOAA
if (gEeprom.DUAL_WATCH == DUAL_WATCH_OFF && gIsNoaaMode)
{
gNOAA_Countdown = 500;
gScheduleNOAA = false;
}
#endif
RADIO_SetupRegisters(true);
if (gFmRadioMode)
{
FM_Start();
gRequestDisplayScreen = DISPLAY_FM;
}
else
gRequestDisplayScreen = gScreenToDisplay;
}
void ACTION_Scan(bool bRestart)
{
if (gFmRadioMode)
{
if (gCurrentFunction != FUNCTION_RECEIVE && gCurrentFunction != FUNCTION_MONITOR && gCurrentFunction != FUNCTION_TRANSMIT)
{
uint16_t Frequency;
GUI_SelectNextDisplay(DISPLAY_FM);
if (gFM_ScanState != FM_SCAN_OFF)
{
FM_PlayAndUpdate();
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
}
else
{
if (bRestart)
{
gFM_AutoScan = true;
gFM_ChannelPosition = 0;
FM_EraseChannels();
Frequency = gEeprom.FM_LowerLimit;
}
else
{
gFM_AutoScan = false;
gFM_ChannelPosition = 0;
Frequency = gEeprom.FM_FrequencyPlaying;
}
BK1080_GetFrequencyDeviation(Frequency);
FM_Tune(Frequency, 1, bRestart);
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_BEGIN;
#endif
}
}
}
else
if (gScreenToDisplay != DISPLAY_SCANNER)
{
RADIO_SelectVfos();
#ifndef DISABLE_NOAA
if (IS_NOT_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
#endif
{
GUI_SelectNextDisplay(DISPLAY_MAIN);
if (gScanState != SCAN_OFF)
{
SCANNER_Stop();
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
}
else
{
CHANNEL_Next(true, 1);
#ifndef DISABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_SCANNING_BEGIN);
AUDIO_PlaySingleVoice(true);
#endif
}
}
}
}
void ACTION_Vox(void)
{
gEeprom.VOX_SWITCH = !gEeprom.VOX_SWITCH;
gRequestSaveSettings = true;
gFlagReconfigureVfos = true;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_VOX;
#endif
gUpdateStatus = true;
}
static void ACTION_AlarmOr1750(bool b1750)
{
gInputBoxIndex = 0;
gAlarmState = b1750 ? ALARM_STATE_TX1750 : ALARM_STATE_TXALARM;
gAlarmRunningCounter = 0;
gFlagPrepareTX = true;
gRequestDisplayScreen = DISPLAY_MAIN;
}
void ACTION_FM(void)
{
if (gCurrentFunction != FUNCTION_TRANSMIT && gCurrentFunction != FUNCTION_MONITOR)
{
if (gFmRadioMode)
{
FM_TurnOff();
gInputBoxIndex = 0;
gVoxResumeCountdown = 80;
gFlagReconfigureVfos = true;
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
FM_Start();
gInputBoxIndex = 0;
gRequestDisplayScreen = DISPLAY_FM;
}
}
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Short;
uint8_t Long;
if (gScreenToDisplay == DISPLAY_MAIN && gDTMF_InputMode)
{
if (Key == KEY_SIDE1 && !bKeyHeld && bKeyPressed)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (gDTMF_InputIndex)
{
gDTMF_InputBox[--gDTMF_InputIndex] = '-';
if (gDTMF_InputIndex)
{
gPttWasReleased = true;
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
}
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_MAIN;
gDTMF_InputMode = false;
}
gPttWasReleased = true;
return;
}
if (Key == KEY_SIDE1)
{
Short = gEeprom.KEY_1_SHORT_PRESS_ACTION;
Long = gEeprom.KEY_1_LONG_PRESS_ACTION;
}
else
{
Short = gEeprom.KEY_2_SHORT_PRESS_ACTION;
Long = gEeprom.KEY_2_LONG_PRESS_ACTION;
}
if (!bKeyHeld && bKeyPressed)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
if (bKeyHeld || bKeyPressed)
{
if (!bKeyHeld)
return;
Short = Long;
if (!bKeyPressed)
return;
}
switch (Short)
{
case 1:
ACTION_FlashLight();
break;
case 2:
ACTION_Power();
break;
case 3:
ACTION_Monitor();
break;
case 4:
ACTION_Scan(true);
break;
case 5:
ACTION_Vox();
break;
case 6:
ACTION_AlarmOr1750(false);
break;
case 7:
ACTION_FM();
break;
case 8:
ACTION_AlarmOr1750(true);
break;
}
}

33
app/action.h Normal file
View File

@ -0,0 +1,33 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_ACTION_H
#define APP_ACTION_H
#include "driver/keyboard.h"
//static void ACTION_FlashLight(void)
void ACTION_Power(void);
//static void ACTION_Monitor(void)
void ACTION_Scan(bool bFlag);
void ACTION_Vox(void);
//static void ACTION_AlarmOr1750(bool b1750)
void ACTION_FM(void);
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
#endif

231
app/aircopy.c Normal file
View File

@ -0,0 +1,231 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef DISABLE_AIRCOPY
#include "app/aircopy.h"
#include "audio.h"
#include "driver/bk4819.h"
#include "driver/crc.h"
#include "driver/eeprom.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "ui/helper.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
static const uint16_t Obfuscation[8] = {0x6C16, 0xE614, 0x912E, 0x400D, 0x3521, 0x40D5, 0x0313, 0x80E9};
AIRCOPY_State_t gAircopyState;
uint16_t gAirCopyBlockNumber;
uint16_t gErrorsDuringAirCopy;
uint8_t gAirCopyIsSendMode;
uint16_t g_FSK_Buffer[36];
void AIRCOPY_SendMessage(void)
{
unsigned int i;
g_FSK_Buffer[1] = (gAirCopyBlockNumber & 0x3FF) << 6;
EEPROM_ReadBuffer(g_FSK_Buffer[1], &g_FSK_Buffer[2], 64);
g_FSK_Buffer[34] = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
for (i = 0; i < 34; i++)
g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8];
if (++gAirCopyBlockNumber >= 0x78)
gAircopyState = AIRCOPY_COMPLETE;
RADIO_SetTxParameters();
BK4819_SendFSKData(g_FSK_Buffer);
BK4819_SetupPowerAmplifier(0, 0);
BK4819_ToggleGpioOut(BK4819_GPIO5_PIN1, false);
gAircopySendCountdown = 30;
}
void AIRCOPY_StorePacket(void)
{
uint16_t Status;
if (gFSKWriteIndex < 36)
return;
gFSKWriteIndex = 0;
gUpdateDisplay = true;
Status = BK4819_ReadRegister(BK4819_REG_0B);
BK4819_PrepareFSKReceive();
// Doc says bit 4 should be 1 = CRC OK, 0 = CRC FAIL, but original firmware checks for FAIL.
if ((Status & 0x0010U) == 0 && g_FSK_Buffer[0] == 0xABCD && g_FSK_Buffer[35] == 0xDCBA)
{
uint16_t CRC;
unsigned int i;
for (i = 0; i < 34; i++)
g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8];
CRC = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64);
if (g_FSK_Buffer[34] == CRC)
{
const uint16_t *pData;
uint16_t Offset;
Offset = g_FSK_Buffer[1];
if (Offset < 0x1E00)
{
pData = &g_FSK_Buffer[2];
for (i = 0; i < 8; i++)
{
EEPROM_WriteBuffer(Offset, pData);
pData += 4;
Offset += 8;
}
if (Offset == 0x1E00)
gAircopyState = AIRCOPY_COMPLETE;
gAirCopyBlockNumber++;
return;
}
}
}
gErrorsDuringAirCopy++;
}
static void AIRCOPY_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
uint32_t Frequency;
unsigned int i;
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_AIRCOPY;
if (gInputBoxIndex < 6)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
return;
}
gInputBoxIndex = 0;
NUMBER_Get(gInputBox, &Frequency);
for (i = 0; i < 7; i++)
{
if (Frequency >= gLowerLimitFrequencyBandTable[i] && Frequency <= gUpperLimitFrequencyBandTable[i])
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gRxVfo->Band = i;
Frequency += 75;
Frequency = FREQUENCY_FloorToStep(Frequency, gRxVfo->StepFrequency, 0);
gRxVfo->ConfigRX.Frequency = Frequency;
gRxVfo->ConfigTX.Frequency = Frequency;
RADIO_ConfigureSquelchAndOutputPower(gRxVfo);
gCurrentVfo = gRxVfo;
RADIO_SetupRegisters(true);
BK4819_SetupAircopy();
BK4819_ResetFSK();
return;
}
}
gRequestDisplayScreen = DISPLAY_AIRCOPY;
}
}
static void AIRCOPY_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
if (gInputBoxIndex == 0)
{
gFSKWriteIndex = 0;
gAirCopyBlockNumber = 0;
gErrorsDuringAirCopy = 0;
gInputBoxIndex = 0;
gAirCopyIsSendMode = 0;
BK4819_PrepareFSKReceive();
gAircopyState = AIRCOPY_TRANSFER;
}
else
gInputBox[--gInputBoxIndex] = 10;
gRequestDisplayScreen = DISPLAY_AIRCOPY;
}
}
static void AIRCOPY_Key_MENU(bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
gFSKWriteIndex = 0;
gAirCopyBlockNumber = 0;
gInputBoxIndex = 0;
gAirCopyIsSendMode = 1;
g_FSK_Buffer[0] = 0xABCD;
g_FSK_Buffer[1] = 0;
g_FSK_Buffer[35] = 0xDCBA;
AIRCOPY_SendMessage();
GUI_DisplayScreen();
gAircopyState = AIRCOPY_TRANSFER;
}
}
void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
switch (Key)
{
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
AIRCOPY_Key_DIGITS(Key, bKeyPressed, bKeyHeld);
break;
case KEY_MENU:
AIRCOPY_Key_MENU(bKeyPressed, bKeyHeld);
break;
case KEY_EXIT:
AIRCOPY_Key_EXIT(bKeyPressed, bKeyHeld);
break;
default:
break;
}
}
#endif

47
app/aircopy.h Normal file
View File

@ -0,0 +1,47 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_AIRCOPY_H
#define APP_AIRCOPY_H
#ifndef DISABLE_AIRCOPY
#include "driver/keyboard.h"
enum AIRCOPY_State_t
{
AIRCOPY_READY = 0,
AIRCOPY_TRANSFER,
AIRCOPY_COMPLETE
};
typedef enum AIRCOPY_State_t AIRCOPY_State_t;
extern AIRCOPY_State_t gAircopyState;
extern uint16_t gAirCopyBlockNumber;
extern uint16_t gErrorsDuringAirCopy;
extern uint8_t gAirCopyIsSendMode;
extern uint16_t g_FSK_Buffer[36];
void AIRCOPY_SendMessage(void);
void AIRCOPY_StorePacket(void);
void AIRCOPY_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
#endif
#endif

1922
app/app.c Normal file

File diff suppressed because it is too large Load Diff

34
app/app.h Normal file
View File

@ -0,0 +1,34 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_APP_H
#define APP_APP_H
#include <stdbool.h>
#include "functions.h"
#include "radio.h"
void APP_EndTransmission(void);
void CHANNEL_Next(bool bFlag, int8_t Direction);
void APP_StartListening(FUNCTION_Type_t Function);
void APP_SetFrequencyByStep(VFO_Info_t *pInfo, int8_t Step);
void APP_Update(void);
void APP_TimeSlice10ms(void);
void APP_TimeSlice500ms(void);
#endif

371
app/dtmf.c Normal file
View File

@ -0,0 +1,371 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "app/fm.h"
#include "app/scanner.h"
#include "bsp/dp32g030/gpio.h"
#include "driver/bk4819.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "driver/system.h"
#include "dtmf.h"
#include "external/printf/printf.h"
#include "misc.h"
#include "settings.h"
#include "ui/ui.h"
char gDTMF_String[15];
char gDTMF_InputBox[15];
char gDTMF_Received[16];
bool gIsDtmfContactValid;
char gDTMF_ID[4];
char gDTMF_Caller[4];
char gDTMF_Callee[4];
DTMF_State_t gDTMF_State;
bool gDTMF_DecodeRing;
uint8_t gDTMF_DecodeRingCountdown;
uint8_t gDTMFChosenContact;
uint8_t gDTMF_WriteIndex;
uint8_t gDTMF_PreviousIndex;
uint8_t gDTMF_AUTO_RESET_TIME;
uint8_t gDTMF_InputIndex;
bool gDTMF_InputMode;
uint8_t gDTMF_RecvTimeout;
DTMF_CallState_t gDTMF_CallState;
DTMF_ReplyState_t gDTMF_ReplyState;
DTMF_CallMode_t gDTMF_CallMode;
bool gDTMF_IsTx;
uint8_t gDTMF_TxStopCountdown;
bool gDTMF_IsGroupCall;
bool DTMF_ValidateCodes(char *pCode, uint8_t Size)
{
uint8_t i;
if (pCode[0] == 0xFF || pCode[0] == 0)
return false;
for (i = 0; i < Size; i++)
{
if (pCode[i] == 0xFF || pCode[i] == 0)
{
pCode[i] = 0;
break;
}
if ((pCode[i] < '0' || pCode[i] > '9') && (pCode[i] < 'A' || pCode[i] > 'D') && pCode[i] != '*' && pCode[i] != '#')
return false;
}
return true;
}
bool DTMF_GetContact(uint8_t Index, char *pContact)
{
EEPROM_ReadBuffer(0x1C00 + (Index * 16), pContact, 16);
return ((pContact[0] - ' ') >= 0x5F) ? false : true;
}
bool DTMF_FindContact(const char *pContact, char *pResult)
{
char Contact [16];
uint8_t i, j;
for (i = 0; i < 16; i++)
{
if (!DTMF_GetContact(i, Contact))
return false;
for (j = 0; j < 3; j++)
if (pContact[j] != Contact[j + 8])
break;
if (j == 3)
{
memcpy(pResult, Contact, 8);
pResult[8] = 0;
return true;
}
}
return false;
}
char DTMF_GetCharacter(uint8_t Code)
{
switch (Code)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
return '0' + (char)Code;
case 10:
return 'A';
case 11:
return 'B';
case 12:
return 'C';
case 13:
return 'D';
case 14:
return '*';
case 15:
return '#';
}
return 0xFF;
}
bool DTMF_CompareMessage(const char *pMsg, const char *pTemplate, uint8_t Size, bool bCheckGroup)
{
uint8_t i;
for (i = 0; i < Size; i++)
{
if (pMsg[i] != pTemplate[i])
{
if (!bCheckGroup || pMsg[i] != gEeprom.DTMF_GROUP_CALL_CODE)
return false;
gDTMF_IsGroupCall = true;
}
}
return true;
}
bool DTMF_CheckGroupCall(const char *pMsg, uint32_t Size)
{
uint32_t i;
for (i = 0; i < Size; i++)
if (pMsg[i] == gEeprom.DTMF_GROUP_CALL_CODE)
break;
return (i != Size) ? true : false;
}
void DTMF_Append(char Code)
{
if (gDTMF_InputIndex == 0)
{
memset(gDTMF_InputBox, '-', sizeof(gDTMF_InputBox));
gDTMF_InputBox[14] = 0;
}
else
if (gDTMF_InputIndex >= sizeof(gDTMF_InputBox))
return;
gDTMF_InputBox[gDTMF_InputIndex++] = Code;
}
void DTMF_HandleRequest(void)
{
char String[20];
uint8_t Offset;
if (!gDTMF_RequestPending)
return;
gDTMF_RequestPending = false;
if (gScanState != SCAN_OFF || gCssScanMode != CSS_SCAN_MODE_OFF)
return;
if (!gRxVfo->DTMF_DECODING_ENABLE && !gSetting_KILLED)
return;
if (gDTMF_WriteIndex >= 9)
{
Offset = gDTMF_WriteIndex - 9;
sprintf(String, "%s%c%s", gEeprom.ANI_DTMF_ID, gEeprom.DTMF_SEPARATE_CODE, gEeprom.KILL_CODE);
if (DTMF_CompareMessage(gDTMF_Received + Offset, String, 9, true))
{
if (gEeprom.PERMIT_REMOTE_KILL)
{
gSetting_KILLED = true;
SETTINGS_SaveSettings();
gDTMF_ReplyState = DTMF_REPLY_AB;
if (gFmRadioMode)
{
FM_TurnOff();
GUI_SelectNextDisplay(DISPLAY_MAIN);
}
}
else
{
gDTMF_ReplyState = DTMF_REPLY_NONE;
}
gDTMF_CallState = DTMF_CALL_STATE_NONE;
gUpdateDisplay = true;
gUpdateStatus = true;
return;
}
sprintf(String, "%s%c%s", gEeprom.ANI_DTMF_ID, gEeprom.DTMF_SEPARATE_CODE, gEeprom.REVIVE_CODE);
if (DTMF_CompareMessage(gDTMF_Received + Offset, String, 9, true))
{
gSetting_KILLED = false;
SETTINGS_SaveSettings();
gDTMF_ReplyState = DTMF_REPLY_AB;
gDTMF_CallState = DTMF_CALL_STATE_NONE;
gUpdateDisplay = true;
gUpdateStatus = true;
return;
}
}
if (gDTMF_WriteIndex >= 2)
{
if (DTMF_CompareMessage(gDTMF_Received + (gDTMF_WriteIndex - 2), "AB", 2, true))
{
gDTMF_State = DTMF_STATE_TX_SUCC;
gUpdateDisplay = true;
return;
}
}
if (gDTMF_CallState == DTMF_CALL_STATE_CALL_OUT && gDTMF_CallMode == DTMF_CALL_MODE_NOT_GROUP && gDTMF_WriteIndex >= 9)
{
Offset = gDTMF_WriteIndex - 9;
sprintf(String, "%s%c%s", gDTMF_String, gEeprom.DTMF_SEPARATE_CODE, "AAAAA");
if (DTMF_CompareMessage(gDTMF_Received + Offset, String, 9, false))
{
gDTMF_State = DTMF_STATE_CALL_OUT_RSP;
gUpdateDisplay = true;
}
}
if (gSetting_KILLED || gDTMF_CallState != DTMF_CALL_STATE_NONE)
return;
if (gDTMF_WriteIndex >= 7)
{
Offset = gDTMF_WriteIndex - 7;
sprintf(String, "%s%c", gEeprom.ANI_DTMF_ID, gEeprom.DTMF_SEPARATE_CODE);
gDTMF_IsGroupCall = false;
if (DTMF_CompareMessage(gDTMF_Received + Offset, String, 4, true))
{
gDTMF_CallState = DTMF_CALL_STATE_RECEIVED;
memcpy(gDTMF_Callee, gDTMF_Received + Offset, 3);
memcpy(gDTMF_Caller, gDTMF_Received + Offset + 4, 3);
gUpdateDisplay = true;
switch (gEeprom.DTMF_DECODE_RESPONSE)
{
case 3:
gDTMF_DecodeRing = true;
gDTMF_DecodeRingCountdown = 20;
// Fallthrough
case 2:
gDTMF_ReplyState = DTMF_REPLY_AAAAA;
break;
case 1:
gDTMF_DecodeRing = true;
gDTMF_DecodeRingCountdown = 20;
break;
default:
gDTMF_DecodeRing = false;
gDTMF_ReplyState = DTMF_REPLY_NONE;
break;
}
if (gDTMF_IsGroupCall)
gDTMF_ReplyState = DTMF_REPLY_NONE;
}
}
}
void DTMF_Reply(void)
{
char String[20];
const char *pString;
uint16_t Delay;
switch (gDTMF_ReplyState)
{
case DTMF_REPLY_ANI:
if (gDTMF_CallMode == DTMF_CALL_MODE_DTMF)
{
pString = gDTMF_String;
}
else
{
sprintf(String, "%s%c%s", gDTMF_String, gEeprom.DTMF_SEPARATE_CODE, gEeprom.ANI_DTMF_ID);
pString = String;
}
break;
case DTMF_REPLY_AB:
pString = "AB";
break;
case DTMF_REPLY_AAAAA:
sprintf(String, "%s%c%s", gEeprom.ANI_DTMF_ID, gEeprom.DTMF_SEPARATE_CODE, "AAAAA");
pString = String;
break;
default:
if (gDTMF_CallState != DTMF_CALL_STATE_NONE || (gCurrentVfo->DTMF_PTT_ID_TX_MODE != PTT_ID_BOT && gCurrentVfo->DTMF_PTT_ID_TX_MODE != PTT_ID_BOTH))
{
gDTMF_ReplyState = DTMF_REPLY_NONE;
return;
}
pString = gEeprom.DTMF_UP_CODE;
break;
}
gDTMF_ReplyState = DTMF_REPLY_NONE;
Delay = gEeprom.DTMF_PRELOAD_TIME;
if (gEeprom.DTMF_SIDE_TONE)
{
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = true;
Delay = gEeprom.DTMF_PRELOAD_TIME;
if (gEeprom.DTMF_PRELOAD_TIME < 60)
Delay = 60;
}
SYSTEM_DelayMs(Delay);
BK4819_EnterDTMF_TX(gEeprom.DTMF_SIDE_TONE);
BK4819_PlayDTMFString(
pString,
1,
gEeprom.DTMF_FIRST_CODE_PERSIST_TIME,
gEeprom.DTMF_HASH_CODE_PERSIST_TIME,
gEeprom.DTMF_CODE_PERSIST_TIME,
gEeprom.DTMF_CODE_INTERVAL_TIME);
GPIO_ClearBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = false;
BK4819_ExitDTMF_TX(false);
}

90
app/dtmf.h Normal file
View File

@ -0,0 +1,90 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef DTMF_H
#define DTMF_H
#include <stdbool.h>
#include <stdint.h>
enum DTMF_State_t {
DTMF_STATE_0 = 0U,
DTMF_STATE_TX_SUCC = 1U,
DTMF_STATE_CALL_OUT_RSP = 2U,
};
typedef enum DTMF_State_t DTMF_State_t;
enum DTMF_CallState_t {
DTMF_CALL_STATE_NONE = 0U,
DTMF_CALL_STATE_CALL_OUT = 1U,
DTMF_CALL_STATE_RECEIVED = 2U,
};
typedef enum DTMF_CallState_t DTMF_CallState_t;
enum DTMF_ReplyState_t {
DTMF_REPLY_NONE = 0U,
DTMF_REPLY_ANI = 1U,
DTMF_REPLY_AB = 2U,
DTMF_REPLY_AAAAA = 3U,
};
typedef enum DTMF_ReplyState_t DTMF_ReplyState_t;
enum DTMF_CallMode_t {
DTMF_CALL_MODE_NOT_GROUP = 0U,
DTMF_CALL_MODE_GROUP = 1U,
DTMF_CALL_MODE_DTMF = 2U,
};
typedef enum DTMF_CallMode_t DTMF_CallMode_t;
extern char gDTMF_String[15];
extern char gDTMF_InputBox[15];
extern char gDTMF_Received[16];
extern bool gIsDtmfContactValid;
extern char gDTMF_ID[4];
extern char gDTMF_Caller[4];
extern char gDTMF_Callee[4];
extern DTMF_State_t gDTMF_State;
extern bool gDTMF_DecodeRing;
extern uint8_t gDTMF_DecodeRingCountdown;
extern uint8_t gDTMFChosenContact;
extern uint8_t gDTMF_WriteIndex;
extern uint8_t gDTMF_PreviousIndex;
extern uint8_t gDTMF_AUTO_RESET_TIME;
extern uint8_t gDTMF_InputIndex;
extern bool gDTMF_InputMode;
extern uint8_t gDTMF_RecvTimeout;
extern DTMF_CallState_t gDTMF_CallState;
extern DTMF_ReplyState_t gDTMF_ReplyState;
extern DTMF_CallMode_t gDTMF_CallMode;
extern bool gDTMF_IsTx;
extern uint8_t gDTMF_TxStopCountdown;
bool DTMF_ValidateCodes(char *pCode, uint8_t Size);
bool DTMF_GetContact(uint8_t Index, char *pContact);
bool DTMF_FindContact(const char *pContact, char *pResult);
char DTMF_GetCharacter(uint8_t Code);
bool DTMF_CompareMessage(const char *pDTMF, const char *pTemplate, uint8_t Size, bool bFlag);
bool DTMF_CheckGroupCall(const char *pDTMF, uint32_t Size);
void DTMF_Append(char Code);
void DTMF_HandleRequest(void);
void DTMF_Reply(void);
#endif

563
app/fm.c Normal file
View File

@ -0,0 +1,563 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "app/action.h"
#include "app/fm.h"
#include "app/generic.h"
#include "audio.h"
#include "bsp/dp32g030/gpio.h"
#include "driver/bk1080.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
uint16_t gFM_Channels[20];
bool gFmRadioMode;
uint8_t gFmRadioCountdown;
volatile uint16_t gFmPlayCountdown;
volatile int8_t gFM_ScanState;
bool gFM_AutoScan;
uint8_t gFM_ChannelPosition;
bool gFM_FoundFrequency;
bool gFM_AutoScan;
uint8_t gFM_ResumeCountdown;
uint16_t gFM_RestoreCountdown;
bool FM_CheckValidChannel(uint8_t Channel)
{
if (Channel < 20 && (gFM_Channels[Channel] >= 760 && gFM_Channels[Channel] < 1080)) {
return true;
}
return false;
}
uint8_t FM_FindNextChannel(uint8_t Channel, uint8_t Direction)
{
uint8_t i;
for (i = 0; i < 20; i++) {
Channel %= 20;
if (FM_CheckValidChannel(Channel)) {
return Channel;
}
Channel += Direction;
}
return 0xFF;
}
int FM_ConfigureChannelState(void)
{
uint8_t Channel;
gEeprom.FM_FrequencyPlaying = gEeprom.FM_SelectedFrequency;
if (gEeprom.FM_IsMrMode) {
Channel = FM_FindNextChannel(gEeprom.FM_SelectedChannel, FM_CHANNEL_UP);
if (Channel == 0xFF) {
gEeprom.FM_IsMrMode = false;
return -1;
}
gEeprom.FM_SelectedChannel = Channel;
gEeprom.FM_FrequencyPlaying = gFM_Channels[Channel];
}
return 0;
}
void FM_TurnOff(void)
{
gFmRadioMode = false;
gFM_ScanState = FM_SCAN_OFF;
gFM_RestoreCountdown = 0;
GPIO_ClearBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = false;
BK1080_Init(0, false);
gUpdateStatus = true;
}
void FM_EraseChannels(void)
{
uint8_t i;
uint8_t Template[8];
memset(Template, 0xFF, sizeof(Template));
for (i = 0; i < 5; i++) {
EEPROM_WriteBuffer(0x0E40 + (i * 8), Template);
}
memset(gFM_Channels, 0xFF, sizeof(gFM_Channels));
}
void FM_Tune(uint16_t Frequency, int8_t Step, bool bFlag)
{
GPIO_ClearBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = false;
if (gFM_ScanState == FM_SCAN_OFF) {
gFmPlayCountdown = 120;
} else {
gFmPlayCountdown = 10;
}
gScheduleFM = false;
gFM_FoundFrequency = false;
gAskToSave = false;
gAskToDelete = false;
gEeprom.FM_FrequencyPlaying = Frequency;
if (!bFlag) {
Frequency += Step;
if (Frequency < gEeprom.FM_LowerLimit) {
Frequency = gEeprom.FM_UpperLimit;
} else if (Frequency > gEeprom.FM_UpperLimit) {
Frequency = gEeprom.FM_LowerLimit;
}
gEeprom.FM_FrequencyPlaying = Frequency;
}
gFM_ScanState = Step;
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
}
void FM_PlayAndUpdate(void)
{
gFM_ScanState = FM_SCAN_OFF;
if (gFM_AutoScan) {
gEeprom.FM_IsMrMode = true;
gEeprom.FM_SelectedChannel = 0;
}
FM_ConfigureChannelState();
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
SETTINGS_SaveFM();
gFmPlayCountdown = 0;
gScheduleFM = false;
gAskToSave = false;
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = true;
}
int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit)
{
uint16_t Test2;
uint16_t Deviation;
int ret = -1;
Test2 = BK1080_ReadRegister(BK1080_REG_07);
// This is supposed to be a signed value, but above function is unsigned
Deviation = BK1080_REG_07_GET_FREQD(Test2);
if (BK1080_REG_07_GET_SNR(Test2) >= 2) {
uint16_t Status;
Status = BK1080_ReadRegister(BK1080_REG_10);
if ((Status & BK1080_REG_10_MASK_AFCRL) == BK1080_REG_10_AFCRL_NOT_RAILED && BK1080_REG_10_GET_RSSI(Status) >= 10) {
// if (Deviation > -281 && Deviation < 280)
if (Deviation < 280 || Deviation > 3815) {
// not BLE(less than or equal)
if (Frequency > LowerLimit && (Frequency - BK1080_BaseFrequency) == 1) {
if (BK1080_FrequencyDeviation & 0x800) {
goto Bail;
}
if (BK1080_FrequencyDeviation < 20) {
goto Bail;
}
}
// not BLT(less than)
if (Frequency >= LowerLimit && (BK1080_BaseFrequency - Frequency) == 1) {
if ((BK1080_FrequencyDeviation & 0x800) == 0) {
goto Bail;
}
// if (BK1080_FrequencyDeviation > -21) {
if (BK1080_FrequencyDeviation > 4075) {
goto Bail;
}
}
ret = 0;
}
}
}
Bail:
BK1080_FrequencyDeviation = Deviation;
BK1080_BaseFrequency = Frequency;
return ret;
}
static void FM_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
#define STATE_FREQ_MODE 0
#define STATE_MR_MODE 1
#define STATE_SAVE 2
if (!bKeyHeld && bKeyPressed) {
if (!gWasFKeyPressed) {
uint8_t State;
if (gAskToDelete) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (gAskToSave) {
State = STATE_SAVE;
} else {
if (gFM_ScanState != FM_SCAN_OFF) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (gEeprom.FM_IsMrMode) {
State = STATE_MR_MODE;
} else {
State = STATE_FREQ_MODE;
}
}
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_FM;
if (State == STATE_FREQ_MODE) {
if (gInputBoxIndex == 1) {
if (gInputBox[0] > 1) {
gInputBox[1] = gInputBox[0];
gInputBox[0] = 0;
gInputBoxIndex = 2;
}
} else if (gInputBoxIndex > 3) {
uint32_t Frequency;
gInputBoxIndex = 0;
NUMBER_Get(gInputBox, &Frequency);
Frequency = Frequency / 10000;
if (Frequency < gEeprom.FM_LowerLimit || gEeprom.FM_UpperLimit < Frequency) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
gRequestDisplayScreen = DISPLAY_FM;
return;
}
gEeprom.FM_SelectedFrequency = (uint16_t)Frequency;
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gEeprom.FM_FrequencyPlaying = gEeprom.FM_SelectedFrequency;
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
gRequestSaveFM = true;
return;
}
} else if (gInputBoxIndex == 2) {
uint8_t Channel;
gInputBoxIndex = 0;
Channel = ((gInputBox[0] * 10) + gInputBox[1]) - 1;
if (State == STATE_MR_MODE) {
if (FM_CheckValidChannel(Channel))
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gEeprom.FM_SelectedChannel = Channel;
gEeprom.FM_FrequencyPlaying = gFM_Channels[Channel];
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
gRequestSaveFM = true;
return;
}
}
else
if (Channel < 20)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gRequestDisplayScreen = DISPLAY_FM;
gInputBoxIndex = 0;
gFM_ChannelPosition = Channel;
return;
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gWasFKeyPressed = false;
gUpdateStatus = true;
gRequestDisplayScreen = DISPLAY_FM;
switch (Key) {
case KEY_0:
ACTION_FM();
break;
case KEY_1:
gEeprom.FM_IsMrMode = !gEeprom.FM_IsMrMode;
if (!FM_ConfigureChannelState()) {
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
gRequestSaveFM = true;
} else {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
break;
case KEY_2:
ACTION_Scan(true);
break;
case KEY_3:
ACTION_Scan(false);
break;
default:
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
}
}
static void FM_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (bKeyHeld) {
return;
}
if (!bKeyPressed) {
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (gFM_ScanState == FM_SCAN_OFF) {
if (gInputBoxIndex == 0) {
if (!gAskToSave && !gAskToDelete) {
ACTION_FM();
return;
}
gAskToSave = false;
gAskToDelete = false;
} else {
gInputBoxIndex--;
gInputBox[gInputBoxIndex] = 10;
if (gInputBoxIndex) {
if (gInputBoxIndex != 1) {
gRequestDisplayScreen = DISPLAY_FM;
return;
}
if (gInputBox[0] != 0) {
gRequestDisplayScreen = DISPLAY_FM;
return;
}
}
gInputBoxIndex = 0;
}
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
}
else
{
FM_PlayAndUpdate();
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
}
gRequestDisplayScreen = DISPLAY_FM;
}
static void FM_Key_MENU(bool bKeyPressed, bool bKeyHeld)
{
if (bKeyHeld) {
return;
}
if (!bKeyPressed) {
return;
}
gRequestDisplayScreen = DISPLAY_FM;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (gFM_ScanState == FM_SCAN_OFF) {
if (!gEeprom.FM_IsMrMode) {
if (gAskToSave) {
gFM_Channels[gFM_ChannelPosition] = gEeprom.FM_FrequencyPlaying;
gAskToSave = false;
gRequestSaveFM = true;
} else {
gAskToSave = true;
}
} else {
if (gAskToDelete) {
gFM_Channels[gEeprom.FM_SelectedChannel] = 0xFFFF;
FM_ConfigureChannelState();
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
gRequestSaveFM = true;
gAskToDelete = false;
} else {
gAskToDelete = true;
}
}
} else {
if (gFM_AutoScan || !gFM_FoundFrequency) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
gInputBoxIndex = 0;
return;
} else if (gAskToSave) {
gFM_Channels[gFM_ChannelPosition] = gEeprom.FM_FrequencyPlaying;
gAskToSave = false;
gRequestSaveFM = true;
} else {
gAskToSave = true;
}
}
}
static void FM_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Step)
{
if (bKeyHeld || !bKeyPressed) {
if (gInputBoxIndex) {
return;
}
if (!bKeyPressed) {
return;
}
} else {
if (gInputBoxIndex) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
if (gAskToSave) {
gRequestDisplayScreen = DISPLAY_FM;
gFM_ChannelPosition = NUMBER_AddWithWraparound(gFM_ChannelPosition, Step, 0, 19);
return;
}
if (gFM_ScanState != FM_SCAN_OFF) {
if (gFM_AutoScan) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
FM_Tune(gEeprom.FM_FrequencyPlaying, Step, false);
gRequestDisplayScreen = DISPLAY_FM;
return;
}
if (gEeprom.FM_IsMrMode) {
uint8_t Channel;
Channel = FM_FindNextChannel(gEeprom.FM_SelectedChannel + Step, Step);
if (Channel == 0xFF || gEeprom.FM_SelectedChannel == Channel) {
goto Bail;
}
gEeprom.FM_SelectedChannel = Channel;
gEeprom.FM_FrequencyPlaying = gFM_Channels[Channel];
} else {
uint16_t Frequency;
Frequency = gEeprom.FM_SelectedFrequency + Step;
if (Frequency < gEeprom.FM_LowerLimit) {
Frequency = gEeprom.FM_UpperLimit;
} else if (Frequency > gEeprom.FM_UpperLimit) {
Frequency = gEeprom.FM_LowerLimit;
}
gEeprom.FM_FrequencyPlaying = Frequency;
gEeprom.FM_SelectedFrequency = gEeprom.FM_FrequencyPlaying;
}
gRequestSaveFM = true;
Bail:
BK1080_SetFrequency(gEeprom.FM_FrequencyPlaying);
gRequestDisplayScreen = DISPLAY_FM;
}
void FM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
switch (Key) {
case KEY_0: case KEY_1: case KEY_2: case KEY_3:
case KEY_4: case KEY_5: case KEY_6: case KEY_7:
case KEY_8: case KEY_9:
FM_Key_DIGITS(Key, bKeyPressed, bKeyHeld);
break;
case KEY_MENU:
FM_Key_MENU(bKeyPressed, bKeyHeld);
return;
case KEY_UP:
FM_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1);
break;
case KEY_DOWN:
FM_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1);
break;;
case KEY_EXIT:
FM_Key_EXIT(bKeyPressed, bKeyHeld);
break;
case KEY_F:
GENERIC_Key_F(bKeyPressed, bKeyHeld);
break;
case KEY_PTT:
GENERIC_Key_PTT(bKeyPressed);
break;
default:
if (!bKeyHeld && bKeyPressed) {
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
break;
}
}
void FM_Play(void)
{
if (!FM_CheckFrequencyLock(gEeprom.FM_FrequencyPlaying, gEeprom.FM_LowerLimit)) {
if (!gFM_AutoScan) {
gFmPlayCountdown = 0;
gFM_FoundFrequency = true;
if (!gEeprom.FM_IsMrMode) {
gEeprom.FM_SelectedFrequency = gEeprom.FM_FrequencyPlaying;
}
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = true;
GUI_SelectNextDisplay(DISPLAY_FM);
return;
}
if (gFM_ChannelPosition < 20) {
gFM_Channels[gFM_ChannelPosition++] = gEeprom.FM_FrequencyPlaying;
}
if (gFM_ChannelPosition >= 20) {
FM_PlayAndUpdate();
GUI_SelectNextDisplay(DISPLAY_FM);
return;
}
}
if (gFM_AutoScan && gEeprom.FM_FrequencyPlaying >= gEeprom.FM_UpperLimit) {
FM_PlayAndUpdate();
} else {
FM_Tune(gEeprom.FM_FrequencyPlaying, gFM_ScanState, false);
}
GUI_SelectNextDisplay(DISPLAY_FM);
}
void FM_Start(void)
{
gFmRadioMode = true;
gFM_ScanState = FM_SCAN_OFF;
gFM_RestoreCountdown = 0;
BK1080_Init(gEeprom.FM_FrequencyPlaying, true);
GPIO_SetBit(&GPIOC->DATA, GPIOC_PIN_AUDIO_PATH);
gEnableSpeaker = true;
gUpdateStatus = true;
}

59
app/fm.h Normal file
View File

@ -0,0 +1,59 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_FM_H
#define APP_FM_H
#include "driver/keyboard.h"
#define FM_CHANNEL_UP 0x01
#define FM_CHANNEL_DOWN 0xFF
enum {
FM_SCAN_OFF = 0U,
};
extern uint16_t gFM_Channels[20];
extern bool gFmRadioMode;
extern uint8_t gFmRadioCountdown;
extern volatile uint16_t gFmPlayCountdown;
extern volatile int8_t gFM_ScanState;
extern bool gFM_AutoScan;
extern uint8_t gFM_ChannelPosition;
// Doubts about whether this should be signed or not.
extern uint16_t gFM_FrequencyDeviation;
extern bool gFM_FoundFrequency;
extern bool gFM_AutoScan;
extern uint8_t gFM_ResumeCountdown;
extern uint16_t gFM_RestoreCountdown;
bool FM_CheckValidChannel(uint8_t Channel);
uint8_t FM_FindNextChannel(uint8_t Channel, uint8_t Direction);
int FM_ConfigureChannelState(void);
void FM_TurnOff(void);
void FM_EraseChannels(void);
void FM_Tune(uint16_t Frequency, int8_t Step, bool bFlag);
void FM_PlayAndUpdate(void);
int FM_CheckFrequencyLock(uint16_t Frequency, uint16_t LowerLimit);
void FM_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void FM_Play(void);
void FM_Start(void);
#endif

223
app/generic.c Normal file
View File

@ -0,0 +1,223 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "app/app.h"
#include "app/fm.h"
#include "app/generic.h"
#include "app/menu.h"
#include "app/scanner.h"
#include "audio.h"
#include "driver/keyboard.h"
#include "dtmf.h"
#include "external/printf/printf.h"
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld)
{
if (gInputBoxIndex)
{
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (bKeyHeld || !bKeyPressed)
{
if (bKeyHeld || bKeyPressed)
{
if (!bKeyHeld)
return;
if (!bKeyPressed)
return;
#ifndef DISABLE_VOICE
gAnotherVoiceID = gEeprom.KEY_LOCK ? VOICE_ID_UNLOCK : VOICE_ID_LOCK;
#endif
gEeprom.KEY_LOCK = !gEeprom.KEY_LOCK;
gRequestSaveSettings = true;
}
else
{
if ((gFmRadioMode || gScreenToDisplay != DISPLAY_MAIN) && gScreenToDisplay != DISPLAY_FM)
return;
gWasFKeyPressed = !gWasFKeyPressed;
#ifndef DISABLE_VOICE
if (!gWasFKeyPressed)
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gUpdateStatus = true;
}
}
else
{
if (gScreenToDisplay != DISPLAY_FM)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
if (gFM_ScanState == FM_SCAN_OFF)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
gBeepToPlay = BEEP_440HZ_500MS;
gPttWasReleased = true;
}
}
void GENERIC_Key_PTT(bool bKeyPressed)
{
gInputBoxIndex = 0;
if (!bKeyPressed)
{
if (gScreenToDisplay == DISPLAY_MAIN)
{
if (gCurrentFunction == FUNCTION_TRANSMIT)
{
if (gFlagEndTransmission)
{
FUNCTION_Select(FUNCTION_FOREGROUND);
}
else
{
APP_EndTransmission();
if (gEeprom.REPEATER_TAIL_TONE_ELIMINATION == 0)
FUNCTION_Select(FUNCTION_FOREGROUND);
else
gRTTECountdown = gEeprom.REPEATER_TAIL_TONE_ELIMINATION * 10;
}
gFlagEndTransmission = false;
gVOX_NoiseDetected = false;
}
RADIO_SetVfoState(VFO_STATE_NORMAL);
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
gInputBoxIndex = 0;
return;
}
if (gScanState != SCAN_OFF)
{
SCANNER_Stop();
gPttDebounceCounter = 0;
gPttIsPressed = false;
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
if (gFM_ScanState == FM_SCAN_OFF)
{
if (gCssScanMode == CSS_SCAN_MODE_OFF)
{
if (gScreenToDisplay == DISPLAY_MENU || gScreenToDisplay == DISPLAY_FM)
{
gRequestDisplayScreen = DISPLAY_MAIN;
gInputBoxIndex = 0;
gPttIsPressed = false;
gPttDebounceCounter = 0;
return;
}
if (gScreenToDisplay != DISPLAY_SCANNER)
{
if (gCurrentFunction == FUNCTION_TRANSMIT && gRTTECountdown == 0)
{
gInputBoxIndex = 0;
return;
}
gFlagPrepareTX = true;
if (gDTMF_InputMode)
{
if (gDTMF_InputIndex || gDTMF_PreviousIndex)
{
if (gDTMF_InputIndex == 0)
gDTMF_InputIndex = gDTMF_PreviousIndex;
gDTMF_InputBox[gDTMF_InputIndex] = 0;
if (gDTMF_InputIndex == 3)
gDTMF_CallMode = DTMF_CheckGroupCall(gDTMF_InputBox, 3);
else
gDTMF_CallMode = DTMF_CALL_MODE_DTMF;
sprintf(gDTMF_String, "%s", gDTMF_InputBox);
gDTMF_PreviousIndex = gDTMF_InputIndex;
gDTMF_ReplyState = DTMF_REPLY_ANI;
gDTMF_State = DTMF_STATE_0;
}
gRequestDisplayScreen = DISPLAY_MAIN;
gDTMF_InputMode = false;
gDTMF_InputIndex = 0;
return;
}
gRequestDisplayScreen = DISPLAY_MAIN;
gFlagPrepareTX = true;
gInputBoxIndex = 0;
return;
}
gRequestDisplayScreen = DISPLAY_MAIN;
gEeprom.CROSS_BAND_RX_TX = gBackupCROSS_BAND_RX_TX;
gUpdateStatus = true;
gFlagStopScan = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
}
else
{
MENU_StopCssScan();
gRequestDisplayScreen = DISPLAY_MENU;
}
}
else
{
FM_PlayAndUpdate();
gRequestDisplayScreen = DISPLAY_FM;
}
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
gPttWasPressed = true;
}

26
app/generic.h Normal file
View File

@ -0,0 +1,26 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_GENERIC_H
#define APP_GENERIC_H
#include <stdbool.h>
void GENERIC_Key_F(bool bKeyPressed, bool bKeyHeld);
void GENERIC_Key_PTT(bool bKeyPressed);
#endif

621
app/main.c Normal file
View File

@ -0,0 +1,621 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "app/action.h"
#include "app/app.h"
#include "app/fm.h"
#include "app/generic.h"
#include "app/main.h"
#include "app/scanner.h"
#include "audio.h"
#include "dtmf.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
uint8_t Band;
uint8_t Vfo = gEeprom.TX_CHANNEL;
if (bKeyHeld)
return;
if (!bKeyPressed)
return;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (!gWasFKeyPressed)
{
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_MAIN;
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
uint16_t Channel;
if (gInputBoxIndex != 3)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
gInputBoxIndex = 0;
Channel = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
if (!RADIO_CheckValidChannel(Channel, false, 0))
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gEeprom.MrChannel[Vfo] = (uint8_t)Channel;
gEeprom.ScreenChannel[Vfo] = (uint8_t)Channel;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
return;
}
#ifndef DISABLE_NOAA
if (IS_NOT_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE))
#endif
{
uint32_t Frequency;
if (gInputBoxIndex < 6)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
return;
}
gInputBoxIndex = 0;
NUMBER_Get(gInputBox, &Frequency);
if (gSetting_350EN || (4999990 < (Frequency - 35000000)))
{
unsigned int i;
for (i = 0; i < 7; i++)
{
if (Frequency <= gUpperLimitFrequencyBandTable[i] && (gLowerLimitFrequencyBandTable[i] <= Frequency))
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
if (gTxVfo->Band != i)
{
gTxVfo->Band = i;
gEeprom.ScreenChannel[Vfo] = i + FREQ_CHANNEL_FIRST;
gEeprom.FreqChannel[Vfo] = i + FREQ_CHANNEL_FIRST;
SETTINGS_SaveVfoIndices();
RADIO_ConfigureChannel(Vfo, 2);
}
Frequency += 75;
gTxVfo->ConfigRX.Frequency = FREQUENCY_FloorToStep(
Frequency,
gTxVfo->StepFrequency,
gLowerLimitFrequencyBandTable[gTxVfo->Band]);
gRequestSaveChannel = 1;
return;
}
}
}
}
#ifndef DISABLE_NOAA
else
{
uint8_t Channel;
if (gInputBoxIndex != 2)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
gInputBoxIndex = 0;
Channel = (gInputBox[0] * 10) + gInputBox[1];
if (Channel >= 1 && Channel <= 10)
{
Channel += NOAA_CHANNEL_FIRST;
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gEeprom.NoaaChannel[Vfo] = Channel;
gEeprom.ScreenChannel[Vfo] = Channel;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
return;
}
}
#endif
gRequestDisplayScreen = DISPLAY_MAIN;
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
gWasFKeyPressed = false;
gUpdateStatus = true;
switch (Key)
{
case KEY_0:
ACTION_FM();
break;
case KEY_1:
if (!IS_FREQ_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
gWasFKeyPressed = false;
gUpdateStatus = true;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
Band = gTxVfo->Band + 1;
if (gSetting_350EN || Band != BAND5_350MHz)
{
if (BAND7_470MHz < Band)
Band = BAND1_50MHz;
}
else
Band = BAND6_400MHz;
gTxVfo->Band = Band;
gEeprom.ScreenChannel[Vfo] = FREQ_CHANNEL_FIRST + Band;
gEeprom.FreqChannel[Vfo] = FREQ_CHANNEL_FIRST + Band;
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_MAIN;
break;
case KEY_2:
if (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_CHAN_A)
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_CHAN_B;
else
if (gEeprom.CROSS_BAND_RX_TX == CROSS_BAND_CHAN_B)
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_CHAN_A;
else
if (gEeprom.DUAL_WATCH == DUAL_WATCH_CHAN_A)
gEeprom.DUAL_WATCH = DUAL_WATCH_CHAN_B;
else
if (gEeprom.DUAL_WATCH == DUAL_WATCH_CHAN_B)
gEeprom.DUAL_WATCH = DUAL_WATCH_CHAN_A;
else
gEeprom.TX_CHANNEL = (Vfo == 0);
gRequestSaveSettings = 1;
gFlagReconfigureVfos = true;
gRequestDisplayScreen = DISPLAY_MAIN;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
break;
case KEY_3:
#ifndef DISABLE_NOAA
if (gEeprom.VFO_OPEN && IS_NOT_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE))
#else
if (gEeprom.VFO_OPEN)
#endif
{
uint8_t Channel;
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
gEeprom.ScreenChannel[Vfo] = gEeprom.FreqChannel[gEeprom.TX_CHANNEL];
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_FREQUENCY_MODE;
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
}
Channel = RADIO_FindNextChannel(gEeprom.MrChannel[gEeprom.TX_CHANNEL], 1, false, 0);
if (Channel != 0xFF)
{
gEeprom.ScreenChannel[Vfo] = Channel;
#ifndef DISABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, Channel + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
}
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
case KEY_4:
gWasFKeyPressed = false;
gUpdateStatus = true;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gFlagStartScan = true;
gScanSingleFrequency = false;
gBackupCROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
break;
case KEY_5:
// TODO: something wrong here !!
#ifndef DISABLE_NOAA
if (IS_NOT_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE))
gEeprom.ScreenChannel[Vfo] = gEeprom.NoaaChannel[gEeprom.TX_CHANNEL];
else
{
gEeprom.ScreenChannel[Vfo] = gEeprom.FreqChannel[gEeprom.TX_CHANNEL];
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_FREQUENCY_MODE;
#endif
}
#else
//gEeprom.ScreenChannel[Vfo] = gEeprom.NoaaChannel[gEeprom.TX_CHANNEL];
gEeprom.ScreenChannel[Vfo] = gEeprom.FreqChannel[gEeprom.TX_CHANNEL];
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_FREQUENCY_MODE;
#endif
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
case KEY_6:
ACTION_Power();
break;
case KEY_7:
ACTION_Vox();
break;
case KEY_8:
gTxVfo->FrequencyReverse = gTxVfo->FrequencyReverse == false;
gRequestSaveChannel = 1;
break;
case KEY_9:
if (RADIO_CheckValidChannel(gEeprom.CHAN_1_CALL, false, 0))
{
gEeprom.MrChannel[Vfo] = gEeprom.CHAN_1_CALL;
gEeprom.ScreenChannel[Vfo] = gEeprom.CHAN_1_CALL;
#ifndef DISABLE_VOICE
AUDIO_SetVoiceID(0, VOICE_ID_CHANNEL_MODE);
AUDIO_SetDigitVoice(1, gEeprom.CHAN_1_CALL + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
break;
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
default:
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gUpdateStatus = true;
gWasFKeyPressed = false;
break;
}
}
static void MAIN_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (!gFmRadioMode)
{
if (gScanState == SCAN_OFF)
{
if (gInputBoxIndex == 0)
return;
gInputBox[--gInputBoxIndex] = 10;
#ifndef DISABLE_VOICE
if (gInputBoxIndex == 0)
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
}
else
{
SCANNER_Stop();
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_SCANNING_STOP;
#endif
}
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
ACTION_FM();
}
}
static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
bool bFlag;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
bFlag = (gInputBoxIndex == 0);
gInputBoxIndex = 0;
if (bFlag)
{
gFlagRefreshSetting = true;
gRequestDisplayScreen = DISPLAY_MENU;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_MENU;
#endif
}
else
{
gRequestDisplayScreen = DISPLAY_MAIN;
}
}
}
static void MAIN_Key_STAR(bool bKeyPressed, bool bKeyHeld)
{
if (gInputBoxIndex)
{
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (bKeyHeld || !bKeyPressed)
{
if (bKeyHeld || bKeyPressed)
{
if (!bKeyHeld)
return;
if (!bKeyPressed)
return;
ACTION_Scan(false);
return;
}
#ifndef DISABLE_NOAA
if (gScanState == SCAN_OFF && IS_NOT_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE))
#else
if (gScanState == SCAN_OFF)
#endif
{
gDTMF_InputMode = true;
memcpy(gDTMF_InputBox, gDTMF_String, 15);
gDTMF_InputIndex = 0;
gRequestDisplayScreen = DISPLAY_MAIN;
return;
}
}
else
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
if (!gWasFKeyPressed)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
return;
}
gWasFKeyPressed = false;
gUpdateStatus = true;
#ifndef DISABLE_NOAA
if (IS_NOT_NOAA_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
gFlagStartScan = true;
gScanSingleFrequency = true;
gBackupCROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
}
else
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
#else
gFlagStartScan = true;
gScanSingleFrequency = true;
gBackupCROSS_BAND_RX_TX = gEeprom.CROSS_BAND_RX_TX;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
#endif
gPttWasReleased = true;
}
}
static void MAIN_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
{
uint8_t Channel = gEeprom.ScreenChannel[gEeprom.TX_CHANNEL];
if (bKeyHeld || !bKeyPressed)
{
if (gInputBoxIndex)
return;
if (!bKeyPressed)
{
if (!bKeyHeld)
return;
if (IS_FREQ_CHANNEL(Channel))
return;
#ifndef DISABLE_VOICE
AUDIO_SetDigitVoice(0, gTxVfo->CHANNEL_SAVE + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
return;
}
}
else
{
if (gInputBoxIndex)
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
if (gScanState == SCAN_OFF)
{
#ifndef DISABLE_NOAA
if (IS_NOT_NOAA_CHANNEL(Channel))
#endif
{
uint8_t Next;
if (IS_FREQ_CHANNEL(Channel))
{
APP_SetFrequencyByStep(gTxVfo, Direction);
gRequestSaveChannel = 1;
return;
}
Next = RADIO_FindNextChannel(Channel + Direction, Direction, false, 0);
if (Next == 0xFF)
return;
if (Channel == Next)
return;
gEeprom.MrChannel[gEeprom.TX_CHANNEL] = Next;
gEeprom.ScreenChannel[gEeprom.TX_CHANNEL] = Next;
if (!bKeyHeld)
{
#ifndef DISABLE_VOICE
AUDIO_SetDigitVoice(0, Next + 1);
gAnotherVoiceID = (VOICE_ID_t)0xFE;
#endif
}
}
#ifndef DISABLE_NOAA
else
{
Channel = NOAA_CHANNEL_FIRST + NUMBER_AddWithWraparound(gEeprom.ScreenChannel[gEeprom.TX_CHANNEL] - NOAA_CHANNEL_FIRST, Direction, 0, 9);
gEeprom.NoaaChannel[gEeprom.TX_CHANNEL] = Channel;
gEeprom.ScreenChannel[gEeprom.TX_CHANNEL] = Channel;
}
#endif
gRequestSaveVFO = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
return;
}
CHANNEL_Next(false, Direction);
gPttWasReleased = true;
}
void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (gFmRadioMode && Key != KEY_PTT && Key != KEY_EXIT)
{
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (gDTMF_InputMode && !bKeyHeld && bKeyPressed)
{
const char Character = DTMF_GetCharacter(Key);
if (Character != 0xFF)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
DTMF_Append(Character);
gRequestDisplayScreen = DISPLAY_MAIN;
gPttWasReleased = true;
return;
}
}
// TODO: ???
if (KEY_PTT < Key)
{
Key = KEY_SIDE2;
}
switch (Key)
{
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
MAIN_Key_DIGITS(Key, bKeyPressed, bKeyHeld);
break;
case KEY_MENU:
MAIN_Key_MENU(bKeyPressed, bKeyHeld);
break;
case KEY_UP:
MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1);
break;
case KEY_DOWN:
MAIN_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1);
break;
case KEY_EXIT:
MAIN_Key_EXIT(bKeyPressed, bKeyHeld);
break;
case KEY_STAR:
MAIN_Key_STAR(bKeyPressed, bKeyHeld);
break;
case KEY_F:
GENERIC_Key_F(bKeyPressed, bKeyHeld);
break;
case KEY_PTT:
GENERIC_Key_PTT(bKeyPressed);
break;
default:
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
}

25
app/main.h Normal file
View File

@ -0,0 +1,25 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_MAIN_H
#define APP_MAIN_H
#include "driver/keyboard.h"
void MAIN_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
#endif

1295
app/menu.c Normal file

File diff suppressed because it is too large Load Diff

32
app/menu.h Normal file
View File

@ -0,0 +1,32 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_MENU_H
#define APP_MENU_H
#include "driver/keyboard.h"
int MENU_GetLimits(uint8_t Cursor, uint8_t *pMin, uint8_t *pMax);
void MENU_AcceptSetting(void);
void MENU_SelectNextCode(void);
void MENU_ShowCurrentSetting(void);
void MENU_StartCssScan(int8_t Direction);
void MENU_StopCssScan(void);
void MENU_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
#endif

445
app/scanner.c Normal file
View File

@ -0,0 +1,445 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "app/generic.h"
#include "app/scanner.h"
#include "audio.h"
#include "driver/bk4819.h"
#include "frequencies.h"
#include "misc.h"
#include "radio.h"
#include "settings.h"
#include "ui/inputbox.h"
#include "ui/ui.h"
DCS_CodeType_t gScanCssResultType;
uint8_t gScanCssResultCode;
bool gFlagStartScan;
bool gFlagStopScan;
bool gScanSingleFrequency;
uint8_t gScannerEditState;
uint8_t gScanChannel;
uint32_t gScanFrequency;
bool gScanPauseMode;
SCAN_CssState_t gScanCssState;
volatile bool gScheduleScanListen = true;
volatile uint16_t ScanPauseDelayIn10msec;
uint8_t gScanProgressIndicator;
uint8_t gScanHitCount;
bool gScanUseCssResult;
uint8_t gScanState;
bool bScanKeepFrequency;
static void SCANNER_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
if (gScannerEditState == 1)
{
uint16_t Channel;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
INPUTBOX_Append(Key);
gRequestDisplayScreen = DISPLAY_SCANNER;
if (gInputBoxIndex < 3)
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
return;
}
gInputBoxIndex = 0;
Channel = ((gInputBox[0] * 100) + (gInputBox[1] * 10) + gInputBox[2]) - 1;
if (IS_MR_CHANNEL(Channel))
{
#ifndef DISABLE_VOICE
gAnotherVoiceID = (VOICE_ID_t)Key;
#endif
gShowChPrefix = RADIO_CheckValidChannel(Channel, false, 0);
gScanChannel = (uint8_t)Channel;
return;
}
}
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
}
static void SCANNER_Key_EXIT(bool bKeyPressed, bool bKeyHeld)
{
if (!bKeyHeld && bKeyPressed)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
switch (gScannerEditState)
{
case 0:
gRequestDisplayScreen = DISPLAY_MAIN;
gEeprom.CROSS_BAND_RX_TX = gBackupCROSS_BAND_RX_TX;
gUpdateStatus = true;
gFlagStopScan = true;
gVfoConfigureMode = VFO_CONFIGURE_RELOAD;
gFlagResetVfos = true;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
break;
case 1:
if (gInputBoxIndex)
{
gInputBox[--gInputBoxIndex] = 10;
gRequestDisplayScreen = DISPLAY_SCANNER;
break;
}
// Fallthrough
case 2:
gScannerEditState = 0;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_CANCEL;
#endif
gRequestDisplayScreen = DISPLAY_SCANNER;
break;
}
}
}
static void SCANNER_Key_MENU(bool bKeyPressed, bool bKeyHeld)
{
uint8_t Channel;
if (bKeyHeld)
return;
if (!bKeyPressed)
return;
if (gScanCssState == SCAN_CSS_STATE_OFF && !gScanSingleFrequency)
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
if (gScanCssState == SCAN_CSS_STATE_SCANNING)
{
if (gScanSingleFrequency)
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
}
if (gScanCssState == SCAN_CSS_STATE_FAILED)
{
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
return;
}
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
switch (gScannerEditState)
{
case 0:
if (!gScanSingleFrequency)
{
uint32_t Freq250;
uint32_t Freq625;
int16_t Delta250;
int16_t Delta625;
Freq250 = FREQUENCY_FloorToStep(gScanFrequency, 250, 0);
Freq625 = FREQUENCY_FloorToStep(gScanFrequency, 625, 0);
Delta250 = (short)gScanFrequency - (short)Freq250;
if (125 < Delta250)
{
Delta250 = 250 - Delta250;
Freq250 += 250;
}
Delta625 = (short)gScanFrequency - (short)Freq625;
if (312 < Delta625)
{
Delta625 = 625 - Delta625;
Freq625 += 625;
}
if (Delta625 < Delta250)
{
gStepSetting = STEP_6_25kHz;
gScanFrequency = Freq625;
}
else
{
gStepSetting = STEP_2_5kHz;
gScanFrequency = Freq250;
}
}
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
gScannerEditState = 1;
gScanChannel = gTxVfo->CHANNEL_SAVE;
gShowChPrefix = RADIO_CheckValidChannel(gTxVfo->CHANNEL_SAVE, false, 0);
}
else
{
gScannerEditState = 2;
}
gScanCssState = SCAN_CSS_STATE_FOUND;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_MEMORY_CHANNEL;
#endif
gRequestDisplayScreen = DISPLAY_SCANNER;
break;
case 1:
if (gInputBoxIndex == 0)
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gRequestDisplayScreen = DISPLAY_SCANNER;
gScannerEditState = 2;
}
break;
case 2:
if (!gScanSingleFrequency)
{
RADIO_InitInfo(gTxVfo, gTxVfo->CHANNEL_SAVE, FREQUENCY_GetBand(gScanFrequency), gScanFrequency);
if (gScanUseCssResult)
{
gTxVfo->ConfigRX.CodeType = gScanCssResultType;
gTxVfo->ConfigRX.Code = gScanCssResultCode;
}
gTxVfo->ConfigTX = gTxVfo->ConfigRX;
gTxVfo->STEP_SETTING = gStepSetting;
}
else
{
RADIO_ConfigureChannel(0, 2);
RADIO_ConfigureChannel(1, 2);
gTxVfo->ConfigRX.CodeType = gScanCssResultType;
gTxVfo->ConfigRX.Code = gScanCssResultCode;
gTxVfo->ConfigTX.CodeType = gScanCssResultType;
gTxVfo->ConfigTX.Code = gScanCssResultCode;
}
if (IS_MR_CHANNEL(gTxVfo->CHANNEL_SAVE))
{
Channel = gScanChannel;
gEeprom.MrChannel[gEeprom.TX_CHANNEL] = Channel;
}
else
{
Channel = gTxVfo->Band + FREQ_CHANNEL_FIRST;
gEeprom.FreqChannel[gEeprom.TX_CHANNEL] = Channel;
}
gTxVfo->CHANNEL_SAVE = Channel;
gEeprom.ScreenChannel[gEeprom.TX_CHANNEL] = Channel;
#ifndef DISABLE_VOICE
gAnotherVoiceID = VOICE_ID_CONFIRM;
#endif
gRequestDisplayScreen = DISPLAY_SCANNER;
gRequestSaveChannel = 2;
gScannerEditState = 0;
break;
default:
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
break;
}
}
static void SCANNER_Key_STAR(bool bKeyPressed, bool bKeyHeld)
{
if ((!bKeyHeld) && (bKeyPressed))
{
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
gFlagStartScan = true;
}
return;
}
static void SCANNER_Key_UP_DOWN(bool bKeyPressed, bool pKeyHeld, int8_t Direction)
{
if (pKeyHeld)
{
if (!bKeyPressed)
return;
}
else
{
if (!bKeyPressed)
return;
gInputBoxIndex = 0;
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
}
if (gScannerEditState == 1)
{
gScanChannel = NUMBER_AddWithWraparound(gScanChannel, Direction, 0, 199);
gShowChPrefix = RADIO_CheckValidChannel(gScanChannel, false, 0);
gRequestDisplayScreen = DISPLAY_SCANNER;
}
else
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
}
void SCANNER_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
{
switch (Key)
{
case KEY_0:
case KEY_1:
case KEY_2:
case KEY_3:
case KEY_4:
case KEY_5:
case KEY_6:
case KEY_7:
case KEY_8:
case KEY_9:
SCANNER_Key_DIGITS(Key, bKeyPressed, bKeyHeld);
break;
case KEY_MENU:
SCANNER_Key_MENU(bKeyPressed, bKeyHeld);
break;
case KEY_UP:
SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, 1);
break;
case KEY_DOWN:
SCANNER_Key_UP_DOWN(bKeyPressed, bKeyHeld, -1);
break;
case KEY_EXIT:
SCANNER_Key_EXIT(bKeyPressed, bKeyHeld);
break;
case KEY_STAR:
SCANNER_Key_STAR(bKeyPressed, bKeyHeld);
break;
case KEY_PTT:
GENERIC_Key_PTT(bKeyPressed);
break;
default:
if (!bKeyHeld && bKeyPressed)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
break;
}
}
void SCANNER_Start(void)
{
uint8_t BackupStep;
uint16_t BackupFrequency;
BK4819_StopScan();
RADIO_SelectVfos();
#ifndef DISABLE_NOAA
if (IS_NOAA_CHANNEL(gRxVfo->CHANNEL_SAVE))
gRxVfo->CHANNEL_SAVE = FREQ_CHANNEL_FIRST + 5;
#endif
BackupStep = gRxVfo->STEP_SETTING;
BackupFrequency = gRxVfo->StepFrequency;
RADIO_InitInfo(gRxVfo, gRxVfo->CHANNEL_SAVE, gRxVfo->Band, gRxVfo->pRX->Frequency);
gRxVfo->STEP_SETTING = BackupStep;
gRxVfo->StepFrequency = BackupFrequency;
RADIO_SetupRegisters(true);
#ifndef DISABLE_NOAA
gIsNoaaMode = false;
#endif
if (gScanSingleFrequency)
{
gScanCssState = SCAN_CSS_STATE_SCANNING;
gScanFrequency = gRxVfo->pRX->Frequency;
gStepSetting = gRxVfo->STEP_SETTING;
BK4819_PickRXFilterPathBasedOnFrequency(gScanFrequency);
BK4819_SetScanFrequency(gScanFrequency);
}
else
{
gScanCssState = SCAN_CSS_STATE_OFF;
gScanFrequency = 0xFFFFFFFF;
BK4819_PickRXFilterPathBasedOnFrequency(0xFFFFFFFF);
BK4819_EnableFrequencyScan();
}
gScanDelay = 21;
gScanCssResultCode = 0xFF;
gScanCssResultType = 0xFF;
gScanHitCount = 0;
gScanUseCssResult = false;
gDTMF_RequestPending = false;
g_CxCSS_TAIL_Found = false;
g_CDCSS_Lost = false;
gCDCSSCodeType = 0;
g_CTCSS_Lost = false;
g_VOX_Lost = false;
g_SquelchLost = false;
gScannerEditState = 0;
gScanProgressIndicator = 0;
}
void SCANNER_Stop(void)
{
uint8_t Previous = gRestoreMrChannel;
gScanState = SCAN_OFF;
if (!bScanKeepFrequency)
{
if (IS_MR_CHANNEL(gNextMrChannel))
{
gEeprom.MrChannel[gEeprom.RX_CHANNEL] = gRestoreMrChannel;
gEeprom.ScreenChannel[gEeprom.RX_CHANNEL] = Previous;
RADIO_ConfigureChannel(gEeprom.RX_CHANNEL, 2);
}
else
{
gRxVfo->ConfigRX.Frequency = gRestoreFrequency;
RADIO_ApplyOffset(gRxVfo);
RADIO_ConfigureSquelchAndOutputPower(gRxVfo);
}
RADIO_SetupRegisters(true);
gUpdateDisplay = true;
return;
}
if (!IS_MR_CHANNEL(gRxVfo->CHANNEL_SAVE))
{
RADIO_ApplyOffset(gRxVfo);
RADIO_ConfigureSquelchAndOutputPower(gRxVfo);
SETTINGS_SaveChannel(gRxVfo->CHANNEL_SAVE, gEeprom.RX_CHANNEL, gRxVfo, 1);
return;
}
SETTINGS_SaveVfoIndices();
}

59
app/scanner.h Normal file
View File

@ -0,0 +1,59 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_SCANNER_H
#define APP_SCANNER_H
#include "dcs.h"
#include "driver/keyboard.h"
enum SCAN_CssState_t {
SCAN_CSS_STATE_OFF = 0U,
SCAN_CSS_STATE_SCANNING = 1U,
SCAN_CSS_STATE_FOUND = 2U,
SCAN_CSS_STATE_FAILED = 3U,
};
typedef enum SCAN_CssState_t SCAN_CssState_t;
enum {
SCAN_OFF = 0U,
};
extern DCS_CodeType_t gScanCssResultType;
extern uint8_t gScanCssResultCode;
extern bool gFlagStartScan;
extern bool gFlagStopScan;
extern bool gScanSingleFrequency;
extern uint8_t gScannerEditState;
extern uint8_t gScanChannel;
extern uint32_t gScanFrequency;
extern bool gScanPauseMode;
extern SCAN_CssState_t gScanCssState;
extern volatile bool gScheduleScanListen;
extern volatile uint16_t ScanPauseDelayIn10msec;
extern uint8_t gScanProgressIndicator;
extern uint8_t gScanHitCount;
extern bool gScanUseCssResult;
extern uint8_t gScanState;
extern bool bScanKeepFrequency;
void SCANNER_ProcessKeys(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
void SCANNER_Start(void);
void SCANNER_Stop(void);
#endif

520
app/uart.c Normal file
View File

@ -0,0 +1,520 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string.h>
#include "app/fm.h"
#include "app/uart.h"
#include "board.h"
#include "bsp/dp32g030/dma.h"
#include "bsp/dp32g030/gpio.h"
#include "driver/aes.h"
#include "driver/bk4819.h"
#include "driver/crc.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "functions.h"
#include "misc.h"
#include "settings.h"
#include "sram-overlay.h"
#include "version.h"
#define DMA_INDEX(x, y) (((x) + (y)) % sizeof(UART_DMA_Buffer))
typedef struct {
uint16_t ID;
uint16_t Size;
} Header_t;
typedef struct {
uint8_t Padding[2];
uint16_t ID;
} Footer_t;
typedef struct {
Header_t Header;
uint32_t Timestamp;
} CMD_0514_t;
typedef struct {
Header_t Header;
struct {
char Version[16];
bool bHasCustomAesKey;
bool bIsInLockScreen;
uint8_t Padding[2];
uint32_t Challenge[4];
} Data;
} REPLY_0514_t;
typedef struct {
Header_t Header;
uint16_t Offset;
uint8_t Size;
uint8_t Padding;
uint32_t Timestamp;
} CMD_051B_t;
typedef struct {
Header_t Header;
struct {
uint16_t Offset;
uint8_t Size;
uint8_t Padding;
uint8_t Data[128];
} Data;
} REPLY_051B_t;
typedef struct {
Header_t Header;
uint16_t Offset;
uint8_t Size;
bool bAllowPassword;
uint32_t Timestamp;
uint8_t Data[0];
} CMD_051D_t;
typedef struct {
Header_t Header;
struct {
uint16_t Offset;
} Data;
} REPLY_051D_t;
typedef struct {
Header_t Header;
struct {
uint16_t RSSI;
uint8_t ExNoiseIndicator;
uint8_t GlitchIndicator;
} Data;
} REPLY_0527_t;
typedef struct {
Header_t Header;
struct {
uint16_t Voltage;
uint16_t Current;
} Data;
} REPLY_0529_t;
typedef struct {
Header_t Header;
uint32_t Response[4];
} CMD_052D_t;
typedef struct {
Header_t Header;
struct {
bool bIsLocked;
uint8_t Padding[3];
} Data;
} REPLY_052D_t;
typedef struct {
Header_t Header;
uint32_t Timestamp;
} CMD_052F_t;
static const uint8_t Obfuscation[16] = { 0x16, 0x6C, 0x14, 0xE6, 0x2E, 0x91, 0x0D, 0x40, 0x21, 0x35, 0xD5, 0x40, 0x13, 0x03, 0xE9, 0x80 };
static union {
uint8_t Buffer[256];
struct {
Header_t Header;
uint8_t Data[252];
};
} UART_Command;
static uint32_t Timestamp;
static uint16_t gUART_WriteIndex;
static bool bIsEncrypted = true;
static void SendReply(void *pReply, uint16_t Size)
{
Header_t Header;
Footer_t Footer;
uint8_t *pBytes;
uint16_t i;
if (bIsEncrypted) {
pBytes = (uint8_t *)pReply;
for (i = 0; i < Size; i++) {
pBytes[i] ^= Obfuscation[i % 16];
}
}
Header.ID = 0xCDAB;
Header.Size = Size;
UART_Send(&Header, sizeof(Header));
UART_Send(pReply, Size);
if (bIsEncrypted) {
Footer.Padding[0] = Obfuscation[(Size + 0) % 16] ^ 0xFF;
Footer.Padding[1] = Obfuscation[(Size + 1) % 16] ^ 0xFF;
} else {
Footer.Padding[0] = 0xFF;
Footer.Padding[1] = 0xFF;
}
Footer.ID = 0xBADC;
UART_Send(&Footer, sizeof(Footer));
}
static void SendVersion(void)
{
REPLY_0514_t Reply;
Reply.Header.ID = 0x0515;
Reply.Header.Size = sizeof(Reply.Data);
strcpy(Reply.Data.Version, Version);
Reply.Data.bHasCustomAesKey = bHasCustomAesKey;
Reply.Data.bIsInLockScreen = bIsInLockScreen;
Reply.Data.Challenge[0] = gChallenge[0];
Reply.Data.Challenge[1] = gChallenge[1];
Reply.Data.Challenge[2] = gChallenge[2];
Reply.Data.Challenge[3] = gChallenge[3];
SendReply(&Reply, sizeof(Reply));
}
static bool IsBadChallenge(const uint32_t *pKey, const uint32_t *pIn, const uint32_t *pResponse)
{
uint8_t i;
uint32_t IV[4];
IV[0] = 0;
IV[1] = 0;
IV[2] = 0;
IV[3] = 0;
AES_Encrypt(pKey, IV, pIn, IV, true);
for (i = 0; i < 4; i++) {
if (IV[i] != pResponse[i]) {
return true;
}
}
return false;
}
static void CMD_0514(const uint8_t *pBuffer)
{
const CMD_0514_t *pCmd = (const CMD_0514_t *)pBuffer;
Timestamp = pCmd->Timestamp;
gFmRadioCountdown = 4;
GPIO_ClearBit(&GPIOB->DATA, GPIOB_PIN_BACKLIGHT);
SendVersion();
}
static void CMD_051B(const uint8_t *pBuffer)
{
const CMD_051B_t *pCmd = (const CMD_051B_t *)pBuffer;
REPLY_051B_t Reply;
bool bLocked = false;
if (pCmd->Timestamp != Timestamp) {
return;
}
gFmRadioCountdown = 4;
memset(&Reply, 0, sizeof(Reply));
Reply.Header.ID = 0x051C;
Reply.Header.Size = pCmd->Size + 4;
Reply.Data.Offset = pCmd->Offset;
Reply.Data.Size = pCmd->Size;
if (bHasCustomAesKey) {
bLocked = gIsLocked;
}
if (!bLocked) {
EEPROM_ReadBuffer(pCmd->Offset, Reply.Data.Data, pCmd->Size);
}
SendReply(&Reply, pCmd->Size + 8);
}
static void CMD_051D(const uint8_t *pBuffer)
{
const CMD_051D_t *pCmd = (const CMD_051D_t *)pBuffer;
REPLY_051D_t Reply;
bool bReloadEeprom;
bool bIsLocked;
if (pCmd->Timestamp != Timestamp) {
return;
}
bReloadEeprom = false;
gFmRadioCountdown = 4;
Reply.Header.ID = 0x051E;
Reply.Header.Size = sizeof(Reply.Data);
Reply.Data.Offset = pCmd->Offset;
bIsLocked = bHasCustomAesKey;
if (bHasCustomAesKey) {
bIsLocked = gIsLocked;
}
if (!bIsLocked) {
uint16_t i;
for (i = 0; i < (pCmd->Size / 8U); i++) {
uint16_t Offset = pCmd->Offset + (i * 8U);
if (Offset >= 0x0F30 && Offset < 0x0F40) {
if (!gIsLocked) {
bReloadEeprom = true;
}
}
if ((Offset < 0x0E98 || Offset >= 0x0EA0) || !bIsInLockScreen || pCmd->bAllowPassword) {
EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U]);
}
}
if (bReloadEeprom) {
BOARD_EEPROM_Init();
}
}
SendReply(&Reply, sizeof(Reply));
}
static void CMD_0527(void)
{
REPLY_0527_t Reply;
Reply.Header.ID = 0x0528;
Reply.Header.Size = sizeof(Reply.Data);
Reply.Data.RSSI = BK4819_ReadRegister(BK4819_REG_67) & 0x01FF;
Reply.Data.ExNoiseIndicator = BK4819_ReadRegister(BK4819_REG_65) & 0x007F;
Reply.Data.GlitchIndicator = BK4819_ReadRegister(BK4819_REG_63);
SendReply(&Reply, sizeof(Reply));
}
static void CMD_0529(void)
{
REPLY_0529_t Reply;
Reply.Header.ID = 0x52A;
Reply.Header.Size = sizeof(Reply.Data);
// Original doesn't actually send current!
BOARD_ADC_GetBatteryInfo(&Reply.Data.Voltage, &Reply.Data.Current);
SendReply(&Reply, sizeof(Reply));
}
static void CMD_052D(const uint8_t *pBuffer)
{
const CMD_052D_t *pCmd = (const CMD_052D_t *)pBuffer;
REPLY_052D_t Reply;
bool bIsLocked;
gFmRadioCountdown = 4;
Reply.Header.ID = 0x052E;
Reply.Header.Size = sizeof(Reply.Data);
bIsLocked = bHasCustomAesKey;
if (!bIsLocked) {
bIsLocked = IsBadChallenge(gCustomAesKey, gChallenge, pCmd->Response);
}
if (!bIsLocked) {
bIsLocked = IsBadChallenge(gDefaultAesKey, gChallenge, pCmd->Response);
if (bIsLocked) {
gTryCount++;
}
}
if (gTryCount < 3) {
if (!bIsLocked) {
gTryCount = 0;
}
} else {
gTryCount = 3;
bIsLocked = true;
}
gIsLocked = bIsLocked;
Reply.Data.bIsLocked = bIsLocked;
SendReply(&Reply, sizeof(Reply));
}
static void CMD_052F(const uint8_t *pBuffer)
{
const CMD_052F_t *pCmd = (const CMD_052F_t *)pBuffer;
gEeprom.DUAL_WATCH = DUAL_WATCH_OFF;
gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
gEeprom.RX_CHANNEL = 0;
gEeprom.DTMF_SIDE_TONE = false;
gEeprom.VfoInfo[0].FrequencyReverse = false;
gEeprom.VfoInfo[0].pRX = &gEeprom.VfoInfo[0].ConfigRX;
gEeprom.VfoInfo[0].pTX = &gEeprom.VfoInfo[0].ConfigTX;
gEeprom.VfoInfo[0].FREQUENCY_DEVIATION_SETTING = FREQUENCY_DEVIATION_OFF;
gEeprom.VfoInfo[0].DTMF_PTT_ID_TX_MODE = PTT_ID_OFF;
gEeprom.VfoInfo[0].DTMF_DECODING_ENABLE = false;
#ifndef DISABLE_NOAA
gIsNoaaMode = false;
#endif
if (gCurrentFunction == FUNCTION_POWER_SAVE)
FUNCTION_Select(FUNCTION_FOREGROUND);
Timestamp = pCmd->Timestamp;
GPIO_ClearBit(&GPIOB->DATA, GPIOB_PIN_BACKLIGHT);
SendVersion();
}
bool UART_IsCommandAvailable(void)
{
uint16_t DmaLength;
uint16_t CommandLength;
uint16_t Index;
uint16_t TailIndex;
uint16_t Size;
uint16_t CRC;
uint16_t i;
DmaLength = DMA_CH0->ST & 0xFFFU;
while (1)
{
if (gUART_WriteIndex == DmaLength)
return false;
while (gUART_WriteIndex != DmaLength && UART_DMA_Buffer[gUART_WriteIndex] != 0xABU)
gUART_WriteIndex = DMA_INDEX(gUART_WriteIndex, 1);
if (gUART_WriteIndex == DmaLength)
return false;
if (gUART_WriteIndex < DmaLength)
CommandLength = DmaLength - gUART_WriteIndex;
else
CommandLength = (DmaLength + sizeof(UART_DMA_Buffer)) - gUART_WriteIndex;
if (CommandLength < 8)
return 0;
if (UART_DMA_Buffer[DMA_INDEX(gUART_WriteIndex, 1)] == 0xCD)
break;
gUART_WriteIndex = DMA_INDEX(gUART_WriteIndex, 1);
}
Index = DMA_INDEX(gUART_WriteIndex, 2);
Size = (UART_DMA_Buffer[DMA_INDEX(Index, 1)] << 8) | UART_DMA_Buffer[Index];
if ((Size + 8) > sizeof(UART_DMA_Buffer))
{
gUART_WriteIndex = DmaLength;
return false;
}
if (CommandLength < (Size + 8))
return false;
Index = DMA_INDEX(Index, 2);
TailIndex = DMA_INDEX(Index, Size + 2);
if (UART_DMA_Buffer[TailIndex] != 0xDC || UART_DMA_Buffer[DMA_INDEX(TailIndex, 1)] != 0xBA)
{
gUART_WriteIndex = DmaLength;
return false;
}
if (TailIndex < Index)
{
const uint16_t ChunkSize = sizeof(UART_DMA_Buffer) - Index;
memcpy(UART_Command.Buffer, UART_DMA_Buffer + Index, ChunkSize);
memcpy(UART_Command.Buffer + ChunkSize, UART_DMA_Buffer, TailIndex);
}
else
memcpy(UART_Command.Buffer, UART_DMA_Buffer + Index, TailIndex - Index);
TailIndex = DMA_INDEX(TailIndex, 2);
if (TailIndex < gUART_WriteIndex)
{
memset(UART_DMA_Buffer + gUART_WriteIndex, 0, sizeof(UART_DMA_Buffer) - gUART_WriteIndex);
memset(UART_DMA_Buffer, 0, TailIndex);
}
else
memset(UART_DMA_Buffer + gUART_WriteIndex, 0, TailIndex - gUART_WriteIndex);
gUART_WriteIndex = TailIndex;
if (UART_Command.Header.ID == 0x0514)
bIsEncrypted = false;
if (UART_Command.Header.ID == 0x6902)
bIsEncrypted = true;
if (bIsEncrypted)
for (i = 0; i < Size + 2; i++)
UART_Command.Buffer[i] ^= Obfuscation[i % 16];
CRC = UART_Command.Buffer[Size] | (UART_Command.Buffer[Size + 1] << 8);
return (CRC_Calculate(UART_Command.Buffer, Size) != CRC) ? false : true;
}
void UART_HandleCommand(void)
{
switch (UART_Command.Header.ID)
{
case 0x0514:
CMD_0514(UART_Command.Buffer);
break;
case 0x051B:
CMD_051B(UART_Command.Buffer);
break;
case 0x051D:
CMD_051D(UART_Command.Buffer);
break;
case 0x051F: // Not implementing non-authentic command
break;
case 0x0521: // Not implementing non-authentic command
break;
case 0x0527:
CMD_0527();
break;
case 0x0529:
CMD_0529();
break;
case 0x052D:
CMD_052D(UART_Command.Buffer);
break;
case 0x052F:
CMD_052F(UART_Command.Buffer);
break;
case 0x05DD:
overlay_FLASH_RebootToBootloader();
break;
}
}

26
app/uart.h Normal file
View File

@ -0,0 +1,26 @@
/* Copyright 2023 Dual Tachyon
* https://github.com/DualTachyon
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef APP_UART_H
#define APP_UART_H
#include <stdbool.h>
bool UART_IsCommandAvailable(void);
void UART_HandleCommand(void);
#endif