Files
flyradar/src/calibration_screen.cpp
Eiswolf-BG a7f14b3125 v3.3.0: Welcome screen, factory reset, calibration/touch fixes, flash partition fix
## New
- "Welcome" screen shown on first-time setup (before touch
  calibration), with the splash screen's animated radar graphic and a
  Start button.
- The "Setup complete" screen now counts down 7 seconds with a button
  matching the Welcome screen's style, then automatically continues
  into the app (previously required a tap).
- System menu "Backup & Reset" page (Backup, Restore, and a new
  "Reset settings" option that wipes the on-device data and re-runs
  first-time setup - useful for testing or handing the device to
  someone else).
- Touch calibration targets are now green mini radar reticles instead
  of red crosshairs.

## Improved
- The SD card's data folder is now only created after tapping Start
  on the Welcome screen, instead of on every boot regardless.
- Flash partition layout changed to free up roughly 850 KB of
  firmware space (no OTA update slot needed, since firmware is always
  flashed via cable/USB) - lowers flash usage from ~95% to ~59%.

## Fixed
- The Start button and the WiFi scanning screen now show a loading
  indicator instead of appearing frozen during SD-card/WiFi
  operations.
- A touch-input bug during first-time setup that could corrupt touch
  calibration and cause taps to land on the wrong on-screen element
  afterward (most noticeable in the WiFi network selection list).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:57:48 +02:00

108 lines
3.4 KiB
C++

#include "calibration_screen.h"
#include "touch_input.h"
#include "menu_stars.h"
#include "config.h"
#include "i18n.h"
namespace CalibrationScreen {
namespace {
struct RawAvg {
long sumX = 0, sumY = 0;
uint16_t n = 0;
void add(int16_t x, int16_t y) { sumX += x; sumY += y; n++; }
int16_t avgX() const { return n ? (int16_t)(sumX / n) : 0; }
int16_t avgY() const { return n ? (int16_t)(sumY / n) : 0; }
};
// Mini radar target: same look as the radar graphic on the splash/
// welcome screen (concentric rings + crosshair in green), but without
// aircraft - instead a green filled center point as the point to aim
// at, replacing the previous red filled circle with white center dot.
// Radii deliberately kept small so the targets in the screen corners
// (edge margin M=24, see run()) don't extend past the screen edge.
void drawTarget(TFT_eSPI& tft, int16_t x, int16_t y) {
uint16_t dim = 0x0320;
tft.drawCircle(x, y, 16, dim);
tft.drawCircle(x, y, 10, dim);
tft.drawFastHLine((int16_t)(x - 18), y, 36, TFT_GREEN);
tft.drawFastVLine(x, (int16_t)(y - 18), 36, TFT_GREEN);
tft.fillCircle(x, y, 3, TFT_GREEN);
}
RawAvg waitForTap(TFT_eSPI& tft) {
RawAvg avg;
while (!TouchInput::rawPoint().touched) {
MenuStars::update(tft);
delay(10);
}
uint32_t sampleStart = millis();
while (millis() - sampleStart < 250) {
TouchInput::Point p = TouchInput::rawPoint();
if (p.touched) avg.add(p.x, p.y);
delay(10);
}
while (TouchInput::rawPoint().touched) {
delay(10);
}
delay(150);
return avg;
}
void swapIfNeeded(int16_t& lo, int16_t& hi) {
if (lo > hi) { int16_t t = lo; lo = hi; hi = t; }
}
}
void run(TFT_eSPI& tft) {
const int16_t M = 24;
const int16_t W = Config::SCREEN_WIDTH;
const int16_t H = Config::SCREEN_HEIGHT;
struct { int16_t x, y; StringId label; } targets[4] = {
{ M, M, StringId::CALIB_TOP_LEFT },
{ W - M, M, StringId::CALIB_TOP_RIGHT },
{ W - M, H - M, StringId::CALIB_BOTTOM_RIGHT },
{ M, H - M, StringId::CALIB_BOTTOM_LEFT },
};
RawAvg samples[4];
for (uint8_t i = 0; i < 4; i++) {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setTextSize(1);
tft.setTextDatum(MC_DATUM);
tft.drawString(I18n::t(StringId::CALIB_TITLE), W / 2, H / 2 - 12);
tft.drawString(I18n::t(StringId::CALIB_PROMPT), W / 2, H / 2 + 4);
tft.drawString(I18n::t(targets[i].label), W / 2, H / 2 + 20);
tft.setTextDatum(TL_DATUM);
drawTarget(tft, targets[i].x, targets[i].y);
MenuStars::reset();
samples[i] = waitForTap(tft);
}
int16_t xmin = (samples[0].avgX() + samples[3].avgX()) / 2;
int16_t xmax = (samples[1].avgX() + samples[2].avgX()) / 2;
int16_t ymin = (samples[0].avgY() + samples[1].avgY()) / 2;
int16_t ymax = (samples[3].avgY() + samples[2].avgY()) / 2;
swapIfNeeded(xmin, xmax);
swapIfNeeded(ymin, ymax);
TouchInput::setCalibration(xmin, xmax, ymin, ymax);
TouchInput::saveCalibration();
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_GREEN, TFT_BLACK);
tft.setCursor(10, 14);
tft.println(I18n::t(StringId::CALIB_SAVED));
delay(800);
}
}