1950 lines
59 KiB
C
1950 lines
59 KiB
C
/*
|
|
* Copyright (C) 2023-2025 Roger Clark, VK3KYY / G4KYF
|
|
* Daniel Caujolle-Bert, F1RMB
|
|
*
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
|
|
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#if !defined(PLATFORM_GD77S)
|
|
|
|
#include "main.h"
|
|
#include "functions/settings.h"
|
|
#include "user_interface/uiLocalisation.h"
|
|
#include "user_interface/uiUtilities.h"
|
|
#include "interfaces/clockManager.h"
|
|
#include "functions/rxPowerSaving.h"
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
#include "fsl_ftm.h"
|
|
#else // CPU_MK22FN512VLL12
|
|
#if !defined(PLATFORM_MD9600)
|
|
#include "hardware/AT1846S.h"
|
|
#endif
|
|
#endif // CPU_MK22FN512VLL12
|
|
#include "functions/aprs.h"
|
|
#include "hardware/HR-C6000.h"
|
|
#include "functions/satellite.h"
|
|
#if defined(HAS_GPS)
|
|
#include "interfaces/gps.h"
|
|
#endif
|
|
#include "user_interface/menuSystem.h"
|
|
|
|
#define AX25_PACKET_BUFFER_SIZE 256U
|
|
#define SMART_BEACONING_SPEED_MIN 54U // more than 1km/h (0.5399568034557235 kn == 1km/h)
|
|
#define APRS_DESTINATION "APOG77" // MAX 6 char (excluding terminator)
|
|
#define APRS_CONFIG_SATELLITE 0U
|
|
#define APRS_CONFIG_CHANNEL 1U
|
|
#define APRS_ANTI_COLLISION_MS_MAX 2000U
|
|
#define APRS_USE_COURSETO_FOR_BEARING 1
|
|
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
// On MK22, isnan macro has a bug, circumvent it
|
|
#undef isnan
|
|
#define isnan(x) (__builtin_isnan(x))
|
|
|
|
// On MK22, round() doesn't exist, __builtin_round() is also not functionnal
|
|
// from newlib-cygwin:
|
|
// https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/s_round.c;hb=master
|
|
typedef union
|
|
{
|
|
double value;
|
|
struct
|
|
{
|
|
uint32_t lsw;
|
|
uint32_t msw;
|
|
} parts;
|
|
} ieee_double_shape_type;
|
|
|
|
#define EXTRACT_WORDS(ix0,ix1,d) \
|
|
do { \
|
|
ieee_double_shape_type ew_u; \
|
|
ew_u.value = (d); \
|
|
(ix0) = ew_u.parts.msw; \
|
|
(ix1) = ew_u.parts.lsw; \
|
|
} while (0)
|
|
|
|
#define INSERT_WORDS(d,ix0,ix1) \
|
|
do { \
|
|
ieee_double_shape_type iw_u; \
|
|
iw_u.parts.msw = (ix0); \
|
|
iw_u.parts.lsw = (ix1); \
|
|
(d) = iw_u.value; \
|
|
} while (0)
|
|
|
|
double round(double x)
|
|
{
|
|
/* Most significant word, least significant word. */
|
|
int32_t msw, exponent_less_1023;
|
|
uint32_t lsw;
|
|
|
|
EXTRACT_WORDS(msw, lsw, x);
|
|
|
|
/* Extract exponent field. */
|
|
exponent_less_1023 = ((msw & 0x7ff00000) >> 20) - 1023;
|
|
|
|
if (exponent_less_1023 < 20)
|
|
{
|
|
if (exponent_less_1023 < 0)
|
|
{
|
|
msw &= 0x80000000;
|
|
if (exponent_less_1023 == -1)
|
|
/* Result is +1.0 or -1.0. */
|
|
msw |= ((int32_t)1023 << 20);
|
|
lsw = 0;
|
|
}
|
|
else
|
|
{
|
|
uint32_t exponent_mask = 0x000fffff >> exponent_less_1023;
|
|
if ((msw & exponent_mask) == 0 && lsw == 0)
|
|
/* x in an integral value. */
|
|
return x;
|
|
|
|
msw += 0x00080000 >> exponent_less_1023;
|
|
msw &= ~exponent_mask;
|
|
lsw = 0;
|
|
}
|
|
}
|
|
else if (exponent_less_1023 > 51)
|
|
{
|
|
if (exponent_less_1023 == 1024)
|
|
/* x is NaN or infinite. */
|
|
return x + x;
|
|
else
|
|
return x;
|
|
}
|
|
else
|
|
{
|
|
uint32_t exponent_mask = 0xffffffff >> (exponent_less_1023 - 20);
|
|
uint32_t tmp;
|
|
|
|
if ((lsw & exponent_mask) == 0)
|
|
/* x is an integral value. */
|
|
return x;
|
|
|
|
tmp = lsw + (1 << (51 - exponent_less_1023));
|
|
if (tmp < lsw)
|
|
msw += 1;
|
|
lsw = tmp;
|
|
|
|
lsw &= ~exponent_mask;
|
|
}
|
|
INSERT_WORDS(x, msw, lsw);
|
|
|
|
return x;
|
|
}
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
uint8_t packetBuffer[AX25_PACKET_BUFFER_SIZE];
|
|
uint16_t packetBufferBitPosition;
|
|
uint16_t bitStuffingCounter;
|
|
uint16_t crc;
|
|
bool currentBitNRZI;
|
|
bool baudIs300;
|
|
} AX25Encoder_t;
|
|
|
|
// Beaconing
|
|
|
|
typedef struct
|
|
{
|
|
double latitude;
|
|
double longitude;
|
|
} aprsBeaconingCoordinates_t;
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t time; // ms
|
|
uint16_t bearing; // hundredth degree
|
|
uint16_t speed; // hundredth of knots
|
|
aprsBeaconingCoordinates_t coords;
|
|
} aprsBeaconingLocation_t;
|
|
|
|
typedef struct
|
|
{
|
|
ticksTimer_t checkTimer;
|
|
ticksTimer_t nextBeaconTimer;
|
|
|
|
#if defined(APRS_USE_COURSETO_FOR_BEARING)
|
|
aprsBeaconingCoordinates_t previousBearingPosition;
|
|
uint16_t currentCourse;
|
|
#endif
|
|
|
|
aprsBeaconingLocation_t currentLocation;
|
|
aprsBeaconingLocation_t previousLocation;
|
|
|
|
CodeplugAPRSConfig_t aprsConfig[APRS_CONFIG_CHANNEL + 1];
|
|
|
|
double fixedLocationLat;
|
|
double fixedLocationLon;
|
|
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
uint8_t rateMessageCount;
|
|
#endif
|
|
|
|
bool triggerBeaconing;
|
|
uint8_t decayMult; // 1 to 32
|
|
|
|
aprsBeaconingSettings_t settings;
|
|
|
|
bool hasDoneQSY;
|
|
bool forcedManualBeaconing;
|
|
uint8_t beaconingFailureCounter;
|
|
} aprsBeaconingData_t;
|
|
|
|
typedef struct
|
|
{
|
|
uint32_t rxFreq;
|
|
uint32_t txFreq;
|
|
uint16_t rxTone;
|
|
uint8_t chMode;
|
|
bool wideBW;
|
|
} aprsChannelSettingsInUse_t;
|
|
|
|
aprsBeaconingData_t aprsBcnData =
|
|
{
|
|
.checkTimer = { 0U, 0U },
|
|
.nextBeaconTimer = { 0U, 0U },
|
|
#if defined(APRS_USE_COURSETO_FOR_BEARING)
|
|
.previousBearingPosition = { 0.0, 0.0 },
|
|
.currentCourse = 0U,
|
|
#endif
|
|
.currentLocation = { .time = 0U, .coords = { .latitude = 0.0, .longitude = 0.0 }, .bearing = UINT16_MAX, .speed = 0U },
|
|
.previousLocation = { .time = 0U, .coords = { .latitude = 0.0, .longitude = 0.0 }, .bearing = UINT16_MAX, .speed = 0U },
|
|
.fixedLocationLat = NAN,
|
|
.fixedLocationLon = NAN,
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
.rateMessageCount = 0U,
|
|
#endif
|
|
.triggerBeaconing = false,
|
|
.decayMult = APRS_BEACON_DECAY_MULT_MIN,
|
|
.settings =
|
|
{
|
|
.state = (APRS_BEACONING_STATE_LOCATION_FROM_CHANNEL | APRS_BEACONING_STATE_DECAY_ALGO_ENABLED | APRS_BEACONING_STATE_COMPRESSED_FORMAT),
|
|
.mode = APRS_BEACONING_MODE_OFF,
|
|
.initialInterval = APRS_BEACON_INITIAL_INTERVAL_DEFAULT, // Offset in initialIntervals[]
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
.messageInterval = APRS_BEACON_MESSAGE_INTERVAL_DEFAULT,
|
|
#endif
|
|
.power = 0U,
|
|
.smart =
|
|
{
|
|
.slowRate = APRS_SMART_BEACON_SLOW_RATE_DEFAULT,
|
|
.fastRate = APRS_SMART_BEACON_FAST_RATE_DEFAULT,
|
|
.lowSpeed = APRS_SMART_BEACON_LOW_SPEED_DEFAULT,
|
|
.highSpeed = APRS_SMART_BEACON_HIGH_SPEED_DEFAULT,
|
|
.turnAngle = APRS_SMART_BEACON_TURN_ANGLE_DEFAULT,
|
|
.turnSlope = APRS_SMART_BEACON_TURN_SLOPE_DEFAULT,
|
|
.turnTime = APRS_SMART_BEACON_TURN_TIME_DEFAULT,
|
|
}
|
|
},
|
|
.hasDoneQSY = false,
|
|
.forcedManualBeaconing = false,
|
|
.beaconingFailureCounter = 0U
|
|
};
|
|
|
|
const uint16_t initialIntervalsInSecs[APRS_BEACON_INITIAL_INTERVAL_MAX + 1] = { 12U, 30U, 60U, 120U, 180U, 300U, 600U, 1200U, 1800U, 3600U };
|
|
|
|
static char myCall[16];
|
|
static int lenBytes = 0;
|
|
static volatile uint32_t lastTone;
|
|
static volatile int bytePos = 0;
|
|
static volatile int bitPos = 0;
|
|
static volatile uint8_t dataByte;
|
|
static AX25Encoder_t encoderData;
|
|
static CodeplugAPRSConfig_t *aprsConfig;
|
|
|
|
volatile aprsSendProgress_t aprsTxProgress = APRS_TX_IDLE; // used in the ISR
|
|
|
|
|
|
static bool aprsBeaconingStateEnabled(aprsBeaconingStates_t s);
|
|
static bool aprsBeaconingLocationIsValid(aprsBeaconingLocation_t *location);
|
|
static void enqueueCharNrzi(AX25Encoder_t *encoderData, uint8_t data, bool useBitStuffing);
|
|
|
|
static void enqueueBit(AX25Encoder_t *encoderData, bool data)
|
|
{
|
|
if (data)
|
|
{
|
|
encoderData->packetBuffer[encoderData->packetBufferBitPosition / 8U] |= 0x01 << (encoderData->packetBufferBitPosition % 8U);
|
|
}
|
|
encoderData->packetBufferBitPosition++;
|
|
}
|
|
|
|
static void updateCRC(AX25Encoder_t *encoderData, bool dataBit)
|
|
{
|
|
uint16_t crcXorDataBit = (encoderData->crc ^ dataBit);
|
|
|
|
encoderData->crc >>= 1;
|
|
|
|
if (crcXorDataBit & 0x01)
|
|
{
|
|
encoderData->crc ^= 0x8408;
|
|
}
|
|
}
|
|
|
|
static void enqueueCRC(AX25Encoder_t *encoderData)
|
|
{
|
|
uint8_t crc_lo = (encoderData->crc ^ 0xff);
|
|
uint8_t crc_hi = ((encoderData->crc >> 8) ^ 0xff);
|
|
|
|
enqueueCharNrzi(encoderData, crc_lo, true);
|
|
enqueueCharNrzi(encoderData, crc_hi, true);
|
|
}
|
|
|
|
static void enqueuePadOfLength(AX25Encoder_t *encoderData, uint8_t len)
|
|
{
|
|
for (uint8_t j = 0; j < len; j++)
|
|
{
|
|
enqueueCharNrzi(encoderData, (' ' << 1), true);
|
|
}
|
|
}
|
|
|
|
static void enqueueCharNrzi(AX25Encoder_t *encoderData, uint8_t data, bool useBitStuffing)
|
|
{
|
|
bool currentBit;
|
|
|
|
for (uint8_t i = 0; i < 8U; i++)
|
|
{
|
|
currentBit = (data & 0x01);
|
|
|
|
updateCRC(encoderData, currentBit);
|
|
|
|
if (currentBit)
|
|
{
|
|
enqueueBit(encoderData, encoderData->currentBitNRZI);
|
|
encoderData->bitStuffingCounter++;
|
|
|
|
if (useBitStuffing && (encoderData->bitStuffingCounter == 5))
|
|
{
|
|
encoderData->currentBitNRZI ^= 1;
|
|
enqueueBit(encoderData, encoderData->currentBitNRZI);
|
|
|
|
encoderData->bitStuffingCounter = 0U;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
encoderData->currentBitNRZI ^= 1;
|
|
enqueueBit(encoderData, encoderData->currentBitNRZI);
|
|
|
|
encoderData->bitStuffingCounter = 0U;
|
|
}
|
|
|
|
data >>= 1;
|
|
}
|
|
}
|
|
|
|
static void enqueueString(AX25Encoder_t *encoderData, const char *str)
|
|
{
|
|
uint8_t i = 0;
|
|
|
|
while (str[i] != 0)
|
|
{
|
|
enqueueCharNrzi(encoderData, str[i], true);
|
|
i++;
|
|
};
|
|
}
|
|
|
|
static void enqueueFlagOfLength(AX25Encoder_t *encoderData, uint8_t len)
|
|
{
|
|
for (uint8_t i = 0; i < len; i++)
|
|
{
|
|
enqueueCharNrzi(encoderData, 0x7E, false); // 0x7E flag
|
|
}
|
|
}
|
|
|
|
static void enqueueHeader(AX25Encoder_t *encoderData)
|
|
{
|
|
//int len = MIN(strlen(APRS_DESTINATION), 6U);
|
|
|
|
for (uint32_t i = 0; i < strlen(APRS_DESTINATION); i++)
|
|
{
|
|
enqueueCharNrzi(encoderData, (APRS_DESTINATION[i] << 1), true);
|
|
}
|
|
|
|
//if (len < 6U)
|
|
//{
|
|
// enqueuePadOfLength(encoderData, (6U - len));
|
|
//}
|
|
|
|
enqueueCharNrzi(encoderData, ('0' << 1), true);
|
|
|
|
uint8_t len = MIN(strlen(myCall), 6U);
|
|
|
|
for (uint8_t i = 0; i < len; i++)
|
|
{
|
|
enqueueCharNrzi(encoderData, (myCall[i] << 1), true);
|
|
}
|
|
|
|
if (len < 6U)
|
|
{
|
|
enqueuePadOfLength(encoderData, (6U - len));
|
|
}
|
|
|
|
enqueueCharNrzi(encoderData, ((aprsConfig->senderSSID + '0') << 1), true);
|
|
|
|
uint8_t numPaths = ((strlen(aprsConfig->paths[1].name) == 0) ? 1U : 2U);
|
|
|
|
for (uint8_t p = 0; p < numPaths; p++)
|
|
{
|
|
len = MIN(strlen(aprsConfig->paths[p].name), 6U);
|
|
|
|
for (uint8_t i = 0; i < len; i++)
|
|
{
|
|
enqueueCharNrzi(encoderData, (aprsConfig->paths[p].name[i] << 1), true);
|
|
}
|
|
|
|
if (len < 6U)
|
|
{
|
|
enqueuePadOfLength(encoderData, (6U - len));
|
|
}
|
|
|
|
uint8_t isEnd = (p == (numPaths - 1)) ? 1U : 0U;
|
|
|
|
enqueueCharNrzi(encoderData, (((aprsConfig->paths[p].SSID + '0') << 1) + isEnd), true);
|
|
}
|
|
|
|
enqueueCharNrzi(encoderData, 0x03, true);
|
|
enqueueCharNrzi(encoderData, 0xF0, true);
|
|
}
|
|
|
|
static void enqueuePayload(AX25Encoder_t *encoderData, const char *latStr, const char *lonStr, const char *courseAndSpeed, aprsChannelSettingsInUse_t *channelSettings, bool fromSatScreen)
|
|
{
|
|
static const uint8_t DT_POS = '!';
|
|
uint8_t symbol = (aprsConfig->iconIndex + '!'); //'+'; // + = cross symbol. Y = yacht etc
|
|
uint8_t symTable = ((aprsConfig->iconTable == 0) ? '/' : '\\'); //' = secondary table
|
|
bool hasComment = (aprsConfig->comment[0] != 0);
|
|
|
|
enqueueCharNrzi(encoderData, DT_POS, true);
|
|
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_COMPRESSED_FORMAT))
|
|
{
|
|
enqueueCharNrzi(encoderData, symTable, true);
|
|
enqueueString(encoderData, latStr);
|
|
enqueueString(encoderData, lonStr);
|
|
enqueueCharNrzi(encoderData, symbol, true);
|
|
enqueueString(encoderData, (courseAndSpeed ? courseAndSpeed : " "));
|
|
enqueueCharNrzi(encoderData, (courseAndSpeed ? (/*0x26 (Other)*/ 0x3E /* (RMC)*/ + '!') : '!'), true);
|
|
}
|
|
else
|
|
{
|
|
enqueueString(encoderData, latStr);
|
|
enqueueCharNrzi(encoderData, symTable, true);
|
|
enqueueString(encoderData, lonStr);
|
|
enqueueCharNrzi(encoderData, symbol, true);
|
|
|
|
if (courseAndSpeed != NULL)
|
|
{
|
|
enqueueString(encoderData, courseAndSpeed);
|
|
}
|
|
}
|
|
|
|
if ((fromSatScreen == false) &&
|
|
(aprsConfig->flags & CODEPLUG_APRS_CONFIG_FLAG_TRANSMIT_QSY_INFO) && (aprsConfig->txFrequency != 0U))
|
|
{
|
|
uint32_t rxFrequency = channelSettings->rxFreq / 100U;
|
|
uint32_t txFrequency = channelSettings->txFreq / 100U;
|
|
int32_t offsetValue = (txFrequency - rxFrequency);
|
|
bool cssEnabled = ((channelSettings->chMode == RADIO_MODE_ANALOG) && (channelSettings->rxTone != CODEPLUG_CSS_TONE_NONE));
|
|
bool wideBW = channelSettings->wideBW;
|
|
uint32_t fMHz = (uint32_t)(rxFrequency * 1E-3);
|
|
uint32_t fKHz = (rxFrequency - (fMHz * 1000U));
|
|
char cssStr[6U] = { ' ', (wideBW ? 'T' : 't'), 'o', 'f', 'f', 0 }; // " Toff"
|
|
char freqStr[23U]; // "FFF.FFFMHz Toff +000 "
|
|
|
|
// There is a limit of 9.90MHz, as the document I found stated (why no 9.99MHz, no idea).
|
|
if (abs(offsetValue) > 9900)
|
|
{
|
|
offsetValue = 0;
|
|
}
|
|
|
|
if (cssEnabled)
|
|
{
|
|
CodeplugCSSTypes_t RxCSSType = codeplugGetCSSType(channelSettings->rxTone);
|
|
|
|
if (RxCSSType == CSS_TYPE_CTCSS)
|
|
{
|
|
snprintf(cssStr, sizeof(cssStr), " %c%03u", (wideBW ? 'C' : 'c'), (channelSettings->rxTone / 10U));
|
|
}
|
|
else if (RxCSSType & CSS_TYPE_DCS)
|
|
{
|
|
snprintf(cssStr, sizeof(cssStr), " %c%03X", (wideBW ? 'D' : 'd'), (channelSettings->rxTone & ~CSS_TYPE_DCS_MASK));
|
|
}
|
|
}
|
|
|
|
// Even if the offset is equal to zero, and no CSS is set,
|
|
// we send that information, which permits to give the bandwidth information
|
|
snprintf(freqStr, sizeof(freqStr), "%03u.%03uMHz%s %c%03d%c", fMHz, fKHz, cssStr,
|
|
(offsetValue < 0 ? '-' : '+'), abs((offsetValue / 10)), (hasComment ? ' ' : 0));
|
|
|
|
enqueueString(encoderData, freqStr);
|
|
}
|
|
|
|
if (hasComment)
|
|
{
|
|
enqueueString(encoderData, aprsConfig->comment);
|
|
}
|
|
}
|
|
|
|
static void aprsTxEnded(void)
|
|
{
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC);
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
#else // CPU_MK22FN512VLL12
|
|
#if defined(PLATFORM_MD9600)
|
|
trxDTMFoff(true);
|
|
trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC);
|
|
#else // PLATFORM_MD9600
|
|
AT1846sWriteTone1Reg(0);
|
|
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
HAL_GPIO_WritePin(RX_AUDIO_MUX_GPIO_Port, RX_AUDIO_MUX_Pin, GPIO_PIN_SET);
|
|
radioWriteReg2byte(0x44, 0x06, 0xCC); // set back to FM default
|
|
|
|
trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_MIC);
|
|
#endif // PLATFORM_MD9600
|
|
HRC6000SetMic(true);
|
|
#endif // CPU_MK22FN512VLL12
|
|
}
|
|
|
|
static void aprsConvertAndSplitCoord(double coord, bool isAmbiguous, uint32_t *degrees, uint32_t *minutesIntegral, uint32_t *minutesFractional)
|
|
{
|
|
*degrees = (uint32_t)coord;
|
|
|
|
double minutes = ((coord - (double)*degrees) * 60.0);
|
|
double minutesIntegralDouble;
|
|
|
|
if (isAmbiguous)
|
|
{
|
|
minutes += 0.000000001;
|
|
}
|
|
|
|
*minutesFractional = (uint32_t)abs((int)(modf(minutes, &minutesIntegralDouble) * 100));
|
|
*minutesIntegral = (uint32_t)(minutesIntegralDouble);
|
|
}
|
|
|
|
static void aprsCompressLatitude(double lat, char *str)
|
|
{
|
|
int latRes;
|
|
int lat0, lat1, lat2, lat3;
|
|
|
|
latRes = (int)round(380926.0 * (90.0 - lat));
|
|
|
|
lat0 = (latRes / (91 * 91 * 91));
|
|
latRes -= (lat0 * (91 * 91 * 91));
|
|
|
|
lat1 = (latRes / (91 * 91));
|
|
latRes -= (lat1 * (91 * 91));
|
|
|
|
lat2 = (latRes / 91);
|
|
latRes -= (lat2 * 91);
|
|
|
|
lat3 = latRes;
|
|
|
|
str[0] = (lat0 + '!');
|
|
str[1] = (lat1 + '!');
|
|
str[2] = (lat2 + '!');
|
|
str[3] = (lat3 + '!');
|
|
str[4] = 0;
|
|
}
|
|
|
|
static void aprsCompressLongitude(double lon, char *str)
|
|
{
|
|
int lonRes;
|
|
int lon0, lon1, lon2, lon3;
|
|
|
|
lonRes = (int)round(190463.0 * (180.0 + lon));
|
|
|
|
lon0 = (lonRes / (91 * 91 * 91));
|
|
lonRes -= (lon0 * (91 * 91 * 91));
|
|
|
|
lon1 = (lonRes / (91 * 91));
|
|
lonRes -= (lon1 * (91 * 91));
|
|
|
|
lon2 = (lonRes / 91);
|
|
lonRes -= (lon2 * 91);
|
|
|
|
lon3 = lonRes;
|
|
|
|
str[0] = (lon0 + '!');
|
|
str[1] = (lon1 + '!');
|
|
str[2] = (lon2 + '!');
|
|
str[3] = (lon3 + '!');
|
|
str[4] = 0;
|
|
}
|
|
|
|
static bool aprsSendPacket(CodeplugAPRSConfig_t *config, aprsBeaconingLocation_t *previousLocation, aprsBeaconingLocation_t *currentLocation, aprsChannelSettingsInUse_t *channelSettings, bool fromSatScreen)
|
|
{
|
|
double lat;
|
|
double lon;
|
|
char latStr[16];
|
|
char lonStr[16];
|
|
char courseSpeedStr[16];
|
|
bool courseAndSpeed = false;
|
|
bool gpsAndLocationAreValid = (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_LOCATION_FROM_GPS) && aprsBeaconingLocationIsValid(previousLocation) && aprsBeaconingLocationIsValid(currentLocation));
|
|
#if ! defined(PLATFORM_MD9600)
|
|
bool beaconingSilently = (aprsConfig->flags & CODEPLUG_APRS_CONFIG_FLAG_BEACONING_SILENTLY);
|
|
#endif
|
|
|
|
aprsConfig = config;
|
|
|
|
encoderData.bitStuffingCounter = 0;
|
|
encoderData.currentBitNRZI = false; // clear
|
|
memset(encoderData.packetBuffer, 0, AX25_PACKET_BUFFER_SIZE);
|
|
encoderData.packetBufferBitPosition = 0;
|
|
|
|
codeplugGetRadioName(myCall);
|
|
myCall[6] = 0; //truncate to 6 chars max
|
|
|
|
bool usePosition = (aprsConfig->flags & CODEPLUG_APRS_CONFIG_FLAG_USE_POSITION);
|
|
if (usePosition)
|
|
{
|
|
uint32_t lat24bits, lon24bits;
|
|
|
|
lat24bits = ((aprsConfig->latitude[2] << 16) | ( aprsConfig->latitude[1] << 8) | aprsConfig->latitude[0]);
|
|
lat = latLonFixed24ToDouble(lat24bits);
|
|
|
|
lon24bits = ((aprsConfig->longitude[2] << 16) | (aprsConfig->longitude[1] << 8) | aprsConfig->longitude[0]);
|
|
lon = latLonFixed24ToDouble(lon24bits);
|
|
}
|
|
else
|
|
{
|
|
lat = currentLocation->coords.latitude;
|
|
lon = currentLocation->coords.longitude;
|
|
}
|
|
|
|
uint8_t ambiguity = (aprsConfig->flags & CODEPLUG_APRS_CONFIG_FLAG_AMBIGUITY_MASK) >> 5;
|
|
if (ambiguity != 0U)
|
|
{
|
|
int tmp;
|
|
double ambCoeff;
|
|
|
|
switch (ambiguity)
|
|
{
|
|
case 1U: //0.0005 deg
|
|
ambCoeff = 5000.0;
|
|
break;
|
|
case 2U: //0.001 deg
|
|
ambCoeff = 1000.0;
|
|
break;
|
|
case 3U: //0.005 deg
|
|
ambCoeff = 500.0;
|
|
break;
|
|
case 4U: //0.01 deg
|
|
ambCoeff = 100.0;
|
|
break;
|
|
case 5U: //0.05 deg
|
|
ambCoeff = 50.0;
|
|
break;
|
|
case 6U: //0.1 deg
|
|
ambCoeff = 10.0;
|
|
break;
|
|
case 7U: //0.5 deg
|
|
ambCoeff = 5.0;
|
|
break;
|
|
default:
|
|
ambCoeff = 1;
|
|
break;
|
|
}
|
|
|
|
tmp = lat * ambCoeff;
|
|
lat = tmp / ambCoeff;
|
|
|
|
tmp = lon * ambCoeff;
|
|
lon = tmp / ambCoeff;
|
|
}
|
|
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_COMPRESSED_FORMAT))
|
|
{
|
|
aprsCompressLatitude(lat, latStr);
|
|
aprsCompressLongitude(lon, lonStr);
|
|
|
|
if (gpsAndLocationAreValid)
|
|
{
|
|
// Altitude ?
|
|
|
|
if (currentLocation->speed > 0)
|
|
{
|
|
int iSpeed = (int)(currentLocation->speed * 1E-2);
|
|
int iCourse = (int)(currentLocation->bearing * 1E-2);
|
|
int c; // speed
|
|
int s; // course
|
|
|
|
// Course
|
|
c = (iCourse / 4);
|
|
|
|
if (c < 0)
|
|
{
|
|
c += 90;
|
|
}
|
|
|
|
if (c >= 90)
|
|
{
|
|
c -= 90;
|
|
}
|
|
|
|
c += '!';
|
|
|
|
// Speed
|
|
s = (int)round(log(iSpeed + 1.0) / log(1.08));
|
|
s += '!';
|
|
|
|
courseSpeedStr[0] = c;
|
|
courseSpeedStr[1] = s;
|
|
courseSpeedStr[2] = 0;
|
|
courseAndSpeed = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32_t latDeg, lonDeg;
|
|
uint32_t latMinsInt, lonMinsInt;
|
|
uint32_t latMinsDecimal, lonMinsDecimal;
|
|
bool latHemisphere = (lat >= 0.0);
|
|
bool lonHemisphere = (lon >= 0.0);
|
|
|
|
lat = fabs(lat);
|
|
lon = fabs(lon);
|
|
|
|
aprsConvertAndSplitCoord(lat, (ambiguity > 3), &latDeg, &latMinsInt, &latMinsDecimal);
|
|
aprsConvertAndSplitCoord(lon, (ambiguity > 3), &lonDeg, &lonMinsInt, &lonMinsDecimal);
|
|
|
|
snprintf(latStr, 16, "%02u%02u.%02u%c", latDeg, latMinsInt, latMinsDecimal, latHemisphere ? 'N' : 'S');
|
|
snprintf(lonStr, 16, "%03u%02u.%02u%c", lonDeg, lonMinsInt, lonMinsDecimal, lonHemisphere ? 'E' : 'W');
|
|
|
|
if (gpsAndLocationAreValid)
|
|
{
|
|
// Altitude ?
|
|
|
|
if (currentLocation->speed > 0)
|
|
{
|
|
snprintf(courseSpeedStr, 16, "%03u/%03u", (uint16_t)(currentLocation->bearing * 1E-2), (uint16_t)(currentLocation->speed * 1E-2));
|
|
courseAndSpeed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
enqueueFlagOfLength(&encoderData, 16U);
|
|
|
|
encoderData.crc = 0xFFFF; // Initialise CRC now, after data has been sent as CRC does is only for data bytes
|
|
enqueueHeader(&encoderData);
|
|
enqueuePayload(&encoderData, latStr, lonStr, (courseAndSpeed ? courseSpeedStr : NULL), channelSettings, fromSatScreen);
|
|
enqueueCRC(&encoderData);
|
|
enqueueFlagOfLength(&encoderData, 3U);
|
|
|
|
lenBytes = encoderData.packetBufferBitPosition / 8;
|
|
bytePos = 0;
|
|
bitPos = 0;
|
|
lastTone = 0xFFFFFFFF;
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
encoderData.baudIs300 = false;
|
|
#else // PLATFORM_MD9600
|
|
encoderData.baudIs300 = ((aprsConfig->flags & CODEPLUG_APRS_CONFIG_FLAG_300_BAUDS) != 0);
|
|
#endif // PLATFORM_MD9600
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
ftm_config_t ftmInfo;
|
|
FTM_GetDefaultConfig(&ftmInfo);
|
|
ftmInfo.prescale = kFTM_Prescale_Divide_4;
|
|
FTM_Init(FTM1, &ftmInfo);
|
|
FTM_SetTimerPeriod(FTM1, USEC_TO_COUNT((!encoderData.baudIs300 ? 833U : 3333U), (CLOCK_GetFreq(kCLOCK_BusClk) / 4)));//use 2087U instead of 833 if in HS_RUN clock speed mode
|
|
FTM_EnableInterrupts(FTM1, kFTM_TimeOverflowInterruptEnable);
|
|
|
|
GPIO_PinWrite(GPIO_RX_audio_mux, Pin_RX_audio_mux, 1);
|
|
|
|
if (beaconingSilently)
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
else
|
|
{
|
|
audioAmpEnable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
AT1846sWriteTone1Reg(lastTone);
|
|
trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_TONE1);
|
|
|
|
aprsTxProgress = APRS_TX_IN_PROGRESS;
|
|
|
|
EnableIRQ(FTM1_IRQn);
|
|
FTM_StartTimer(FTM1, kFTM_SystemClock);
|
|
#else // CPU_MK22FN512VLL12
|
|
htim6.Instance = TIM6;
|
|
htim6.Init.Prescaler = encoderData.baudIs300 ? 7 : 1; // 7 gives 300 baud. 1 gives 1200 baud
|
|
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
htim6.Init.Period = 15000;
|
|
#else // PLATFORM_MD9600
|
|
htim6.Init.Period = 30000;
|
|
#endif // PLATFORM_MD9600
|
|
|
|
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
|
|
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
|
|
{
|
|
Error_Handler();
|
|
}
|
|
|
|
TIM_MasterConfigTypeDef sMasterConfig = { 0 };
|
|
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
|
|
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
|
|
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
|
|
{
|
|
Error_Handler();
|
|
}
|
|
|
|
HRC6000SetMic(false);
|
|
|
|
#if defined(PLATFORM_MD9600)
|
|
uint32_t lastTone = 1;
|
|
trxSetTone1(lastTone);
|
|
#else // PLATFORM_MD9600
|
|
uint32_t lastTone = 0;
|
|
AT1846sWriteTone1Reg(lastTone);
|
|
trxSelectVoiceChannel(AT1846_VOICE_CHANNEL_TONE1);
|
|
|
|
int volume = getVolumeControl() + 30;
|
|
|
|
if (volume >= 0)
|
|
{
|
|
radioWriteReg2byte(0x44, 0x06, volume);
|
|
HAL_GPIO_WritePin(RX_AUDIO_MUX_GPIO_Port, RX_AUDIO_MUX_Pin, GPIO_PIN_RESET);
|
|
|
|
if (beaconingSilently)
|
|
{
|
|
audioAmpDisable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
else
|
|
{
|
|
audioAmpEnable(AUDIO_AMP_CHANNEL_RF);
|
|
}
|
|
}
|
|
#endif // PLATFORM_MD9600
|
|
|
|
aprsTxProgress = APRS_TX_IN_PROGRESS;
|
|
HAL_TIM_Base_Start_IT(&htim6);
|
|
#endif // CPU_MK22FN512VLL12
|
|
|
|
return true;
|
|
}
|
|
|
|
void aprsBitStreamSender(void)
|
|
{
|
|
uint32_t newTone;
|
|
|
|
if (aprsTxProgress != APRS_TX_IN_PROGRESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bitPos % 8 == 0)
|
|
{
|
|
dataByte = encoderData.packetBuffer[bytePos];
|
|
bytePos++;
|
|
|
|
if (bytePos == lenBytes)
|
|
{
|
|
// AT1846SWriteTone1Reg(0);
|
|
// just stop the ISR and flag that the data has been sent.
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
FTM_StopTimer(FTM1);
|
|
DisableIRQ(FTM1_IRQn);
|
|
#else // CPU_MK22FN512VLL12
|
|
HAL_TIM_Base_Stop_IT(&htim6);
|
|
#endif // CPU_MK22FN512VLL12
|
|
|
|
aprsTxProgress = APRS_TX_FINISHED; // Tell the foreground we've finished, so it can do the speaker and other stuff
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
newTone = (encoderData.baudIs300 ? 16000 : 12000) + ((dataByte & 0x01) ? (encoderData.baudIs300 ? 2000 : 10000) : 0);
|
|
|
|
if (newTone != lastTone)
|
|
{
|
|
AT1846sWriteTone1Reg(newTone);
|
|
}
|
|
#else // CPU_MK22FN512VLL12
|
|
#if defined(PLATFORM_MD9600)
|
|
newTone = 1200 + ((dataByte & 0x01) ? 1000 : 0);
|
|
if (newTone != lastTone)
|
|
{
|
|
int tval = (newTone * 65536) / 32000; //calculate the value required to generate this tone
|
|
uint8_t tH = (tval >> 8) & 0xFF;
|
|
uint8_t tL = tval & 0xFF;
|
|
SPI0WritePageRegByteExtended(0x01, 0x11B, tH);// Set DTMF tone osc 1 to frequency of the required tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x11A, tL);
|
|
|
|
|
|
SPI0WritePageRegByteExtended(0x01, 0x123, tH);// Set DTMF tone osc 2 to frequency of the required tone
|
|
SPI0WritePageRegByteExtended(0x01, 0x122, tL);
|
|
lastTone = newTone;
|
|
}
|
|
#else // PLATFORM_MD9600
|
|
newTone = (encoderData.baudIs300 ? 16000 : 12000) + ((dataByte & 0x01) ? (encoderData.baudIs300 ? 2000 : 10000) : 0);
|
|
if (newTone != lastTone)
|
|
{
|
|
AT1846sWriteTone1Reg(newTone);
|
|
lastTone = newTone;
|
|
}
|
|
#endif // PLATFORM_MD9600
|
|
#endif // CPU_MK22FN512VLL12
|
|
|
|
dataByte >>= 1;
|
|
bitPos++;
|
|
}
|
|
|
|
#if defined(CPU_MK22FN512VLL12)
|
|
void FTM1_IRQHandler(void)
|
|
{
|
|
/* Clear interrupt flag.*/
|
|
FTM_ClearStatusFlags(FTM1, kFTM_TimeOverflowFlag);
|
|
aprsBitStreamSender();
|
|
__DSB();
|
|
}
|
|
|
|
#endif // CPU_MK22FN512VLL12
|
|
|
|
//
|
|
// *****************
|
|
// *** Beaconing ***
|
|
// *****************
|
|
//
|
|
// Aknowlegments:
|
|
// - SmartBeaconing™ is a beaconing algorithm invented by Tony Arnerich KD7TA and Steve Bragg KA9MVA.
|
|
// - largely inspired by https://github.com/erstec/APRS-ESP/blob/master/src/smartBeaconing.cpp from Ernest Stepanov LY3PH.
|
|
//
|
|
|
|
static bool aprsBeaconingStateEnabled(aprsBeaconingStates_t s)
|
|
{
|
|
return ((aprsBcnData.settings.state & s) == s);
|
|
}
|
|
|
|
// Returns true if the bit value has changed.
|
|
static bool aprsBeaconingStateSetEnable(aprsBeaconingStates_t s, bool enabled)
|
|
{
|
|
if ((s & (APRS_BEACONING_STATE_LOCATION_FROM_CHANNEL | APRS_BEACONING_STATE_LOCATION_FROM_GPS)) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ret = (((aprsBcnData.settings.state & s) != 0) != enabled);
|
|
|
|
if (enabled)
|
|
{
|
|
aprsBcnData.settings.state |= s;
|
|
}
|
|
else
|
|
{
|
|
aprsBcnData.settings.state &= ~s;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool gpsPVTIsValid(void)
|
|
{
|
|
#if defined(HAS_GPS)
|
|
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
|
|
{
|
|
return ((gpsData.Status & (GPS_STATUS_HAS_FIX | GPS_STATUS_HAS_POSITION | GPS_STATUS_HAS_SPEED | GPS_STATUS_HAS_TIME)) == (GPS_STATUS_HAS_FIX | GPS_STATUS_HAS_POSITION | GPS_STATUS_HAS_SPEED | GPS_STATUS_HAS_TIME));
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool gpsFixIsValid(void)
|
|
{
|
|
#if defined(HAS_GPS)
|
|
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
|
|
{
|
|
return ((gpsData.Status & GPS_STATUS_HAS_FIX) == GPS_STATUS_HAS_FIX);
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
static void aprsBeaconingInvalidateLocation(aprsBeaconingLocation_t *location)
|
|
{
|
|
location->bearing = UINT16_MAX;
|
|
location->coords.latitude = location->coords.longitude = 0.0;
|
|
location->speed = 0U;
|
|
location->time = 0U;
|
|
}
|
|
|
|
static bool aprsBeaconingLocationIsValid(aprsBeaconingLocation_t *location)
|
|
{
|
|
return (location->bearing != UINT16_MAX);
|
|
}
|
|
|
|
static bool aprsBeaconingCurrentPositionIsValid(void)
|
|
{
|
|
return ((aprsBeaconingStateEnabled(APRS_BEACONING_STATE_LOCATION_FROM_GPS) && settingsLocationIsValid()) ||
|
|
(aprsBeaconingStateEnabled(APRS_BEACONING_STATE_LOCATION_FROM_CHANNEL) && ((isnan(aprsBcnData.fixedLocationLat) == 0) && (isnan(aprsBcnData.fixedLocationLon) == 0))));
|
|
}
|
|
|
|
static double aprsGetFixedPositionLatitude(void)
|
|
{
|
|
double lat = 0.0;
|
|
|
|
if (isnan(aprsBcnData.fixedLocationLat) == 0)
|
|
{
|
|
return aprsBcnData.fixedLocationLat;
|
|
}
|
|
else
|
|
{
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG))
|
|
{
|
|
if (aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].flags & CODEPLUG_APRS_CONFIG_FLAG_USE_POSITION)
|
|
{
|
|
uint32_t tLat = aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].latitude[2];
|
|
tLat = (tLat << 8) + aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].latitude[1];
|
|
tLat = (tLat << 8) + aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].latitude[0];
|
|
|
|
aprsBcnData.fixedLocationLat = latLonFixed24ToDouble(tLat);
|
|
|
|
return aprsBcnData.fixedLocationLat;
|
|
}
|
|
}
|
|
|
|
// Returns NAN if no position is valid, not even the one stored in the settings.
|
|
if (settingsLocationIsValid() == false)
|
|
{
|
|
return NAN;
|
|
}
|
|
else
|
|
{
|
|
aprsBcnData.fixedLocationLat = lat = settingsLocationGetLatitude();
|
|
}
|
|
}
|
|
|
|
return lat;
|
|
}
|
|
|
|
static double aprsGetFixedPositionLongitude(void)
|
|
{
|
|
double lon = 0.0;
|
|
|
|
if (isnan(aprsBcnData.fixedLocationLon) == 0)
|
|
{
|
|
return aprsBcnData.fixedLocationLon;
|
|
}
|
|
else
|
|
{
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG))
|
|
{
|
|
if (aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].flags & CODEPLUG_APRS_CONFIG_FLAG_USE_POSITION)
|
|
{
|
|
uint32_t tLon = aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].longitude[2];
|
|
tLon = (tLon << 8) + aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].longitude[1];
|
|
tLon = (tLon << 8) + aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].longitude[0];
|
|
|
|
aprsBcnData.fixedLocationLon = latLonFixed24ToDouble(tLon);
|
|
|
|
return aprsBcnData.fixedLocationLon;
|
|
}
|
|
}
|
|
|
|
if (settingsLocationIsValid() == false)
|
|
{
|
|
return NAN;
|
|
}
|
|
else
|
|
{
|
|
aprsBcnData.fixedLocationLon = lon = settingsLocationGetLongitude();
|
|
}
|
|
}
|
|
|
|
return lon;
|
|
}
|
|
|
|
static double aprsGetBearingAngleDiff(double a, double b)
|
|
{
|
|
double delta = fmod(fabs(a - b), 360.0);
|
|
|
|
return (delta <= 180.0 ? delta : (360.0 - delta));
|
|
}
|
|
|
|
#if defined(APRS_USE_COURSETO_FOR_BEARING)
|
|
uint16_t aprsBeaconingGetBearing(void)
|
|
{
|
|
if (distanceBetweenTwoCoords(aprsBcnData.previousBearingPosition.latitude, aprsBcnData.previousBearingPosition.longitude,
|
|
aprsBcnData.currentLocation.coords.latitude, aprsBcnData.currentLocation.coords.longitude, false) > 2.0)
|
|
{
|
|
aprsBcnData.currentCourse = (uint16_t)(courseTo(aprsBcnData.previousBearingPosition.latitude, aprsBcnData.previousBearingPosition.longitude,
|
|
aprsBcnData.currentLocation.coords.latitude, aprsBcnData.currentLocation.coords.longitude) * 1E2);
|
|
|
|
memcpy(&aprsBcnData.previousBearingPosition, &aprsBcnData.currentLocation.coords, sizeof(aprsBeaconingCoordinates_t));
|
|
}
|
|
|
|
return aprsBcnData.currentCourse;
|
|
}
|
|
#endif // APRS_USE_COURSETO_FOR_BEARING
|
|
|
|
static bool aprsSmartBeaconingCornerPegging(double *currentSpeedMPS, uint32_t *timeDiff)
|
|
{
|
|
*currentSpeedMPS = ((aprsBcnData.currentLocation.speed * 1E-2) * MPS_PER_KNOT);
|
|
*timeDiff = (aprsBcnData.currentLocation.time - aprsBcnData.previousLocation.time);
|
|
|
|
if (aprsBcnData.currentLocation.speed > SMART_BEACONING_SPEED_MIN)
|
|
{
|
|
if (aprsBeaconingLocationIsValid(&aprsBcnData.currentLocation) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if last bearing unknown, deploy turn_time
|
|
if (aprsBeaconingLocationIsValid(&aprsBcnData.previousLocation) == false)
|
|
{
|
|
return ((*timeDiff / MILLISECS_PER_SEC) >= aprsBcnData.settings.smart.turnTime);
|
|
}
|
|
|
|
double turnDiff = aprsGetBearingAngleDiff((aprsBcnData.currentLocation.bearing * 1E-2), (aprsBcnData.previousLocation.bearing * 1E-2));
|
|
double turnThreshold = MIN(120.0, ((aprsBcnData.settings.smart.turnAngle * 1.0) + ((aprsBcnData.settings.smart.turnSlope * 10.0) / (*currentSpeedMPS * MPS_TO_MPH))));
|
|
|
|
return (((*timeDiff / MILLISECS_PER_SEC) >= ((uint32_t)aprsBcnData.settings.smart.turnTime)) && (turnDiff > turnThreshold));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static double aprsSmartBeaconingGetMaxSpeed(double currentSpeedMPS, uint32_t timeDiff)
|
|
{
|
|
double dist = distanceBetweenTwoCoords(aprsBcnData.currentLocation.coords.latitude, aprsBcnData.currentLocation.coords.longitude, aprsBcnData.previousLocation.coords.latitude, aprsBcnData.previousLocation.coords.longitude, false);
|
|
|
|
return MAX(MAX((dist / (timeDiff / MILLISECS_PER_SEC)), currentSpeedMPS), ((aprsBcnData.previousLocation.speed * 1E-2) * MPS_PER_KNOT));
|
|
}
|
|
|
|
static uint32_t aprsSmartBeaconingSpeedRate(double speedMPS)
|
|
{
|
|
int32_t slowRate = (aprsBcnData.settings.smart.slowRate * 60); // min => s
|
|
int32_t fastRate = aprsBcnData.settings.smart.fastRate; // s
|
|
double lowSpeed = (aprsBcnData.settings.smart.lowSpeed * MPS_PER_KMPH); // => m/s
|
|
double highSpeed = (aprsBcnData.settings.smart.highSpeed * MPS_PER_KMPH); // => m/s
|
|
|
|
if (speedMPS <= lowSpeed)
|
|
{
|
|
return slowRate;
|
|
}
|
|
else if (speedMPS >= highSpeed)
|
|
{
|
|
return fastRate;
|
|
}
|
|
|
|
return (uint32_t)(fastRate + (slowRate - fastRate) * (highSpeed - speedMPS) / (highSpeed - lowSpeed));
|
|
}
|
|
|
|
static bool aprsSmartBeaconingCheck(void)
|
|
{
|
|
double currentSpeedMPS;
|
|
uint32_t timeDiff;
|
|
|
|
if ((aprsBeaconingLocationIsValid(&aprsBcnData.previousLocation) == false) || aprsSmartBeaconingCornerPegging(¤tSpeedMPS, &timeDiff))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint32_t speedRate = aprsSmartBeaconingSpeedRate(aprsSmartBeaconingGetMaxSpeed(currentSpeedMPS, timeDiff));
|
|
|
|
// timediff in seconds.
|
|
if ((timeDiff / MILLISECS_PER_SEC) >= speedRate)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void aprsBeaconingDecayTick(bool reset)
|
|
{
|
|
if ((aprsBcnData.settings.mode != APRS_BEACONING_MODE_SMART_BEACONING) && aprsBeaconingStateEnabled(APRS_BEACONING_STATE_DECAY_ALGO_ENABLED))
|
|
{
|
|
if (reset)
|
|
{
|
|
aprsBcnData.decayMult = APRS_BEACON_DECAY_MULT_MIN;
|
|
}
|
|
else if (aprsBcnData.decayMult < APRS_BEACON_DECAY_MULT_MAX)
|
|
{
|
|
aprsBcnData.decayMult *= APRS_BEACON_DECAY_MULT_STEP;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aprsBeaconingTxStateTick(uiEvent_t *ev)
|
|
{
|
|
switch(aprsTxProgress)
|
|
{
|
|
case APRS_TX_IDLE:
|
|
case APRS_TX_IN_PROGRESS:
|
|
break;
|
|
|
|
case APRS_TX_FINISHED:
|
|
aprsTxEnded();// foreground parts of APRS send finished
|
|
// deliberate fall through
|
|
case APRS_TX_TERMINATE:
|
|
trxTransmissionEnabled = false;
|
|
// In analog mode. Stop transmitting immediately
|
|
LedWrite(LED_RED, 0);
|
|
#if defined(HAS_GPS)
|
|
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
|
|
{
|
|
gpsDataInputStartStop(true);
|
|
}
|
|
#endif
|
|
// Need to wrap this in Task Critical to avoid bus contention on the I2C bus.
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
|
|
trxActivateRx(true);
|
|
trxIsTransmitting = false;
|
|
PTTToggledDown = false;
|
|
aprsTxProgress = APRS_TX_WAIT_PTT_OFF;
|
|
break;
|
|
|
|
case APRS_TX_WAIT_PTT_OFF:
|
|
case APRS_TX_FAILED_WAITING_TO_BEACON:
|
|
if ((ev->buttons & BUTTON_PTT) == 0)
|
|
{
|
|
aprsTxProgress = APRS_TX_FINAL_STAGE;
|
|
|
|
if (aprsBcnData.forcedManualBeaconing || (aprsBcnData.settings.mode == APRS_BEACONING_MODE_MANUAL))
|
|
{
|
|
aprsBcnData.forcedManualBeaconing = false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case APRS_TX_FINAL_STAGE:
|
|
// Needs to restore frequency/CSS/Mode after an APRS's config QSY
|
|
// That never happen in APRS_BEACONING_MODE_PTT or Satellite modes
|
|
if (aprsBcnData.hasDoneQSY)
|
|
{
|
|
radioSetTRxDevice(RADIO_DEVICE_PRIMARY);
|
|
|
|
if (menuSystemGetRootMenuNumber() == UI_CHANNEL_MODE)
|
|
{
|
|
uint32_t rxFreq = (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->txFreq : currentChannelData->rxFreq);
|
|
uint32_t txFreq = (uiDataGlobal.talkaround ? rxFreq : (uiDataGlobal.reverseRepeaterChannel ? currentChannelData->rxFreq : currentChannelData->txFreq));
|
|
|
|
trxSetFrequency(rxFreq, txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && (uiDataGlobal.reverseRepeaterChannel || codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO))) ? DMR_MODE_DMO : DMR_MODE_AUTO));
|
|
}
|
|
else
|
|
{
|
|
uint32_t rxFreq = (uiDataGlobal.reverseRepeaterVFO ? currentChannelData->txFreq : currentChannelData->rxFreq);
|
|
uint32_t txFreq = (uiDataGlobal.reverseRepeaterVFO ? currentChannelData->rxFreq : currentChannelData->txFreq);
|
|
|
|
trxSetFrequency(rxFreq, txFreq, (((currentChannelData->chMode == RADIO_MODE_DIGITAL) && codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_FORCE_DMO)) ? DMR_MODE_DMO : DMR_MODE_AUTO));
|
|
}
|
|
|
|
trxSetModeAndBandwidth(currentChannelData->chMode, (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_BW_25K) != 0));
|
|
|
|
if (currentChannelData->chMode == RADIO_MODE_ANALOG)
|
|
{
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, currentChannelData->rxTone);
|
|
}
|
|
|
|
vTaskDelay((40U / portTICK_PERIOD_MS));
|
|
|
|
if (currentChannelData->chMode == RADIO_MODE_DIGITAL)
|
|
{
|
|
HRC6000ResetTimeSlotDetection();
|
|
HRC6000ClearActiveDMRID();
|
|
}
|
|
|
|
aprsBcnData.hasDoneQSY = false;
|
|
}
|
|
|
|
uiDataGlobal.displayQSOState = QSO_DISPLAY_DEFAULT_SCREEN;
|
|
aprsTxProgress = APRS_TX_IDLE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingInit(void)
|
|
{
|
|
aprsBcnData.decayMult = APRS_BEACON_DECAY_MULT_MIN;
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_ENABLED, false);
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_GPS_FIX, false);
|
|
}
|
|
|
|
void aprsBeaconingStart(void)
|
|
{
|
|
if (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF)
|
|
{
|
|
aprsBeaconingResetTimers();
|
|
aprsBeaconingInvalidateLocation(&aprsBcnData.currentLocation);
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_ENABLED, true);
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_GPS_FIX, false);
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingStop(void)
|
|
{
|
|
if (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF)
|
|
{
|
|
if (aprsTxProgress != APRS_TX_IDLE)
|
|
{
|
|
#if defined(PLATFORM_MD9600)
|
|
uint16_t frontPanelButtons;
|
|
uiEvent_t ev = { .buttons = buttonsRead(&frontPanelButtons, uiDataGlobal.sk2latched), .keys = NO_KEYCODE, .rotary = 0, .function = 0, .events = NO_EVENT, .hasEvent = false, .time = ticksGetMillis() };
|
|
#else
|
|
uiEvent_t ev = { .buttons = buttonsRead(), .keys = NO_KEYCODE, .rotary = 0, .function = 0, .events = NO_EVENT, .hasEvent = false, .time = ticksGetMillis() };
|
|
#endif
|
|
|
|
aprsTxProgress = APRS_TX_FINISHED;
|
|
aprsBeaconingTxStateTick(&ev);
|
|
}
|
|
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_ENABLED, false);
|
|
ticksTimerReset(&aprsBcnData.checkTimer);
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingResetTimers(void)
|
|
{
|
|
if ((uiDataGlobal.Scan.active == false) && (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF))
|
|
{
|
|
uint8_t APRSConfigIndex = currentChannelData->aprsConfigIndex;
|
|
bool hasAPRSCfg = false;
|
|
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
aprsBcnData.rateMessageCount = 0U;
|
|
#endif
|
|
aprsBcnData.decayMult = APRS_BEACON_DECAY_MULT_MIN;
|
|
|
|
aprsBeaconingInvalidateLocation(&aprsBcnData.previousLocation);
|
|
aprsBeaconingInvalidateFixedPosition();
|
|
|
|
ticksTimerStart(&aprsBcnData.checkTimer, MILLISECS_PER_SEC); // 1s timer
|
|
ticksTimerReset(&aprsBcnData.nextBeaconTimer);
|
|
|
|
if (APRSConfigIndex != 0)
|
|
{
|
|
if (codeplugAPRSGetDataForIndex(APRSConfigIndex, &aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL]))
|
|
{
|
|
hasAPRSCfg = true;
|
|
}
|
|
}
|
|
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_APRS_CONFIG, hasAPRSCfg);
|
|
}
|
|
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG))
|
|
{
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG, false);
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingPrepareSatelliteConfig(void)
|
|
{
|
|
bool hasAprsConfig = false;
|
|
int APRSConfigIndex = codeplugAPRSGetIndexOfName(currentActiveSatellite->name);
|
|
|
|
if (APRSConfigIndex != 0)
|
|
{
|
|
if (codeplugAPRSGetDataForIndex(APRSConfigIndex, &aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE]))
|
|
{
|
|
hasAprsConfig = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (satelliteDataNative[uiDataGlobal.SatelliteAndAlarmData.currentSatellite].AdditionalData[0] != 0)
|
|
{
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].senderSSID = 7U;
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].comment[0] = 0U;
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].iconTable = 0U;
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].iconIndex = ('0' - '!');
|
|
|
|
memcpy(aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].paths[0].name, &satelliteDataNative[uiDataGlobal.SatelliteAndAlarmData.currentSatellite].AdditionalData[0], 6U);
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].paths[0].SSID = satelliteDataNative[uiDataGlobal.SatelliteAndAlarmData.currentSatellite].AdditionalData[6] - '0';
|
|
memcpy(aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].paths[1].name, &satelliteDataNative[uiDataGlobal.SatelliteAndAlarmData.currentSatellite].AdditionalData[7], 6U);
|
|
aprsBcnData.aprsConfig[APRS_CONFIG_SATELLITE].paths[1].SSID = satelliteDataNative[uiDataGlobal.SatelliteAndAlarmData.currentSatellite].AdditionalData[13] - '0';
|
|
|
|
hasAprsConfig = true;
|
|
}
|
|
}
|
|
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG, hasAprsConfig);
|
|
|
|
aprsBeaconingSetCurrentLocation(settingsLocationGetLatitude(), settingsLocationGetLongitude());
|
|
|
|
// Ensure to get fixed position updated, when entering the Satellite
|
|
// screen, as aprsBeaconingTick() could invalidate it when Satellite screen
|
|
// isn't running.
|
|
if (hasAprsConfig)
|
|
{
|
|
(void)aprsGetFixedPositionLatitude();
|
|
(void)aprsGetFixedPositionLongitude();
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingSetCurrentLocation(double lat, double lon)
|
|
{
|
|
aprsBcnData.currentLocation.coords.latitude = lat;
|
|
aprsBcnData.currentLocation.coords.longitude = lon;
|
|
}
|
|
|
|
void aprsBeaconingInvalidateFixedPosition(void)
|
|
{
|
|
aprsBcnData.fixedLocationLat = aprsBcnData.fixedLocationLon = NAN;
|
|
}
|
|
|
|
bool aprsBeaconingIsSuspended(void)
|
|
{
|
|
return ((aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF) && (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_ENABLED) == false));
|
|
}
|
|
|
|
void aprsBeaconingSetSuspend(bool suspend)
|
|
{
|
|
if (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF)
|
|
{
|
|
if (suspend)
|
|
{
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_ENABLED, false);
|
|
}
|
|
else
|
|
{
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_ENABLED) == false)
|
|
{
|
|
//aprsBcnData.decayMult = APRS_BEACON_DECAY_MULT_MIN;
|
|
aprsBcnData.beaconingFailureCounter = 0U;
|
|
ticksTimerStart(&aprsBcnData.checkTimer, MILLISECS_PER_SEC); // 1s timer
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_ENABLED, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingToggles(void)
|
|
{
|
|
if (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF)
|
|
{
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
bool running = aprsBeaconingStateEnabled(APRS_BEACONING_STATE_ENABLED);
|
|
|
|
aprsBeaconingSetSuspend(running);
|
|
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "Beaconing: %s", (running ? currentLanguage->off : currentLanguage->on));
|
|
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 1000, buf, false);
|
|
}
|
|
}
|
|
|
|
aprsBeaconingMode_t aprsBeaconingGetMode(void)
|
|
{
|
|
return aprsBcnData.settings.mode;
|
|
}
|
|
|
|
// Returns true if the beacon has been sent.
|
|
bool aprsBeaconingSendBeacon(bool fromSatScreen, bool forcedManualBeaconing)
|
|
{
|
|
aprsChannelSettingsInUse_t channelSettings;
|
|
|
|
if (uiDataGlobal.Scan.active ||
|
|
(trxGetMode() == RADIO_MODE_NONE) ||
|
|
((trxGetMode() == RADIO_MODE_DIGITAL) && (aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].txFrequency == 0U)) ||
|
|
settingsIsOptionBitSet(BIT_TX_INHIBIT) ||
|
|
(fromSatScreen && (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG) == false)) ||
|
|
((fromSatScreen == false) &&
|
|
((aprsBcnData.settings.mode == APRS_BEACONING_MODE_OFF)
|
|
|| (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_ENABLED) == false)
|
|
|| (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG) == false)
|
|
)
|
|
)
|
|
|| (aprsTxProgress != APRS_TX_IDLE))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Ignore PTT rate when in satellite mode.
|
|
if ((fromSatScreen == false) && (aprsBcnData.settings.mode == APRS_BEACONING_MODE_PTT))
|
|
{
|
|
if ((ticksTimerHasExpired(&aprsBcnData.nextBeaconTimer) == false)
|
|
|| (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG) == false))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Copy currentChannelData settings, as it could change because of QSY
|
|
channelSettings.chMode = currentChannelData->chMode;
|
|
channelSettings.rxFreq = currentChannelData->rxFreq;
|
|
channelSettings.txFreq = currentChannelData->txFreq;
|
|
channelSettings.rxTone = currentChannelData->rxTone;
|
|
channelSettings.wideBW = trxGetBandwidthIs25kHz();
|
|
|
|
/// Build and Send beacon Here
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
if (((aprsBcnData.rateMessageCount % aprsBcnData.settings.messageInterval) == 0))
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
bool anyManualMode = (fromSatScreen || forcedManualBeaconing || (aprsBcnData.settings.mode == APRS_BEACONING_MODE_PTT) || (aprsBcnData.settings.mode == APRS_BEACONING_MODE_MANUAL));
|
|
|
|
// check config validity
|
|
if (anyManualMode)
|
|
{
|
|
char buffer[SCREEN_LINE_BUFFER_SIZE];
|
|
bool configIsValid = ((fromSatScreen && aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG)) ||
|
|
((fromSatScreen == false) && aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG)));
|
|
bool positionIsValid = (fromSatScreen ? false : aprsBeaconingCurrentPositionIsValid());
|
|
|
|
aprsBcnData.forcedManualBeaconing = forcedManualBeaconing;
|
|
|
|
if (fromSatScreen && (positionIsValid == false))
|
|
{
|
|
// Use current position (from settings and/or GPS)
|
|
if (settingsLocationIsValid())
|
|
{
|
|
aprsBeaconingSetCurrentLocation(settingsLocationGetLatitude(), settingsLocationGetLongitude());
|
|
positionIsValid = true;
|
|
}
|
|
}
|
|
|
|
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%s%c",
|
|
((configIsValid && positionIsValid) ? "APRS Tx" : (positionIsValid ? currentLanguage->APRS : currentLanguage->location)),
|
|
((configIsValid && positionIsValid) ? '\0' : '?'));
|
|
|
|
if (configIsValid && positionIsValid)
|
|
{
|
|
int16_t yPos = (fromSatScreen ?
|
|
#if defined(HAS_COLOURS)
|
|
(DISPLAY_SIZE_Y / 4)
|
|
#else
|
|
DISPLAY_Y_POS_CONTACT
|
|
#endif
|
|
: DISPLAY_Y_POS_TX_TIMER);
|
|
ucFont_t font = (fromSatScreen ? FONT_SIZE_3 : FONT_SIZE_4);
|
|
int16_t fontHeight = (fromSatScreen ? FONT_SIZE_3_HEIGHT : FONT_SIZE_4_HEIGHT);
|
|
#if ! defined(HAS_COLOURS)
|
|
int16_t tBorder = (fromSatScreen ? 0 : 6);
|
|
int16_t bBorder = (fromSatScreen ? -1 : 12);
|
|
#endif
|
|
|
|
displayThemeApply(THEME_ITEM_FG_TX_COUNTER, THEME_ITEM_BG);
|
|
#if defined(HAS_COLOURS)
|
|
displayFillRect(0, yPos, DISPLAY_SIZE_X, fontHeight, true);
|
|
#else
|
|
displayFillRect(0, (yPos + tBorder), DISPLAY_SIZE_X, (fontHeight - bBorder), true);
|
|
#endif
|
|
displayPrintCentered(yPos, buffer, font);
|
|
displayThemeResetToDefault();
|
|
displayRender();
|
|
}
|
|
else
|
|
{
|
|
uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_MESSAGE, 1500, buffer, true);
|
|
soundSetMelody(MELODY_ERROR_BEEP);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (fromSatScreen == false)
|
|
{
|
|
bool aprsConfigHasFrequencyQSY = (aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].txFrequency != 0U);
|
|
uint32_t txFrequency = (aprsConfigHasFrequencyQSY ? aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].txFrequency : currentChannelData->txFreq);
|
|
|
|
// toggle xmit
|
|
switch (aprsBcnData.settings.mode)
|
|
{
|
|
case APRS_BEACONING_MODE_MANUAL:
|
|
case APRS_BEACONING_MODE_AUTO:
|
|
case APRS_BEACONING_MODE_SMART_BEACONING:
|
|
if ((txFrequency != 0U)
|
|
&&
|
|
(aprsConfigHasFrequencyQSY ? true : (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_RX_ONLY) == 0))
|
|
&&
|
|
((nonVolatileSettings.txFreqLimited == BAND_LIMITS_NONE) || trxCheckFrequencyInAmateurBand(txFrequency)
|
|
#if defined(PLATFORM_MD9600)
|
|
|| (aprsConfigHasFrequencyQSY ? true : (codeplugChannelGetFlag(currentChannelData, CHANNEL_FLAG_OUT_OF_BAND) != 0))
|
|
#endif
|
|
)
|
|
&&
|
|
aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG))
|
|
{
|
|
rxPowerSavingSetState(ECOPHASE_POWERSAVE_INACTIVE);
|
|
|
|
#if defined(HAS_GPS)
|
|
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
|
|
{
|
|
gpsDataInputStartStop(false);
|
|
}
|
|
#endif
|
|
|
|
watchdogRun(false);
|
|
|
|
// Needs to QSY ?
|
|
// The current channel configuration will be restored
|
|
// once the frame is sent (see aprsBeaconingTick())
|
|
if (aprsConfigHasFrequencyQSY)
|
|
{
|
|
radioSetTRxDevice(RADIO_DEVICE_PRIMARY);
|
|
|
|
trxSetFrequency(txFrequency, txFrequency, DMR_MODE_AUTO);
|
|
trxSetModeAndBandwidth(RADIO_MODE_ANALOG, false);
|
|
trxSetRxCSS(RADIO_DEVICE_PRIMARY, CODEPLUG_CSS_TONE_NONE);
|
|
trxSetTxCSS(CODEPLUG_CSS_TONE_NONE);
|
|
|
|
aprsBcnData.hasDoneQSY = true;
|
|
|
|
vTaskDelay((40U / portTICK_PERIOD_MS));
|
|
}
|
|
|
|
// Avoid collision.
|
|
uint32_t waitingForXmit = (APRS_ANTI_COLLISION_MS_MAX / RSSI_NOISE_SAMPLE_PERIOD_PIT);
|
|
while((waitingForXmit > 0) && (LedRead(LED_GREEN) || ((audioAmpGetStatus() & AUDIO_AMP_CHANNEL_RF) != 0)))
|
|
{
|
|
vTaskDelay((RSSI_NOISE_SAMPLE_PERIOD_PIT / portTICK_PERIOD_MS));
|
|
waitingForXmit--;
|
|
trxCheckAnalogSquelch();
|
|
}
|
|
|
|
watchdogRun(true);
|
|
|
|
if (waitingForXmit == 0U)
|
|
{
|
|
#if defined(HAS_GPS)
|
|
if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) > GPS_MODE_OFF)
|
|
{
|
|
gpsDataInputStartStop(true);
|
|
}
|
|
#endif
|
|
aprsTxProgress = APRS_TX_FAILED_WAITING_TO_BEACON;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Temporary change the TX power output, if needed, and only if
|
|
// the QSY is enabled in the APRS config.
|
|
// The power will be set back to the "normal" value when
|
|
// restoring the frequency.
|
|
if (aprsBcnData.hasDoneQSY && (aprsBcnData.settings.power != 0U))
|
|
{
|
|
trxSetPowerFromLevel((aprsBcnData.settings.power - 1U));
|
|
}
|
|
|
|
LedWrite(LED_GREEN, 0);
|
|
LedWrite(LED_RED, 1);
|
|
|
|
HRC6000ClearIsWakingState();
|
|
trxSetTX();
|
|
|
|
// TX Delay;
|
|
uint32_t m = ticksGetMillis();
|
|
while((ticksGetMillis() - m) < APRS_XMIT_TX_DELAY)
|
|
{
|
|
vTaskDelay((1U / portTICK_PERIOD_MS));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
aprsSendPacket(&aprsBcnData.aprsConfig[(fromSatScreen ? APRS_CONFIG_SATELLITE : APRS_CONFIG_CHANNEL)], &aprsBcnData.previousLocation, &aprsBcnData.currentLocation, &channelSettings, fromSatScreen);
|
|
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
aprsBcnData.rateMessageCount = (aprsBcnData.rateMessageCount + 1) % aprsBcnData.settings.messageInterval;
|
|
#endif
|
|
|
|
if (anyManualMode)
|
|
{
|
|
if (aprsBcnData.settings.mode == APRS_BEACONING_MODE_PTT)
|
|
{
|
|
ticksTimerStart(&aprsBcnData.nextBeaconTimer, (initialIntervalsInSecs[aprsBcnData.settings.initialInterval] * MILLISECS_PER_SEC));
|
|
}
|
|
|
|
memcpy(&aprsBcnData.previousLocation, &aprsBcnData.currentLocation, sizeof(aprsBeaconingLocation_t));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool aprsBeaconingIsTransmitting(void)
|
|
{
|
|
return (((aprsTxProgress == APRS_TX_IDLE) && (aprsBcnData.hasDoneQSY == false)) == false);
|
|
}
|
|
|
|
bool aprsBeaconingForcedManualBeaconingTriggered(void)
|
|
{
|
|
return aprsBcnData.forcedManualBeaconing;
|
|
}
|
|
|
|
bool aprsBeaconingOnDifferentFrequency(void)
|
|
{
|
|
return ((settingsIsOptionBitSet(BIT_TX_INHIBIT) == false) &&
|
|
(aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF) &&
|
|
aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG) &&
|
|
(aprsBcnData.aprsConfig[APRS_CONFIG_CHANNEL].txFrequency != 0U));
|
|
}
|
|
|
|
bool aprsBeaconingHasSatelliteConfiguration(void)
|
|
{
|
|
return (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_SATELLITE_CONFIG));
|
|
}
|
|
|
|
void aprsBeaconingUpdateConfigurationFromSystemSettings(void)
|
|
{
|
|
// Unpack
|
|
aprsBcnData.settings.mode = ((nonVolatileSettings.aprsBeaconingSettingsPart1[0] >> 29) & 0x7);
|
|
aprsBcnData.settings.smart.lowSpeed = ((nonVolatileSettings.aprsBeaconingSettingsPart1[0] >> 24) & 0x1F);
|
|
aprsBcnData.settings.smart.fastRate = ((nonVolatileSettings.aprsBeaconingSettingsPart1[0] >> 16) & 0xFF);
|
|
aprsBcnData.settings.smart.turnSlope = ((nonVolatileSettings.aprsBeaconingSettingsPart1[0] >> 8) & 0xFF);
|
|
aprsBcnData.settings.smart.turnTime = (nonVolatileSettings.aprsBeaconingSettingsPart1[0] & 0xFF);
|
|
|
|
uint8_t messageInterval = 0;
|
|
|
|
messageInterval = (((nonVolatileSettings.aprsBeaconingSettingsPart1[1] >> 21) & 0x1F) + APRS_BEACON_MESSAGE_INTERVAL_MIN);
|
|
aprsBcnData.settings.smart.slowRate = ((nonVolatileSettings.aprsBeaconingSettingsPart1[1] >> 14) & 0x7F);
|
|
aprsBcnData.settings.smart.highSpeed = ((nonVolatileSettings.aprsBeaconingSettingsPart1[1] >> 7) & 0x7F);
|
|
aprsBcnData.settings.smart.turnAngle = (nonVolatileSettings.aprsBeaconingSettingsPart1[1] & 0x7F);
|
|
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
aprsBcnData.settings.messageInterval = messageInterval;
|
|
#else
|
|
(void)messageInterval;
|
|
#endif
|
|
|
|
aprsBcnData.settings.power = ((nonVolatileSettings.aprsBeaconingSettingsPart2 >> 12) & 0x0F);
|
|
aprsBcnData.settings.state = ((nonVolatileSettings.aprsBeaconingSettingsPart2 >> 4) & 0xFF);
|
|
aprsBcnData.settings.initialInterval = (nonVolatileSettings.aprsBeaconingSettingsPart2 & 0x0F);
|
|
}
|
|
|
|
void aprsBeaconingUpdateSystemSettingsFromConfiguration(void)
|
|
{
|
|
uint8_t messageInterval = APRS_BEACON_MESSAGE_INTERVAL_MIN;
|
|
uint16_t v16;
|
|
uint32_t v32;
|
|
|
|
// Pack
|
|
v32 = (((aprsBcnData.settings.mode & 0x7) << 29) | ((aprsBcnData.settings.smart.lowSpeed & 0x1F) << 24) |
|
|
(aprsBcnData.settings.smart.fastRate << 16) | (aprsBcnData.settings.smart.turnSlope << 8) |
|
|
aprsBcnData.settings.smart.turnTime);
|
|
|
|
settingsSet(nonVolatileSettings.aprsBeaconingSettingsPart1[0], v32);
|
|
|
|
#if defined(RATE_MESSAGE_FEATURE)
|
|
messageInterval = (aprsBcnData.settings.messageInterval - APRS_BEACON_MESSAGE_INTERVAL_MIN);
|
|
#endif
|
|
|
|
v32 = (((messageInterval & 0x1F) << 21) | ((aprsBcnData.settings.smart.slowRate & 0x7F) << 14) |
|
|
((aprsBcnData.settings.smart.highSpeed & 0x7F) << 7) | (aprsBcnData.settings.smart.turnAngle & 0x7F));
|
|
|
|
settingsSet(nonVolatileSettings.aprsBeaconingSettingsPart1[1], v32);
|
|
|
|
v16 = (((aprsBcnData.settings.power & 0x0F) << 12) | ((aprsBcnData.settings.state & 0xFF) << 4) | (aprsBcnData.settings.initialInterval & 0x0F));
|
|
|
|
settingsSet(nonVolatileSettings.aprsBeaconingSettingsPart2, v16);
|
|
}
|
|
|
|
void aprsBeaconingGetSettings(aprsBeaconingSettings_t *dest)
|
|
{
|
|
memcpy(dest, &aprsBcnData.settings, sizeof(aprsBeaconingSettings_t));
|
|
}
|
|
|
|
void aprsBeaconingSetSettings(aprsBeaconingSettings_t *src)
|
|
{
|
|
if (memcmp(src, &aprsBcnData.settings, sizeof(aprsBeaconingSettings_t)) != 0)
|
|
{
|
|
aprsBeaconingStop();
|
|
memcpy(&aprsBcnData.settings, src, sizeof(aprsBeaconingSettings_t));
|
|
aprsBeaconingUpdateSystemSettingsFromConfiguration();
|
|
aprsBeaconingStart();
|
|
}
|
|
}
|
|
|
|
void aprsBeaconingTick(uiEvent_t *ev)
|
|
{
|
|
if (aprsTxProgress != APRS_TX_IDLE)
|
|
{
|
|
aprsBeaconingTxStateTick(ev);
|
|
return;
|
|
}
|
|
|
|
int currentMenu = menuSystemGetCurrentMenuNumber();
|
|
|
|
// Do not automatically beaconing while in Satellite or TX screens
|
|
if ((currentMenu == MENU_SATELLITE) || (currentMenu == UI_TX_SCREEN) || (currentMenu == UI_HOTSPOT_MODE) || (currentMenu == UI_CPS))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (((uiDataGlobal.Scan.active == false) && (aprsBcnData.settings.mode != APRS_BEACONING_MODE_OFF) && aprsBeaconingStateEnabled(APRS_BEACONING_STATE_ENABLED))
|
|
&& aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_APRS_CONFIG) &&
|
|
(settingsIsOptionBitSet(BIT_TX_INHIBIT) == false))
|
|
{
|
|
if (ticksTimerHasExpired(&aprsBcnData.checkTimer))
|
|
{
|
|
bool locFromGPS = aprsBeaconingStateEnabled(APRS_BEACONING_STATE_LOCATION_FROM_GPS);
|
|
bool gpsHasPVT = (locFromGPS ? gpsPVTIsValid() : false);
|
|
bool forceEntering = (locFromGPS ? ((isnan(aprsBcnData.fixedLocationLat) == 0) ? false : true) : true); // every second check when GPS is OFF
|
|
uint32_t rearmTimerMilliseconds = MILLISECS_PER_SEC;
|
|
|
|
if (gpsHasPVT || forceEntering)
|
|
{
|
|
bool gpsFix = (locFromGPS ? gpsFixIsValid() : true);
|
|
bool checkForBeaconing = (locFromGPS ? ((isnan(aprsBcnData.fixedLocationLat) == 0) ? gpsFix : true) : true);
|
|
|
|
// Reset stored location when the Fix is lost or just acquired
|
|
if (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_LOCATION_FROM_GPS))
|
|
{
|
|
if (((gpsFix == false) && aprsBeaconingLocationIsValid(&aprsBcnData.previousLocation)) ||
|
|
(gpsFix && (aprsBeaconingStateEnabled(APRS_BEACONING_STATE_HAS_GPS_FIX) == false)))
|
|
{
|
|
aprsBeaconingInvalidateLocation(&aprsBcnData.previousLocation);
|
|
aprsBeaconingStateSetEnable(APRS_BEACONING_STATE_HAS_GPS_FIX, gpsFix);
|
|
}
|
|
}
|
|
|
|
if (checkForBeaconing)
|
|
{
|
|
aprsBcnData.currentLocation.time = ticksGetMillis();
|
|
|
|
if (locFromGPS)
|
|
{
|
|
aprsBcnData.currentLocation.coords.latitude =
|
|
#if defined(HAS_GPS)
|
|
gpsFix ? gpsData.LatitudeHiRes :
|
|
#endif
|
|
aprsGetFixedPositionLatitude();
|
|
|
|
aprsBcnData.currentLocation.coords.longitude =
|
|
#if defined(HAS_GPS)
|
|
gpsFix ? gpsData.LongitudeHiRes :
|
|
#endif
|
|
aprsGetFixedPositionLongitude();
|
|
|
|
aprsBcnData.currentLocation.bearing =
|
|
#if defined(HAS_GPS)
|
|
#if defined(APRS_USE_COURSETO_FOR_BEARING)
|
|
(gpsFix && (gpsData.SpeedInHundredthKn > SMART_BEACONING_SPEED_MIN)) ? aprsBeaconingGetBearing() :
|
|
#else
|
|
(gpsFix && (gpsData.SpeedInHundredthKn > SMART_BEACONING_SPEED_MIN)) ? gpsData.CourseInHundredthDeg :
|
|
#endif
|
|
#endif
|
|
0U;
|
|
aprsBcnData.currentLocation.speed =
|
|
#if defined(HAS_GPS)
|
|
(gpsFix && (gpsData.SpeedInHundredthKn > GPS_SPEED_THRESHOLD_MIN)) ? gpsData.SpeedInHundredthKn :
|
|
#endif
|
|
0U;
|
|
}
|
|
else
|
|
{
|
|
aprsBcnData.currentLocation.coords.latitude = aprsGetFixedPositionLatitude();
|
|
aprsBcnData.currentLocation.coords.longitude = aprsGetFixedPositionLongitude();
|
|
aprsBcnData.currentLocation.bearing = 0U;
|
|
aprsBcnData.currentLocation.speed = 0U; // Fixed position
|
|
}
|
|
|
|
// No position is usable (GPS/Channel and settings ones), don't do anything for now.
|
|
if (isnan(aprsBcnData.currentLocation.coords.latitude) != 0)
|
|
{
|
|
aprsBeaconingInvalidateLocation(&aprsBcnData.currentLocation);
|
|
aprsBeaconingInvalidateLocation(&aprsBcnData.previousLocation);
|
|
aprsBeaconingInvalidateFixedPosition();
|
|
goto exitRearmTimer;
|
|
}
|
|
|
|
if (aprsBcnData.triggerBeaconing == false)
|
|
{
|
|
if (aprsBcnData.settings.mode == APRS_BEACONING_MODE_SMART_BEACONING)
|
|
{
|
|
if (aprsSmartBeaconingCheck())
|
|
{
|
|
aprsBcnData.triggerBeaconing = true;
|
|
}
|
|
}
|
|
else if ((aprsBcnData.settings.mode == APRS_BEACONING_MODE_AUTO) && ticksTimerHasExpired(&aprsBcnData.nextBeaconTimer))
|
|
{
|
|
aprsBcnData.triggerBeaconing = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aprsBcnData.triggerBeaconing)
|
|
{
|
|
bool beaconSent = false;
|
|
|
|
// in MANUAL and PTT modes, we just need the updated position (even on fixed one, as aprsBeaconingResetTimers() also invalidate positions).
|
|
if ((aprsBcnData.settings.mode == APRS_BEACONING_MODE_AUTO) || (aprsBcnData.settings.mode == APRS_BEACONING_MODE_SMART_BEACONING))
|
|
{
|
|
beaconSent = aprsBeaconingSendBeacon(false, false);
|
|
|
|
if (beaconSent && (aprsBcnData.settings.mode != APRS_BEACONING_MODE_SMART_BEACONING)) // Pure time driven
|
|
{
|
|
double dist = 0.0;
|
|
|
|
if (aprsBeaconingLocationIsValid(&aprsBcnData.currentLocation) && aprsBeaconingLocationIsValid(&aprsBcnData.previousLocation))
|
|
{
|
|
dist = distanceBetweenTwoCoords(aprsBcnData.currentLocation.coords.latitude, aprsBcnData.currentLocation.coords.longitude, aprsBcnData.previousLocation.coords.latitude, aprsBcnData.previousLocation.coords.longitude, false);
|
|
}
|
|
|
|
ticksTimerStart(&aprsBcnData.nextBeaconTimer, ((initialIntervalsInSecs[aprsBcnData.settings.initialInterval] * MILLISECS_PER_SEC) * aprsBcnData.decayMult));
|
|
|
|
// Reset the Decay mult if we moved outside a radius of APRS_BEACON_DECAY_RESET_DISTANCE_MIN meters.
|
|
aprsBeaconingDecayTick((dist > APRS_BEACON_DECAY_RESET_DISTANCE_MIN));
|
|
}
|
|
}
|
|
|
|
// store current position as previous one for the next run
|
|
if (beaconSent)
|
|
{
|
|
aprsBcnData.triggerBeaconing = false;
|
|
memcpy(&aprsBcnData.previousLocation, &aprsBcnData.currentLocation, sizeof(aprsBeaconingLocation_t));
|
|
aprsBcnData.beaconingFailureCounter = 0U;
|
|
}
|
|
else
|
|
{
|
|
if (aprsTxProgress == APRS_TX_FAILED_WAITING_TO_BEACON)
|
|
{
|
|
aprsBcnData.beaconingFailureCounter = SAFE_MIN((aprsBcnData.beaconingFailureCounter + 1), APRS_BEACONING_FAILURE_MAX);
|
|
rearmTimerMilliseconds = (MILLISECS_PER_SEC * (aprsBcnData.beaconingFailureCounter * 2));
|
|
}
|
|
}
|
|
}
|
|
|
|
exitRearmTimer:
|
|
ticksTimerStart(&aprsBcnData.checkTimer, rearmTimerMilliseconds); // 1s timer
|
|
}
|
|
}
|
|
}
|
|
#endif // PLATFORM_GD77S
|