## 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>
108 lines
3.4 KiB
C++
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);
|
|
}
|
|
|
|
} |