v1.5: World map background, LED heartbeat pulse, screenshot flash confirmation, splash screen resize
This commit is contained in:
@@ -15,6 +15,10 @@ namespace {
|
|||||||
uint32_t lastToggleMs = 0;
|
uint32_t lastToggleMs = 0;
|
||||||
Mode lastMode = Mode::Off;
|
Mode lastMode = Mode::Off;
|
||||||
|
|
||||||
|
bool heartbeatActive = false;
|
||||||
|
uint32_t heartbeatStartMs = 0;
|
||||||
|
constexpr uint32_t HEARTBEAT_PULSE_MS = 200;
|
||||||
|
|
||||||
void setAllOff() {
|
void setAllOff() {
|
||||||
digitalWrite(PIN_RED, HIGH);
|
digitalWrite(PIN_RED, HIGH);
|
||||||
digitalWrite(PIN_GREEN, HIGH);
|
digitalWrite(PIN_GREEN, HIGH);
|
||||||
@@ -30,10 +34,27 @@ void begin() {
|
|||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pulseHeartbeat(uint32_t nowMs) {
|
||||||
|
heartbeatActive = true;
|
||||||
|
heartbeatStartMs = nowMs;
|
||||||
|
}
|
||||||
|
|
||||||
bool update(Mode mode, uint32_t nowMs) {
|
bool update(Mode mode, uint32_t nowMs) {
|
||||||
if (!initialized) begin();
|
if (!initialized) begin();
|
||||||
|
|
||||||
if (mode == Mode::Off) {
|
if (mode == Mode::Off) {
|
||||||
|
if (heartbeatActive) {
|
||||||
|
if (nowMs - heartbeatStartMs < HEARTBEAT_PULSE_MS) {
|
||||||
|
digitalWrite(PIN_GREEN, LOW);
|
||||||
|
digitalWrite(PIN_RED, HIGH);
|
||||||
|
digitalWrite(PIN_BLUE, HIGH);
|
||||||
|
blinkState = false;
|
||||||
|
lastMode = mode;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
heartbeatActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
setAllOff();
|
setAllOff();
|
||||||
blinkState = false;
|
blinkState = false;
|
||||||
lastMode = mode;
|
lastMode = mode;
|
||||||
@@ -61,4 +82,14 @@ bool update(Mode mode, uint32_t nowMs) {
|
|||||||
return blinkState;
|
return blinkState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void flashWhite(uint32_t durationMs) {
|
||||||
|
if (!initialized) begin();
|
||||||
|
|
||||||
|
digitalWrite(PIN_RED, LOW);
|
||||||
|
digitalWrite(PIN_GREEN, LOW);
|
||||||
|
digitalWrite(PIN_BLUE, LOW);
|
||||||
|
delay(durationMs);
|
||||||
|
setAllOff();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,17 +7,19 @@
|
|||||||
namespace LedAlert {
|
namespace LedAlert {
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
Off, // keine LED
|
Off,
|
||||||
ProximityGreen, // Flugzeug innerhalb des Naeherungsradius -> gruen blinkt
|
ProximityGreen,
|
||||||
EmergencyRed, // Notfall-Squawk (7500/7600/7700) -> rot blinkt schneller, hat Vorrang
|
EmergencyRed,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Einmalig in setup() aufrufen.
|
|
||||||
void begin();
|
void begin();
|
||||||
|
|
||||||
// Haeufig aufrufen (z.B. alle 80-100ms). Schaltet die LED passend zum
|
|
||||||
// Modus an/aus (blinkend, volle Helligkeit) und gibt den aktuellen
|
|
||||||
// Blink-Zustand zurueck (true = LED gerade an), damit der Radar-
|
|
||||||
// Bildschirm z.B. den betroffenen Punkt synchron mitblinken lassen kann.
|
|
||||||
bool update(Mode mode, uint32_t nowMs);
|
bool update(Mode mode, uint32_t nowMs);
|
||||||
|
|
||||||
|
void pulseHeartbeat(uint32_t nowMs);
|
||||||
|
|
||||||
|
// Blockierendes, kurzes weisses Aufblitzen (alle 3 Farbkanaele an) als
|
||||||
|
// sofortige Bestaetigung fuer eine einmalige Nutzeraktion (z.B. "Cam"-
|
||||||
|
// Button getroffen). Bewusst blockierend (delay), da nur aus dem
|
||||||
|
// Haupt-Loop bei einem Tap aufgerufen, nicht aus der NetTask-Schleife.
|
||||||
|
void flashWhite(uint32_t durationMs = 150);
|
||||||
}
|
}
|
||||||
@@ -109,6 +109,8 @@ void updateStatusLine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void takeScreenshotWithFeedback() {
|
void takeScreenshotWithFeedback() {
|
||||||
|
LedAlert::flashWhite();
|
||||||
|
|
||||||
String filename = Screenshot::save(tft);
|
String filename = Screenshot::save(tft);
|
||||||
|
|
||||||
tft.fillRect(0, 0, Config::SCREEN_WIDTH, CONTENT_TOP, TFT_NAVY);
|
tft.fillRect(0, 0, Config::SCREEN_WIDTH, CONTENT_TOP, TFT_NAVY);
|
||||||
@@ -126,11 +128,6 @@ void takeScreenshotWithFeedback() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void haltWithSdRequiredScreen() {
|
void haltWithSdRequiredScreen() {
|
||||||
// Ohne SD-Karte kann SettingsStore::load() nie laufen, also ist auch die
|
|
||||||
// Invertierungs-Einstellung dieses Geraets unbekannt. Dieses konkrete
|
|
||||||
// CYD-Board braucht invertDisplay(true), um Farben korrekt darzustellen
|
|
||||||
// (ohne das erscheint Schwarz als Weiss und Gruen als Rosa/Magenta) -
|
|
||||||
// hier fest setzen, damit Schwarz/Gruen GARANTIERT korrekt aussieht.
|
|
||||||
tft.invertDisplay(true);
|
tft.invertDisplay(true);
|
||||||
|
|
||||||
tft.fillScreen(TFT_BLACK);
|
tft.fillScreen(TFT_BLACK);
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ namespace {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr int16_t ROW_H = 26;
|
constexpr int16_t ROW_H = 24;
|
||||||
constexpr int16_t ROW_GAP = 3;
|
constexpr int16_t ROW_GAP = 2;
|
||||||
constexpr int16_t ROW_START_Y = 24;
|
constexpr int16_t ROW_START_Y = 20;
|
||||||
|
|
||||||
Rect rowRect(uint8_t index) {
|
Rect rowRect(uint8_t index) {
|
||||||
return {10, (int16_t)(ROW_START_Y + index * (ROW_H + ROW_GAP)),
|
return {10, (int16_t)(ROW_START_Y + index * (ROW_H + ROW_GAP)),
|
||||||
@@ -44,16 +44,17 @@ void run(TFT_eSPI& tft) {
|
|||||||
Rect wifiBtn = rowRect(2);
|
Rect wifiBtn = rowRect(2);
|
||||||
Rect emergencyBtn = rowRect(3);
|
Rect emergencyBtn = rowRect(3);
|
||||||
Rect proximityBtn = rowRect(4);
|
Rect proximityBtn = rowRect(4);
|
||||||
Rect logbookBtn = rowRect(5);
|
Rect heartbeatBtn = rowRect(5);
|
||||||
Rect statsBtn = rowRect(6);
|
Rect logbookBtn = rowRect(6);
|
||||||
Rect logFilesBtn = rowRect(7);
|
Rect statsBtn = rowRect(7);
|
||||||
Rect backBtn = rowRect(8);
|
Rect logFilesBtn = rowRect(8);
|
||||||
|
Rect backBtn = rowRect(9);
|
||||||
|
|
||||||
bool done = false;
|
bool done = false;
|
||||||
while (!done) {
|
while (!done) {
|
||||||
tft.fillScreen(TFT_BLACK);
|
tft.fillScreen(TFT_BLACK);
|
||||||
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
||||||
tft.setCursor(10, 6);
|
tft.setCursor(10, 4);
|
||||||
tft.println("Settings");
|
tft.println("Settings");
|
||||||
|
|
||||||
drawButton(tft, calibBtn, "Calibrate touch");
|
drawButton(tft, calibBtn, "Calibrate touch");
|
||||||
@@ -67,6 +68,7 @@ void run(TFT_eSPI& tft) {
|
|||||||
|
|
||||||
drawButton(tft, emergencyBtn, "Emergency alert: " + onOff(SettingsStore::emergencyAlertEnabled()));
|
drawButton(tft, emergencyBtn, "Emergency alert: " + onOff(SettingsStore::emergencyAlertEnabled()));
|
||||||
drawButton(tft, proximityBtn, "Proximity LED: " + onOff(SettingsStore::proximityAlertEnabled()));
|
drawButton(tft, proximityBtn, "Proximity LED: " + onOff(SettingsStore::proximityAlertEnabled()));
|
||||||
|
drawButton(tft, heartbeatBtn, "LED heartbeat: " + onOff(SettingsStore::ledHeartbeatEnabled()));
|
||||||
drawButton(tft, logbookBtn, "Flight logbook: " + onOff(SettingsStore::flightLogbookEnabled()));
|
drawButton(tft, logbookBtn, "Flight logbook: " + onOff(SettingsStore::flightLogbookEnabled()));
|
||||||
drawButton(tft, statsBtn, "Statistics");
|
drawButton(tft, statsBtn, "Statistics");
|
||||||
drawButton(tft, logFilesBtn, "Logbook files");
|
drawButton(tft, logFilesBtn, "Logbook files");
|
||||||
@@ -91,6 +93,8 @@ void run(TFT_eSPI& tft) {
|
|||||||
SettingsStore::setEmergencyAlertEnabled(!SettingsStore::emergencyAlertEnabled());
|
SettingsStore::setEmergencyAlertEnabled(!SettingsStore::emergencyAlertEnabled());
|
||||||
} else if (proximityBtn.contains(tap.x, tap.y)) {
|
} else if (proximityBtn.contains(tap.x, tap.y)) {
|
||||||
SettingsStore::setProximityAlertEnabled(!SettingsStore::proximityAlertEnabled());
|
SettingsStore::setProximityAlertEnabled(!SettingsStore::proximityAlertEnabled());
|
||||||
|
} else if (heartbeatBtn.contains(tap.x, tap.y)) {
|
||||||
|
SettingsStore::setLedHeartbeatEnabled(!SettingsStore::ledHeartbeatEnabled());
|
||||||
} else if (logbookBtn.contains(tap.x, tap.y)) {
|
} else if (logbookBtn.contains(tap.x, tap.y)) {
|
||||||
SettingsStore::setFlightLogbookEnabled(!SettingsStore::flightLogbookEnabled());
|
SettingsStore::setFlightLogbookEnabled(!SettingsStore::flightLogbookEnabled());
|
||||||
} else if (statsBtn.contains(tap.x, tap.y)) {
|
} else if (statsBtn.contains(tap.x, tap.y)) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "settings_store.h"
|
#include "settings_store.h"
|
||||||
#include "aircraft_details.h"
|
#include "aircraft_details.h"
|
||||||
#include "flight_logbook.h"
|
#include "flight_logbook.h"
|
||||||
|
#include "led_alert.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
@@ -50,6 +51,14 @@ namespace {
|
|||||||
AircraftTable::unlock();
|
AircraftTable::unlock();
|
||||||
|
|
||||||
FlightLogbook::update();
|
FlightLogbook::update();
|
||||||
|
|
||||||
|
// Kurzer gruener LED-Blitz als "Herzschlag" - zeigt,
|
||||||
|
// dass gerade eine Abfrage gelaufen ist. Wird von
|
||||||
|
// LedAlert::update() automatisch ignoriert, solange
|
||||||
|
// ein Naeherungs-/Notfall-Alarm aktiv ist.
|
||||||
|
if (SettingsStore::ledHeartbeatEnabled()) {
|
||||||
|
LedAlert::pulseHeartbeat(millis());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[NetTask] Abfrage fehlgeschlagen (HTTP %d)\n", result.httpCode);
|
Serial.printf("[NetTask] Abfrage fehlgeschlagen (HTTP %d)\n", result.httpCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "settings_store.h"
|
#include "settings_store.h"
|
||||||
#include "led_alert.h"
|
#include "led_alert.h"
|
||||||
#include "location_manager.h"
|
#include "location_manager.h"
|
||||||
|
#include "world_map.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
namespace RadarScreen {
|
namespace RadarScreen {
|
||||||
@@ -211,6 +212,37 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dezente Weltkarten-Silhouette als Hintergrund-Layer, wie bei einem
|
||||||
|
// klassischen ATC-Radarschirm - rein dekorativ (nicht massstabsgetreu
|
||||||
|
// zu den Entfernungsringen). Wird auf das quadratische Kreis-Layout
|
||||||
|
// gestreckt (die Karte ist 2:1, der Kreis-Bereich 1:1 - das verzerrt
|
||||||
|
// die Kontinente etwas in der Hoehe, faellt aber bei dieser Groesse
|
||||||
|
// kaum auf und haelt die Zeichenlogik einfach). Wird NUR bei render()
|
||||||
|
// gezeichnet, NICHT bei jedem Sweep-Tick (zu teuer fuer 12x/Sekunde).
|
||||||
|
void drawWorldMap(TFT_eSPI& gfx, const Layout& L) {
|
||||||
|
constexpr uint16_t dim = 0x0320; // sehr gedaempftes Gruen
|
||||||
|
float scaleX = (2.0f * L.radius) / WorldMap::GRID_W;
|
||||||
|
float scaleY = (2.0f * L.radius) / WorldMap::GRID_H;
|
||||||
|
int32_t radiusSq = (int32_t)(L.radius - 2) * (L.radius - 2);
|
||||||
|
|
||||||
|
for (uint8_t row = 0; row < WorldMap::GRID_H; row++) {
|
||||||
|
uint64_t bits = WorldMap::ROWS[row];
|
||||||
|
if (bits == 0) continue;
|
||||||
|
for (uint8_t col = 0; col < WorldMap::GRID_W; col++) {
|
||||||
|
if (!(bits & (1ULL << (WorldMap::GRID_W - 1 - col)))) continue;
|
||||||
|
|
||||||
|
int16_t px = L.cx - L.radius + (int16_t)((col + 0.5f) * scaleX);
|
||||||
|
int16_t py = L.cy - L.radius + (int16_t)((row + 0.5f) * scaleY);
|
||||||
|
|
||||||
|
int16_t dx = px - L.cx;
|
||||||
|
int16_t dy = py - L.cy;
|
||||||
|
if ((int32_t)dx * dx + (int32_t)dy * dy > radiusSq) continue;
|
||||||
|
|
||||||
|
gfx.drawPixel(px, py, dim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void drawSweepLine(TFT_eSPI& gfx, const Layout& L, float angleDeg, uint16_t color) {
|
void drawSweepLine(TFT_eSPI& gfx, const Layout& L, float angleDeg, uint16_t color) {
|
||||||
double rad = angleDeg * DEG_TO_RAD;
|
double rad = angleDeg * DEG_TO_RAD;
|
||||||
int16_t x2 = L.cx + (int16_t)(L.radius * sin(rad));
|
int16_t x2 = L.cx + (int16_t)(L.radius * sin(rad));
|
||||||
@@ -368,6 +400,7 @@ void render(TFT_eSPI& tft, int16_t top) {
|
|||||||
|
|
||||||
tft.fillRect(0, top, Config::SCREEN_WIDTH, Config::SCREEN_HEIGHT - top, TFT_BLACK);
|
tft.fillRect(0, top, Config::SCREEN_WIDTH, Config::SCREEN_HEIGHT - top, TFT_BLACK);
|
||||||
|
|
||||||
|
drawWorldMap(tft, L);
|
||||||
drawStaticBackground(tft, L, rangeKm);
|
drawStaticBackground(tft, L, rangeKm);
|
||||||
|
|
||||||
drawSweepLine(tft, L, sweepAngleDeg, TFT_GREEN);
|
drawSweepLine(tft, L, sweepAngleDeg, TFT_GREEN);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace {
|
|||||||
bool emergencyAlertOn = true;
|
bool emergencyAlertOn = true;
|
||||||
bool proximityAlertOn = true;
|
bool proximityAlertOn = true;
|
||||||
bool flightLogbookOn = true;
|
bool flightLogbookOn = true;
|
||||||
|
bool ledHeartbeatOn = true;
|
||||||
|
|
||||||
void applyKeyValue(const String& key, const String& value) {
|
void applyKeyValue(const String& key, const String& value) {
|
||||||
if (key == "range_index") {
|
if (key == "range_index") {
|
||||||
@@ -25,20 +26,26 @@ namespace {
|
|||||||
proximityAlertOn = (value.toInt() != 0);
|
proximityAlertOn = (value.toInt() != 0);
|
||||||
} else if (key == "flight_logbook") {
|
} else if (key == "flight_logbook") {
|
||||||
flightLogbookOn = (value.toInt() != 0);
|
flightLogbookOn = (value.toInt() != 0);
|
||||||
|
} else if (key == "led_heartbeat") {
|
||||||
|
ledHeartbeatOn = (value.toInt() != 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (!SD.exists(Config::SD_SETTINGS_FILE)) return;
|
if (!SD.exists(Config::SD_SETTINGS_FILE)) return;
|
||||||
|
|
||||||
File f = SD.open(Config::SD_SETTINGS_FILE, FILE_READ);
|
File f = SD.open(Config::SD_SETTINGS_FILE, FILE_READ);
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
|
|
||||||
while (f.available()) {
|
while (f.available()) {
|
||||||
String line = f.readStringUntil('\n');
|
String line = f.readStringUntil('\n');
|
||||||
line.trim();
|
line.trim();
|
||||||
if (line.length() == 0 || line.startsWith("#")) continue;
|
if (line.length() == 0 || line.startsWith("#")) continue;
|
||||||
|
|
||||||
int eq = line.indexOf('=');
|
int eq = line.indexOf('=');
|
||||||
if (eq < 0) continue;
|
if (eq < 0) continue;
|
||||||
|
|
||||||
String key = line.substring(0, eq);
|
String key = line.substring(0, eq);
|
||||||
String value = line.substring(eq + 1);
|
String value = line.substring(eq + 1);
|
||||||
key.trim();
|
key.trim();
|
||||||
@@ -56,24 +63,52 @@ void save() {
|
|||||||
f.printf("emergency_alert=%d\n", emergencyAlertOn ? 1 : 0);
|
f.printf("emergency_alert=%d\n", emergencyAlertOn ? 1 : 0);
|
||||||
f.printf("proximity_alert=%d\n", proximityAlertOn ? 1 : 0);
|
f.printf("proximity_alert=%d\n", proximityAlertOn ? 1 : 0);
|
||||||
f.printf("flight_logbook=%d\n", flightLogbookOn ? 1 : 0);
|
f.printf("flight_logbook=%d\n", flightLogbookOn ? 1 : 0);
|
||||||
|
f.printf("led_heartbeat=%d\n", ledHeartbeatOn ? 1 : 0);
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t rangeIndex() { return rangeIdx; }
|
uint8_t rangeIndex() { return rangeIdx; }
|
||||||
|
|
||||||
void setRangeIndex(uint8_t idx) {
|
void setRangeIndex(uint8_t idx) {
|
||||||
if (idx < Config::RANGE_STEP_COUNT) { rangeIdx = idx; save(); }
|
if (idx < Config::RANGE_STEP_COUNT) {
|
||||||
|
rangeIdx = idx;
|
||||||
|
save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool displayInverted() { return inverted; }
|
bool displayInverted() { return inverted; }
|
||||||
void setDisplayInverted(bool inv) { inverted = inv; save(); }
|
|
||||||
|
void setDisplayInverted(bool inv) {
|
||||||
|
inverted = inv;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
bool emergencyAlertEnabled() { return emergencyAlertOn; }
|
bool emergencyAlertEnabled() { return emergencyAlertOn; }
|
||||||
void setEmergencyAlertEnabled(bool on) { emergencyAlertOn = on; save(); }
|
|
||||||
|
void setEmergencyAlertEnabled(bool on) {
|
||||||
|
emergencyAlertOn = on;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
bool proximityAlertEnabled() { return proximityAlertOn; }
|
bool proximityAlertEnabled() { return proximityAlertOn; }
|
||||||
void setProximityAlertEnabled(bool on) { proximityAlertOn = on; save(); }
|
|
||||||
|
void setProximityAlertEnabled(bool on) {
|
||||||
|
proximityAlertOn = on;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
bool flightLogbookEnabled() { return flightLogbookOn; }
|
bool flightLogbookEnabled() { return flightLogbookOn; }
|
||||||
void setFlightLogbookEnabled(bool on) { flightLogbookOn = on; save(); }
|
|
||||||
|
void setFlightLogbookEnabled(bool on) {
|
||||||
|
flightLogbookOn = on;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ledHeartbeatEnabled() { return ledHeartbeatOn; }
|
||||||
|
|
||||||
|
void setLedHeartbeatEnabled(bool on) {
|
||||||
|
ledHeartbeatOn = on;
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -19,4 +19,10 @@ namespace SettingsStore {
|
|||||||
|
|
||||||
bool flightLogbookEnabled();
|
bool flightLogbookEnabled();
|
||||||
void setFlightLogbookEnabled(bool on);
|
void setFlightLogbookEnabled(bool on);
|
||||||
|
|
||||||
|
// Kurzes gruenes Aufblitzen der Rueckseiten-LED synchron zu jeder
|
||||||
|
// ADS-B-Abfrage (alle 8s) - zeigt "Geraet ist aktiv", auch wenn gerade
|
||||||
|
// kein Naeherungs-/Notfall-Alarm laeuft.
|
||||||
|
bool ledHeartbeatEnabled();
|
||||||
|
void setLedHeartbeatEnabled(bool on);
|
||||||
}
|
}
|
||||||
@@ -15,28 +15,30 @@ namespace {
|
|||||||
// Radar-Bildschirm), damit das Flugzeug wie ins Visier genommen wirkt.
|
// Radar-Bildschirm), damit das Flugzeug wie ins Visier genommen wirkt.
|
||||||
void drawRadarReticle(TFT_eSPI& tft, int16_t cx, int16_t cy) {
|
void drawRadarReticle(TFT_eSPI& tft, int16_t cx, int16_t cy) {
|
||||||
uint16_t dim = 0x0320;
|
uint16_t dim = 0x0320;
|
||||||
tft.drawCircle(cx, cy, 112, dim);
|
tft.drawCircle(cx, cy, 80, dim);
|
||||||
tft.drawCircle(cx, cy, 76, dim);
|
tft.drawCircle(cx, cy, 54, dim);
|
||||||
tft.drawCircle(cx, cy, 40, dim);
|
tft.drawCircle(cx, cy, 29, dim);
|
||||||
tft.drawFastHLine(cx - 112, cy, 224, dim);
|
tft.drawFastHLine(cx - 80, cy, 160, dim);
|
||||||
tft.drawFastVLine(cx, cy - 112, 224, dim);
|
tft.drawFastVLine(cx, cy - 80, 160, dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Einfache Vektor-Silhouette eines schlanken Duesenjets von oben (keine
|
// Einfache Vektor-Silhouette eines schlanken Duesenjets von oben (keine
|
||||||
// Bilddatei eingebunden, daher per Dreiecken gezeichnet) - nur als Umriss
|
// Bilddatei eingebunden, daher per Dreiecken gezeichnet) - nur als Umriss
|
||||||
// (nicht ausgefuellt), in Gruen, passend zum restlichen Dark-Theme.
|
// (nicht ausgefuellt), in Gruen, passend zum restlichen Dark-Theme.
|
||||||
|
// Etwas kleiner als frueher, damit er sauber zwischen Titel und
|
||||||
|
// Statuszeilen passt, ohne diese zu ueberlappen.
|
||||||
void drawAirplane(TFT_eSPI& tft, int16_t cx) {
|
void drawAirplane(TFT_eSPI& tft, int16_t cx) {
|
||||||
uint16_t color = TFT_GREEN;
|
uint16_t color = TFT_GREEN;
|
||||||
|
|
||||||
// Schlanker Rumpf (nur Umriss)
|
// Schlanker Rumpf (nur Umriss)
|
||||||
tft.drawTriangle(cx, 105, cx - 6, 255, cx + 6, 255, color);
|
tft.drawTriangle(cx, 116, cx - 4, 223, cx + 4, 223, color);
|
||||||
|
|
||||||
// Deltafluegel, nach hinten gepfeilt (nur Umriss)
|
// Deltafluegel, nach hinten gepfeilt (nur Umriss)
|
||||||
tft.drawTriangle(cx, 160, cx - 100, 235, cx, 200, color);
|
tft.drawTriangle(cx, 155, cx - 71, 209, cx, 184, color);
|
||||||
tft.drawTriangle(cx, 160, cx + 100, 235, cx, 200, color);
|
tft.drawTriangle(cx, 155, cx + 71, 209, cx, 184, color);
|
||||||
|
|
||||||
// Kleines Leitwerk (Hoehenruder) am Heck (nur Umriss)
|
// Kleines Leitwerk (Hoehenruder) am Heck (nur Umriss)
|
||||||
tft.drawTriangle(cx, 250, cx - 22, 268, cx + 22, 268, color);
|
tft.drawTriangle(cx, 219, cx - 16, 232, cx + 16, 232, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ void begin(TFT_eSPI& tft) {
|
|||||||
int16_t cx = tft.width() / 2;
|
int16_t cx = tft.width() / 2;
|
||||||
|
|
||||||
tft.fillScreen(TFT_BLACK);
|
tft.fillScreen(TFT_BLACK);
|
||||||
drawRadarReticle(tft, cx, 185);
|
drawRadarReticle(tft, cx, 174);
|
||||||
drawAirplane(tft, cx);
|
drawAirplane(tft, cx);
|
||||||
|
|
||||||
tft.setTextDatum(MC_DATUM);
|
tft.setTextDatum(MC_DATUM);
|
||||||
|
|||||||
40
src/world_map.cpp
Normal file
40
src/world_map.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "world_map.h"
|
||||||
|
|
||||||
|
namespace WorldMap {
|
||||||
|
|
||||||
|
const uint64_t ROWS[GRID_H] = {
|
||||||
|
0x0000000000000000ULL,
|
||||||
|
0x0000F7FC00000000ULL,
|
||||||
|
0x001B07F000107080ULL,
|
||||||
|
0x1C0DF3E0080FFFF0ULL,
|
||||||
|
0x3FFF31883DFFFFFFULL,
|
||||||
|
0x19FE38006FFFFF88ULL,
|
||||||
|
0x00FFBC037FFFFF80ULL,
|
||||||
|
0x003FF601FFFFFF80ULL,
|
||||||
|
0x003FE003B9FFFE00ULL,
|
||||||
|
0x003FE000C3FFFE80ULL,
|
||||||
|
0x001FC003EFFFFC00ULL,
|
||||||
|
0x00060007FD9FF800ULL,
|
||||||
|
0x00060007FFC67000ULL,
|
||||||
|
0x00008007FE846400ULL,
|
||||||
|
0x00007803FF804000ULL,
|
||||||
|
0x00003E003F004800ULL,
|
||||||
|
0x00003F803E002980ULL,
|
||||||
|
0x00003FC03E000E00ULL,
|
||||||
|
0x00003F803E000340ULL,
|
||||||
|
0x00000F803C8007C0ULL,
|
||||||
|
0x00001E001C000FE0ULL,
|
||||||
|
0x00001E00180007E0ULL,
|
||||||
|
0x00001C0000000060ULL,
|
||||||
|
0x0000100000000002ULL,
|
||||||
|
0x0000100000000000ULL,
|
||||||
|
0x0000100000000000ULL,
|
||||||
|
0x0000000000000000ULL,
|
||||||
|
0x0000000000000000ULL,
|
||||||
|
0x0000180077F7FFF8ULL,
|
||||||
|
0x03FFF00FFFFFFFF8ULL,
|
||||||
|
0x0FFFF07FFFFFFFF8ULL,
|
||||||
|
0xFFFFFFFFFFFFFFFFULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
14
src/world_map.h
Normal file
14
src/world_map.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Sehr stark vereinfachte Weltkarte (Kontinent-Umrisse) als 64x32-Raster-
|
||||||
|
// Bitmap, generiert aus Natural-Earth-Daten (1:110m-Aufloesung, die groebste
|
||||||
|
// oeffentlich verfuegbare Stufe - genau richtig fuer ein kleines Display).
|
||||||
|
// Jede Zeile ist ein 64-Bit-Wert; Bit (63-i) gesetzt = Landflaeche an
|
||||||
|
// Spalte i. Equirektangulare Projektion: Zeile 0 = Nordpol, Zeile 31 =
|
||||||
|
// Suedpol, Spalte 0 = -180 Grad Laenge, Spalte 63 = +180 Grad Laenge.
|
||||||
|
namespace WorldMap {
|
||||||
|
constexpr uint8_t GRID_W = 64;
|
||||||
|
constexpr uint8_t GRID_H = 32;
|
||||||
|
extern const uint64_t ROWS[GRID_H];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user