Skip to content

Commit eb0e8d5

Browse files
authored
Merge pull request #253 from wasatch-dev/satalerts
Add support for Sat Alerts via hams.at; add satellite co-visibility p…
2 parents 547b6dd + 2ee058a commit eb0e8d5

15 files changed

Lines changed: 2176 additions & 38 deletions

ESPHamClock/ESPHamClock.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,6 +1714,7 @@ void drawAllSymbols()
17141714
drawDXClusterSpotsOnMap();
17151715
drawADIFSpotsOnMap();
17161716
drawDXPedsOnMap();
1717+
drawHamsatOnMap();
17171718
drawStormsOnMap();
17181719
drawLaunchesOnMap();
17191720
drawActiveNetsOnMap();
@@ -1885,7 +1886,10 @@ void drawDEFormatMenu()
18851886
// number of core menu items
18861887
#define N_DEFMT_CORE 8
18871888

1888-
MenuItem mitems[N_DEFMT_CORE+N_PANE_0_CH] = {
1889+
// PLOT_CH_SATACT can't join PANE_0_CH_MASK -- that's a 32-bit bitmask constant and SATACT's
1890+
// ordinal (32) can never fit in one, by the same design as everywhere else PLOTBIT() is used.
1891+
// it's offered as one extra, separately-handled menu item instead.
1892+
MenuItem mitems[N_DEFMT_CORE+N_PANE_0_CH+1] = {
18891893

18901894
// outer menu is whether to display the DE pane or use both DE+DX for a pane choice
18911895
{MENU_1OFN, !SHOWING_PANE_0(), 1, mi, "DE format:", NULL},
@@ -1919,11 +1923,22 @@ void drawDEFormatMenu()
19191923
PlotPane pp = findPaneForChoice ((PlotChoice)plot_n);
19201924
bool available = plotChoiceIsAvailable((PlotChoice)plot_n) && (pp == PANE_NONE || pp == PANE_0);
19211925
MenuFieldType type = available ? MENU_AL1OFN : MENU_IGNORE;
1922-
mitems[N_DEFMT_CORE+i] = {type, !!(plot_rotset[PANE_0] & (1<<plot_n)), 3, Mi, plot_names[plot_n], 0};
1926+
mitems[N_DEFMT_CORE+i] = {type, !!(plot_rotset[PANE_0] & PLOTBIT(plot_n)), 3, Mi, plot_names[plot_n], 0};
19231927
}
19241928
if (pane_0_bits != 0)
19251929
fatalError ("drawDEFormatMenu() %d %d 0x%x\n", pane_0_bits, N_PANE_0_CH, PANE_0_CH_MASK);
19261930

1931+
// extra SATACT item -- can't be represented in plot_rotset (see comment above), so its
1932+
// "currently set" state is determined the same way paneHasChoice() checks a solo choice
1933+
const int SATACT_MI = N_DEFMT_CORE + N_PANE_0_CH;
1934+
{
1935+
PlotPane pp = findPaneForChoice (PLOT_CH_SATACT);
1936+
bool available = plotChoiceIsAvailable (PLOT_CH_SATACT) && (pp == PANE_NONE || pp == PANE_0);
1937+
MenuFieldType type = available ? MENU_AL1OFN : MENU_IGNORE;
1938+
bool is_set = (plot_ch[PANE_0] == PLOT_CH_SATACT && plot_rotset[PANE_0] == 0);
1939+
mitems[SATACT_MI] = {type, is_set, 3, Mi, plot_names[PLOT_CH_SATACT], 0};
1940+
}
1941+
19271942
// create a box for the menu
19281943
SBox menu_b;
19291944
menu_b.x = de_info_b.x + 4;
@@ -1965,18 +1980,26 @@ void drawDEFormatMenu()
19651980
uint32_t new_rotset = 0;
19661981
for (int i = 0; i < N_PANE_0_CH; i++) {
19671982
if (mitems[N_DEFMT_CORE+i].set)
1968-
new_rotset |= (1<<menu_ch[i]);
1983+
new_rotset |= PLOTBIT(menu_ch[i]);
19691984
}
19701985

1971-
// might not be any if user clicked this section but made no choice
1972-
if (new_rotset != 0) {
1986+
bool satact_set = mitems[SATACT_MI].set;
1987+
1988+
if (satact_set && new_rotset == 0) {
1989+
1990+
// SATACT alone: solo, non-rotating choice -- can't be represented in plot_rotset
1991+
plot_ch[PANE_0] = PLOT_CH_SATACT;
1992+
plot_rotset[PANE_0] = 0;
1993+
1994+
} else if (new_rotset != 0) {
19731995

1974-
// ok!
1996+
// one or more mask-based choices (SATACT, if also checked, can't join a
1997+
// multi-item rotation set the way these can, so it's dropped in that case)
19751998
plot_rotset[PANE_0] = new_rotset;
19761999

19772000
// pick any one as current
19782001
for (int i = 0; i < PLOT_CH_N; i++) {
1979-
if (plot_rotset[PANE_0] & (1 << i)) {
2002+
if (plot_rotset[PANE_0] & PLOTBIT(i)) {
19802003
plot_ch[PANE_0] = (PlotChoice) i;
19812004
break;
19822005
}

ESPHamClock/HamClock.h

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,8 @@ typedef enum {
418418
X(PLOT_CH_ACTIVENETS, "Nets") \
419419
X(PLOT_CH_LAUNCHES, "Launches") \
420420
X(PLOT_CH_HFCOND, "HF_Bands") \
421-
X(PLOT_CH_VHFCOND, "VHF_Cond")
421+
X(PLOT_CH_VHFCOND, "VHF_Cond") \
422+
X(PLOT_CH_SATACT, "Sat_Alerts")
422423

423424
#define X(a,b) a, // expands PLOTNAMES to each enum and comma
424425
typedef enum {
@@ -682,12 +683,16 @@ extern SBox de_info_b; // de info pane
682683
extern SBox map_b; // main map
683684
extern SBox view_btn_b; // map view menu button
684685
extern SBox motd_btn_b; // MOTD mailbox icon (next to UTC button)
686+
extern SBox adsb_btn_b; // ADS-B airplane icon (next to MOTD icon)
685687

686688
// MOTD (Message of the Day) functions
687689
extern void checkMOTD (void);
688690
extern bool motdIsPresent (void);
689691
extern void drawMOTDIcon (void);
690692
extern void motdClicked (void);
693+
694+
// ADS-B airplane icon -- opens https://adsb.lol
695+
extern void drawADSBIcon (void);
691696
extern SBox dx_maid_b; // dx maidenhead pick
692697
extern SBox de_maid_b; // de maidenhead pick
693698
extern SBox lkscrn_b; // screen lock icon button
@@ -1582,6 +1587,16 @@ extern void drawSatName(void);
15821587
extern void drawOneTimeDX(void);
15831588
extern void drawOneTimeDE(void);
15841589
extern bool setSatFromName (const char *new_name);
1590+
extern Satellite *lookupSatByName (const char *name, int *norad = NULL);
1591+
1592+
/*********************************************************************************************
1593+
*
1594+
* satsked.cpp
1595+
*
1596+
*/
1597+
1598+
extern void drawSatGroupSchedule (void);
1599+
extern void drawSatCoVis (void);
15851600
extern bool setSatFromTLE (const char *name, const char *t1, const char *t2);
15861601
extern bool initSat(void);
15871602
extern bool getSatNow (SatNow &satnow);
@@ -2110,6 +2125,16 @@ extern void reportEESize (uint16_t &ee_used, uint16_t &ee_size);
21102125

21112126
extern bool updateOnTheAir (const SBox &box, bool fresh);
21122127
extern bool checkOnTheAirTouch (const SCoord &s, const SBox &box);
2128+
2129+
/*********************************************************************************************
2130+
*
2131+
* hamsat.cpp
2132+
*
2133+
*/
2134+
2135+
extern bool updateHamsat (const SBox &box, bool fresh);
2136+
extern bool checkHamsatTouch (const SCoord &s, const SBox &box);
2137+
extern void drawHamsatOnMap (void);
21132138
extern bool getOnTheAirSpots (DXSpot **spp, uint8_t *nspotsp);
21142139
extern void drawOnTheAirSpotsOnMap (void);
21152140
extern bool getClosestOnTheAirSpot (LatLong &ll, DXSpot *sp, LatLong *llp);
@@ -2211,9 +2236,15 @@ extern uint32_t plot_rothold; // bitmask of PlotChoice in rotset bu
22112236
extern uint32_t plot_rotset[PANE_N]; // bitmask of each pane's PlotChoice rotation choices
22122237
// N.B. plot_rotset[i] must always include plot_ch[i] unless NONE
22132238

2214-
#define ROTHOLD_SET(pc) (plot_rothold |= (1<<(pc)))
2215-
#define ROTHOLD_CLR(pc) (plot_rothold &= ~(1<<(pc)))
2216-
#define ROTHOLD_TST(pc) ((plot_rothold & (1<<(pc))) != 0)
2239+
// plot_rotset/plot_rothold are only 32 bits wide, but PLOT_CH_N may exceed 32 (e.g. PLOT_CH_SATACT).
2240+
// PLOTBIT() maps any PlotChoice >= 32 to 0 instead of performing an out-of-range shift, which both
2241+
// avoids undefined behavior and, as a deliberate side effect, guarantees such choices can never be
2242+
// captured in a rotation set -- i.e. they can never participate in pane auto rotation.
2243+
#define PLOTBIT(pc) ((uint32_t)(pc) < 32 ? (1u << (uint32_t)(pc)) : 0u)
2244+
2245+
#define ROTHOLD_SET(pc) (plot_rothold |= PLOTBIT(pc))
2246+
#define ROTHOLD_CLR(pc) (plot_rothold &= ~PLOTBIT(pc))
2247+
#define ROTHOLD_TST(pc) ((plot_rothold & PLOTBIT(pc)) != 0)
22172248

22182249
#define SHOWING_PANE_0() (plot_ch[PANE_0] != PLOT_CH_NONE)
22192250
#define BOX_IS_PANE_0(b) ((b).w == PLOTBOX0_W && (b).h == PLOTBOX0_H)

ESPHamClock/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ OBJS = \
115115
dxpeds.o \
116116
dxpeds_hide.o \
117117
hamqsl.o \
118+
hamsat.o \
118119
hurricane.o \
119120
earthmap.o \
120121
earthsat.o \
@@ -155,6 +156,7 @@ OBJS = \
155156
runner.o \
156157
santa.o \
157158
sattool.o \
159+
satsked.o \
158160
scrollbar.o \
159161
scrollstate.o \
160162
sdo.o \

ESPHamClock/clocks.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44

55
#include "HamClock.h"
66

7+
// ADS-B airplane icon -- opens https://adsb.lol, sits just below the MOTD mailbox icon
8+
#define ADSB_ICON_W 14
9+
#define ADSB_ICON_H 14
10+
#define ADSB_ICON_GAP 6 // gap below MOTD icon
11+
SBox adsb_btn_b;
12+
713
// format flags
814
uint8_t de_time_fmt; // one of DETIME_*
915
uint8_t desrss, dxsrss; // show actual de/dx sun rise/set else time to
@@ -71,6 +77,43 @@ static void drawUTCButton()
7177

7278
// draw the MOTD mailbox icon to the left of the UTC button (or blank slot if no MOTD)
7379
drawMOTDIcon();
80+
81+
// draw the ADS-B airplane icon just below that -- always shown, opens adsb.lol when tapped
82+
drawADSBIcon();
83+
}
84+
85+
/* draw the ADS-B airplane icon immediately below the MOTD mailbox icon. always shown
86+
* (unlike the MOTD icon, which blanks itself when there's no message) -- tapping it opens
87+
* https://adsb.lol, a live ADS-B aircraft tracking site. should be called right after
88+
* drawMOTDIcon() since it positions itself relative to motd_btn_b.
89+
*/
90+
void drawADSBIcon()
91+
{
92+
adsb_btn_b.x = motd_btn_b.x;
93+
adsb_btn_b.y = motd_btn_b.y + motd_btn_b.h + ADSB_ICON_GAP;
94+
adsb_btn_b.w = ADSB_ICON_W;
95+
adsb_btn_b.h = ADSB_ICON_H;
96+
97+
tft.fillRect (adsb_btn_b.x, adsb_btn_b.y, adsb_btn_b.w, adsb_btn_b.h, RA8875_BLACK);
98+
99+
// airplane silhouette: fuselage nose-to-tail, wide wings ~1/3 back from the nose, a smaller
100+
// tail stabilizer near the back -- the classic top-down airplane pictogram, not a paper dart.
101+
// no icon font available, so this is built from basic line primitives, same approach
102+
// drawMOTDIcon() uses for its envelope shape.
103+
const uint16_t x = adsb_btn_b.x + 1;
104+
const uint16_t y = adsb_btn_b.y + 1;
105+
const uint16_t w = 12;
106+
const uint16_t h = 12;
107+
const uint16_t col = GRAY;
108+
109+
uint16_t nose_x = x + w, tail_x = x, mid_y = y + h/2;
110+
tft.drawLine (tail_x, mid_y, nose_x, mid_y, col); // fuselage
111+
tft.drawLine (nose_x, mid_y, nose_x-3, mid_y-1, col); // nose taper
112+
tft.drawLine (nose_x, mid_y, nose_x-3, mid_y+1, col);
113+
uint16_t wing_x = x + (w*5)/8;
114+
tft.drawLine (wing_x, y, wing_x, y+h-1, col); // main wings
115+
uint16_t tail_wing_x = x + w/6;
116+
tft.drawLine (tail_wing_x, mid_y-2, tail_wing_x, mid_y+2, col); // tail stabilizer
74117
}
75118

76119
/* function given to TimeLib's setSyncProvider() to resync its time base occasionally.
@@ -1166,6 +1209,15 @@ bool checkClockTouch (SCoord &s)
11661209
return (true);
11671210
}
11681211

1212+
// ADS-B airplane icon: always present, opens adsb.lol centered on DE's location
1213+
if (inBox (s, adsb_btn_b)) {
1214+
char url[100];
1215+
snprintf (url, sizeof(url), "https://adsb.lol/?lat=%.3f&lon=%.3f&zoom=10",
1216+
de_ll.lat_d, de_ll.lng_d);
1217+
openURL (url);
1218+
return (true);
1219+
}
1220+
11691221
if (inBox (s, auxtime_b)) {
11701222

11711223
runAuxTimeMenu();

ESPHamClock/earthsat.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,6 +1467,23 @@ static bool checkSatUpToDate (SatState &s, bool *updated)
14671467
return (true);
14681468
}
14691469

1470+
/* lightweight TLE lookup for a satellite NOT part of the globally-tracked sat_state[] --
1471+
* does not disturb sat_state[], the map display, or NVRAM. used by satsked.cpp's group
1472+
* schedule feature to get orbital elements for many satellites at once.
1473+
* returns a heap-allocated Satellite* the caller owns (delete when done), or NULL if the
1474+
* name isn't found or its TLE is unavailable/stale.
1475+
*/
1476+
Satellite *lookupSatByName (const char *name, int *norad)
1477+
{
1478+
SatState tmp{};
1479+
strncpySubChar (tmp.name, name, '_', ' ', NV_SATNAME_LEN);
1480+
if (!checkSatUpToDate (tmp, NULL))
1481+
return (NULL);
1482+
if (norad)
1483+
*norad = tmp.norad;
1484+
return (tmp.sat);
1485+
}
1486+
14701487
/* show pass time of sat_rs(void)
14711488
*/
14721489
static void drawSatRSEvents(SatState &s)
@@ -3842,6 +3859,8 @@ void drawDXSatMenu (const SCoord &s)
38423859
_SMI_ORBTRACK2,
38433860
_SMI_SATNOGS2,
38443861
_SMI_AMSAT_STATUS,
3862+
_SMI_GROUPSKED,
3863+
_SMI_COVIS,
38453864
_SMI_N,
38463865
};
38473866

@@ -3900,6 +3919,8 @@ void drawDXSatMenu (const SCoord &s)
39003919
{menu_sat2, false, 1, _DXS_INDENT2, "Show OrbTrack", NULL},
39013920
{menu_sat2, false, 1, _DXS_INDENT2, "Show SatNogs", NULL},
39023921
{MENU_1OFN, false, 1, _DXS_INDENT1, "Show Status", NULL},
3922+
{MENU_1OFN, false, 1, _DXS_INDENT1, "Group Schedule", NULL},
3923+
{MENU_1OFN, false, 1, _DXS_INDENT1, "Co-Visibility", NULL},
39033924
};
39043925

39053926
// box for menu
@@ -4045,6 +4066,14 @@ void drawDXSatMenu (const SCoord &s)
40454066
case _SMI_AMSAT_STATUS:
40464067
openURL ("https://www.amsat.org/status/");
40474068
break;
4069+
case _SMI_GROUPSKED:
4070+
drawSatGroupSchedule ();
4071+
initEarthMap ();
4072+
break;
4073+
case _SMI_COVIS:
4074+
drawSatCoVis ();
4075+
initEarthMap ();
4076+
break;
40484077
case _SMI_N:
40494078
// lint
40504079
fatalError ("sat menu bogus entry %d", i);

0 commit comments

Comments
 (0)