FreeTRX/MDUV380_firmware/application/include/user_interface/uiUtilities.h
Marcus Kida f67bb2fade Mute: toggle audio mute with SK2 short-tap, with LED + header indicator
Add a one-tap audio mute on the VFO and Channel screens:

- SK2 short-tap toggles mute via the built-in audioAmpMute()/audioAmpIsMuted().
  The tap is only consumed by the existing "set TX TG to incoming" behaviour
  while a DMR QSO is on screen, so mute works everywhere else.
- Header: a bold "M" is shown just left of the battery while muted (the
  firmware's built-in strike-through over the mode text also still shows).
- LED: flashes red for 0.5s every 2s while muted (main loop), overriding the
  normal RX/TX LED.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:41:29 +02:00

350 lines
14 KiB
C

/*
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
* Daniel Caujolle-Bert, F1RMB
*
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef _OPENGD77_UIUTILITIES_H_
#define _OPENGD77_UIUTILITIES_H_
#include <math.h>
#include "user_interface/uiGlobals.h"
#include "user_interface/menuSystem.h"
#include "functions/settings.h"
#define COMPUTE_BUILD_YEAR \
( \
(__DATE__[ 7] - '0') * 1000 + \
(__DATE__[ 8] - '0') * 100 + \
(__DATE__[ 9] - '0') * 10 + \
(__DATE__[10] - '0') \
)
#define COMPUTE_BUILD_DAY \
( \
((__DATE__[4] >= '0') ? (__DATE__[4] - '0') * 10 : 0) + \
(__DATE__[5] - '0') \
)
#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n')
#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F')
#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r')
#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p')
#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y')
#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n')
#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l')
#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u')
#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S')
#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O')
#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N')
#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D')
#define COMPUTE_BUILD_MONTH \
( \
(BUILD_MONTH_IS_JAN) ? 1 : \
(BUILD_MONTH_IS_FEB) ? 2 : \
(BUILD_MONTH_IS_MAR) ? 3 : \
(BUILD_MONTH_IS_APR) ? 4 : \
(BUILD_MONTH_IS_MAY) ? 5 : \
(BUILD_MONTH_IS_JUN) ? 6 : \
(BUILD_MONTH_IS_JUL) ? 7 : \
(BUILD_MONTH_IS_AUG) ? 8 : \
(BUILD_MONTH_IS_SEP) ? 9 : \
(BUILD_MONTH_IS_OCT) ? 10 : \
(BUILD_MONTH_IS_NOV) ? 11 : \
(BUILD_MONTH_IS_DEC) ? 12 : \
/* error default */ 99 \
)
#define COMPUTE_BUILD_HOUR ((__TIME__[0] - '0') * 10 + __TIME__[1] - '0')
#define COMPUTE_BUILD_MIN ((__TIME__[3] - '0') * 10 + __TIME__[4] - '0')
#define COMPUTE_BUILD_SEC ((__TIME__[6] - '0') * 10 + __TIME__[7] - '0')
#define BUILD_DATE_IS_BAD (__DATE__[0] == '?')
#define BUILD_YEAR ((BUILD_DATE_IS_BAD) ? 99 : COMPUTE_BUILD_YEAR)
#define BUILD_MONTH ((BUILD_DATE_IS_BAD) ? 99 : COMPUTE_BUILD_MONTH)
#define BUILD_DAY ((BUILD_DATE_IS_BAD) ? 99 : COMPUTE_BUILD_DAY)
#define BUILD_TIME_IS_BAD (__TIME__[0] == '?')
#define BUILD_HOUR ((BUILD_TIME_IS_BAD) ? 99 : COMPUTE_BUILD_HOUR)
#define BUILD_MIN ((BUILD_TIME_IS_BAD) ? 99 : COMPUTE_BUILD_MIN)
#define BUILD_SEC ((BUILD_TIME_IS_BAD) ? 99 : COMPUTE_BUILD_SEC)
#define BUILD_DAY_CH0 ((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')
#define BUILD_DAY_CH1 (__DATE__[ 5])
#if defined(PLATFORM_GD77S)
#define ANNOUNCE_STATIC
#else
#define ANNOUNCE_STATIC static
#endif
#if defined(PLATFORM_MD9600)
#define CLICK_DISCRIMINATOR 50U
#endif
#if defined(PLATFORM_GD77S)
// Displaying ANNOUNCE_STATIC here is a bit overkill but it makes things clearer.
ANNOUNCE_STATIC void announceRadioMode(bool voicePromptWasPlaying);
ANNOUNCE_STATIC void announceZoneName(bool voicePromptWasPlaying);
ANNOUNCE_STATIC void announceContactNameTgOrPc(bool voicePromptWasPlaying);
ANNOUNCE_STATIC void announcePowerLevel(bool voicePromptWasPlaying);
void announceEcoLevel(bool voicePromptWasPlaying);
ANNOUNCE_STATIC void announceBatteryPercentage(void);
ANNOUNCE_STATIC void announceTS(void);
ANNOUNCE_STATIC void announceCC(void);
ANNOUNCE_STATIC void announceChannelName(bool voicePromptWasPlaying);
ANNOUNCE_STATIC void announceFrequency(void);
ANNOUNCE_STATIC void announceVFOChannelName(void);
ANNOUNCE_STATIC void announceVFOAndFrequency(bool announceVFOName);
ANNOUNCE_STATIC void announceSquelchLevel(bool voicePromptWasPlaying);
#endif
typedef enum
{
DIRECTION_NONE = 0U,
DIRECTION_TRANSMIT,
DIRECTION_RECEIVE
} Direction_t;
typedef enum
{
PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ_AND_MODE,
PROMPT_SEQUENCE_ZONE_NAME_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE,
PROMPT_SEQUENCE_ZONE_NAME_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE_AND_TS_AND_CC,
PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE_AND_TS_AND_CC,
PROMPT_SEQUENCE_CHANNEL_NAME_AND_CONTACT_OR_VFO_FREQ_AND_MODE,
PROMPT_SEQUENCE_CHANNEL_NAME_OR_VFO_FREQ,
PROMPT_SEQUENCE_VFO_FREQ_UPDATE,
PROMPT_SEQUENCE_ZONE,
PROMPT_SEQUENCE_MODE,
PROMPT_SEQUENCE_CONTACT_TG_OR_PC,
PROMPT_SEQUENCE_TS,
PROMPT_SEQUENCE_CC,
PROMPT_SEQUENCE_POWER,
PROMPT_SEQUENCE_BATTERY,
PROMPT_SQUENCE_SQUELCH,
PROMPT_SEQUENCE_TEMPERATURE,
PROMPT_SEQUENCE_DIRECTION_TX,
PROMPT_SEQUENCE_DIRECTION_RX,
NUM_PROMPT_SEQUENCES
} voicePromptItem_t;
typedef enum
{
DISPLAY_INFO_CONTACT_INVERTED = 0U,
DISPLAY_INFO_CONTACT,
DISPLAY_INFO_CONTACT_OVERRIDE_FRAME,
DISPLAY_INFO_CHANNEL,
DISPLAY_INFO_CHANNEL_INVERTED,
DISPLAY_INFO_CHANNEL_DETAILS,
DISPLAY_INFO_TX_TIMER,
DISPLAY_INFO_ZONE
} displayInformation_t;
typedef struct
{
uint32_t entries;
uint8_t contactLength;
uint32_t slices[ID_SLICES]; // [0] is min availabel ID, [ID_SLICES - 1] is max available ID
uint32_t IDsPerSlice;
} dmrIDsCache_t;
#define TS_NO_OVERRIDE 0
void tsSetManualOverride(Channel_t chan, int8_t ts);
void tsSetFromContactOverride(Channel_t chan, CodeplugContact_t *contact);
int8_t tsGetManualOverride(Channel_t chan);
int8_t tsGetManualOverrideFromCurrentChannel(void);
bool tsIsManualOverridden(Channel_t chan);
void tsSetContactHasBeenOverriden(Channel_t chan, bool isOverriden);
bool tsIsContactHasBeenOverridden(Channel_t chan);
bool tsIsContactHasBeenOverriddenFromCurrentChannel(void);
bool isQSODataAvailableForCurrentTalker(void);
//int alignFrequencyToStep(int freq, int step);
char *chomp(char *str);
int32_t getFirstSpacePos(const char *str);
void getCallsignOnly(char *dest, const char *src);
void dmrIDCacheInit(void);
void dmrIDCacheClear(void);
uint32_t dmrIDCacheGetCount(void);
bool dmrIDLookup(uint32_t targetId, dmrIdDataStruct_t *foundRecord);
bool contactIDLookup(uint32_t id, ContactCalltype_t calltype, char *buffer);
void uiUtilityRenderQSOData(void);
void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning, bool forceBatteryDisplay);
void uiUtilityRedrawHeaderOnly(bool isVFODualWatchScanning, bool isVFOSweepScanning, bool forceBatteryDisplay);
LinkItem_t *lastHeardFindInList(uint32_t id);
void lastHeardInitList(void);
void lastHeardClearWorkingTAData(void);
bool lastHeardListUpdate(uint8_t *dmrDataBuffer, bool forceOnHotspot);
void lastHeardClearLastID(void);
void uiUtilityDrawRSSIBarGraph(void);
void uiUtilityDrawFMMicLevelBarGraph(void);
void uiUtilityDrawDMRMicLevelBarGraph(void);
void setOverrideTGorPC(uint32_t tgOrPc, bool privateCall);
void uiUtilityDisplayFrequency(uint8_t y, bool isTX, bool hasFocus, uint32_t frequency, bool displayVFOChannel, bool isScanMode, uint8_t dualWatchVFO);
uint16_t cssGetToneFromIndex(uint8_t index, CodeplugCSSTypes_t type);
uint8_t cssGetToneIndex(uint16_t tone, CodeplugCSSTypes_t type);
void cssIncrement(uint16_t *tone, uint8_t *index, uint8_t step, CodeplugCSSTypes_t *type, bool loop, bool stayInCSSType);
void cssDecrement(uint16_t *tone, uint8_t *index, uint8_t step, CodeplugCSSTypes_t *type, bool loop, bool stayInCSSType);
size_t dcsPrintf(char *dest, size_t maxLen, char *prefix, uint16_t tone);
void freqEnterReset(void);
int freqEnterRead(int startDigit, int endDigit, bool simpleDigits);
int getBatteryPercentage(void);
void getBatteryVoltage(int *volts, int *mvolts);
bool decreasePowerLevel(void);
bool increasePowerLevel(bool allowFullPower);
void announceChar(char ch);
void buildCSSCodeVoicePrompts(uint16_t tone, CodeplugCSSTypes_t cssType, Direction_t direction, bool announceType);
void announceCSSCode(uint16_t tone, CodeplugCSSTypes_t cssType, Direction_t direction, bool announceType, audioPromptThreshold_t immediateAnnounceThreshold);
void announceItemWithInit(bool init, voicePromptItem_t item, audioPromptThreshold_t immediateAnnounceThreshold);
void announceItem(voicePromptItem_t item, audioPromptThreshold_t immediateAnnouceThreshold);
void promptsPlayNotAfterTx(void);
void uiUtilityBuildTgOrPCDisplayName(char *nameBuf, int bufferLen);
void acceptPrivateCall(uint32_t id, int timeslot);
bool rebuildVoicePromptOnExtraLongSK1(uiEvent_t *ev);
bool repeatVoicePromptOnSK1(uiEvent_t *ev);
bool handleMonitorMode(uiEvent_t *ev);
void toggleAudioMute(void);
void uiUtilityDisplayInformation(const char *str, displayInformation_t line, int8_t yOverride);
void uiUtilityRenderQSODataAndUpdateScreen(void);
// QuickKeys
void saveQuickkeyMenuIndex(char key, uint8_t menuId, uint8_t entryId, uint8_t function);
void saveQuickkeyMenuLongValue(char key, uint8_t menuId, uint16_t entryiI);
void saveQuickkeyContactIndex(char key, uint16_t contactId);
void cssIncrement2(uint16_t *tone, uint8_t *index, CodeplugCSSTypes_t *type, bool loop, bool stayInCSSType);
void cssIncrement(uint16_t *tone, uint8_t *index, uint8_t step, CodeplugCSSTypes_t *type, bool loop, bool stayInCSSType);
uint16_t cssGetTone(uint8_t index, CodeplugCSSTypes_t type);
bool uiQuickKeysShowChoices(char *buf, const int bufferLen, const char *menuTitle);
bool uiQuickKeysIsStoring(uiEvent_t *ev);
void uiQuickKeysStore(uiEvent_t *ev, menuStatus_t *status);
// DTMF contact sequences
void dtmfSequenceReset(void);
bool dtmfSequenceIsKeying(void);
void dtmfSequencePrepare(uint8_t *seq, bool autoStart);
void dtmfSequenceStart(void);
void dtmfSequenceStop(void);
void dtmfSequenceTick(bool popPreviousMenuOnEnding);
void resetOriginalSettingsData(void);
void showErrorMessage(const char *message);
const char *getPowerLevel(uint8_t level);
const char *getPowerLevelUnit(uint8_t level);
uint8_t *coordsToMaidenhead(uint8_t *maidenheadBuffer, double longitude, double latitude);
void buildLocationAndMaidenheadStrings(char *locationBufferOrNull, char *maidenheadBufferOrNull, bool locIsValid);
double latLonFixed32ToDouble(uint32_t fixedVal);
uint32_t latLonDoubleToFixed32(double value);
double latLonFixed24ToDouble(uint32_t fixedVal);
uint32_t latLonDoubleToFixed24(double value);
double distanceToLocation(double latitude, double longitude);
double distanceBetweenTwoCoords(double lat1, double lon1, double lat2, double lon2, bool inKm);
double courseTo(double lat1, double lon1, double lat2, double lon2);
void uiSetUTCDateTimeInSecs(time_t_custom UTCdateTimeInSecs);
#ifdef USE_RTC
uint8_t getDayOfTheWeek(uint8_t day, uint8_t month, uint8_t year);
time_t getEpochTime(RTC_TimeTypeDef *rtcTime, RTC_DateTypeDef *rtcDate);
#endif
struct tm *gmtime_r_Custom(const time_t_custom *__restrict tim_p, struct tm *__restrict res);
time_t_custom mktime_custom(const struct tm * tb);
#if defined(PLATFORM_MD9600) || defined(PLATFORM_MDUV380) || defined(PLATFORM_MD380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
time_t_custom getRtcTime_custom(void);
void setRtc_custom(time_t_custom tc);
#endif
#if ! defined(PLATFORM_GD77S)
#define DAYTIME_THEME_TIMER_INTERVAL ((60 * 1000) * 5) // 5 minutes
extern ticksTimer_t daytimeThemeTimer;
#define HOURS(h) ((int)(floor(h)))
#define MINUTES(h) ((int)(60*(h-floor(h))))
/* A macro to compute the number of days elapsed since 2000 Jan 0.0 */
/* (which is equal to 1999 Dec 31, 0h UT) */
/* Dan R sez: This is some pretty fucking high magic. */
#define DAYS_SINCE_2000_JAN_0(y,m,d) (367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L)
/* Some conversion factors between radians and degrees */
#define RADEG (180.0 / M_PI)
#define DEGRAD (M_PI / 180.0)
/* The trigonometric functions in degrees */
#define SIND(x) sin((x)*DEGRAD)
#define COSD(x) cos((x)*DEGRAD)
#define TAND(x) tan((x)*DEGRAD)
#define ATAND(x) (RADEG*atan(x))
#define ASIND(x) (RADEG*asin(x))
#define ACOSD(x) (RADEG*acos(x))
#define ATAN2D(y,x) (RADEG*atan2(y,x))
int sunriset(int year, int month, int day, double lon, double lat, double altit, int upper_limb, double *rise, double *set);
void daytimeThemeChangeUpdate(bool startup);
void daytimeThemeApply(DayTime_t daytime);
void daytimeThemeTick(void);
void uiChannelModeOrVFOModeThemeDaytimeChange(bool toggle, bool isChannelMode);
#else
#define uiChannelModeOrVFOModeThemeDaytimeChange(x, y) do { } while(0)
#endif
bool txInhibitCheckAndWarn(void);
bool isTransmittingIgnoringAPRSBeaconing(void);
#if defined(STM32F405xx) && ! defined(PLATFORM_MD9600)
uint32_t cpuGetSignature(void);
uint32_t cpuGetRevision(void);
uint32_t cpuGetPackage(void);
int32_t cpuGetFlashSize(void);
#endif
#endif