/* 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. */ #ifdef ENABLE_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]; bool AIRCOPY_SendMessage(void) { static uint8_t gAircopySendCountdown = 1; if (gAircopyState != AIRCOPY_TRANSFER) { return 1; } if (--gAircopySendCountdown) { return 1; } 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 (unsigned int 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_GPIO1_PIN29_PA_ENABLE, false); gAircopySendCountdown = 30; return 0; } void AIRCOPY_StorePacket(void) { if (gFSKWriteIndex < 36) { return; } gFSKWriteIndex = 0; gUpdateDisplay = true; uint16_t 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) { gErrorsDuringAirCopy++; return; } for (unsigned int i = 0; i < 34; i++) { g_FSK_Buffer[i + 1] ^= Obfuscation[i % 8]; } uint16_t CRC = CRC_Calculate(&g_FSK_Buffer[1], 2 + 64); if (g_FSK_Buffer[34] != CRC) { gErrorsDuringAirCopy++; return; } uint16_t Offset = g_FSK_Buffer[1]; if (Offset >= 0x1E00) { gErrorsDuringAirCopy++; return; } const uint16_t *pData = &g_FSK_Buffer[2]; for (unsigned int i = 0; i < 8; i++) { EEPROM_WriteBuffer(Offset, pData); pData += 4; Offset += 8; } if (Offset == 0x1E00) { gAircopyState = AIRCOPY_COMPLETE; } gAirCopyBlockNumber++; } static void AIRCOPY_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld) { if (bKeyHeld || !bKeyPressed) { return; } INPUTBOX_Append(Key); gRequestDisplayScreen = DISPLAY_AIRCOPY; if (gInputBoxIndex < 6) { #ifdef ENABLE_VOICE gAnotherVoiceID = (VOICE_ID_t)Key; #endif return; } gInputBoxIndex = 0; uint32_t Frequency = StrToUL(INPUTBOX_GetAscii()) * 100; for (unsigned int i = 0; i < BAND_N_ELEM; i++) { if (Frequency < frequencyBandTable[i].lower || Frequency >= frequencyBandTable[i].upper) { continue; } if (TX_freq_check(Frequency)) { continue; } #ifdef ENABLE_VOICE gAnotherVoiceID = (VOICE_ID_t)Key; #endif Frequency = FREQUENCY_RoundToStep(Frequency, gRxVfo->StepFrequency); gRxVfo->Band = i; gRxVfo->freq_config_RX.Frequency = Frequency; gRxVfo->freq_config_TX.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) { return; } if (gInputBoxIndex == 0) { gFSKWriteIndex = 0; gAirCopyBlockNumber = 0; gInputBoxIndex = 0; gErrorsDuringAirCopy = 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) { return; } gFSKWriteIndex = 0; gAirCopyBlockNumber = 0; gInputBoxIndex = 0; gAirCopyIsSendMode = 1; g_FSK_Buffer[0] = 0xABCD; g_FSK_Buffer[1] = 0; g_FSK_Buffer[35] = 0xDCBA; 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