fix 3.5 backlight and GT911 for cyd_35r.

TFT_BL was wrongly GPIO21 (GT911 INT); use GPIO27, DRAW_BUF_DIV=20, and export firmware/cyd_35r.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-25 19:40:29 +08:00
commit ba09f4f48e
33 changed files with 3223 additions and 0 deletions

403
src/wifi_prov.cpp Normal file
View File

@@ -0,0 +1,403 @@
#include "wifi_prov.h"
#include <DNSServer.h>
#include <Preferences.h>
#include <WebServer.h>
#include <cstring>
static constexpr const char *NVS_NS = "wifi_prov";
static constexpr const char *NVS_SSID = "ssid";
static constexpr const char *NVS_PASS = "pass";
static Preferences g_prefs;
static WifiProvState g_state = WIFI_PROV_IDLE;
static WifiProvStateCb g_cb = nullptr;
static void *g_cb_user = nullptr;
static char g_ssid[WIFI_PROV_SSID_MAX + 1] = {};
static char g_pass[WIFI_PROV_PASS_MAX + 1] = {};
static bool g_has_creds = false;
static uint32_t g_connect_started_ms = 0;
static uint32_t g_last_reconnect_ms = 0;
static bool g_want_connect = false;
static DNSServer *g_dns = nullptr;
static WebServer *g_http = nullptr;
static bool g_portal = false;
static char g_ap_ssid[33] = WIFI_PROV_AP_SSID;
static void set_state(WifiProvState s) {
if (g_state == s) {
return;
}
g_state = s;
if (g_cb) {
g_cb(s, g_cb_user);
}
}
static void load_creds() {
g_has_creds = false;
g_ssid[0] = '\0';
g_pass[0] = '\0';
if (!g_prefs.begin(NVS_NS, true)) {
return;
}
String ssid = g_prefs.getString(NVS_SSID, "");
String pass = g_prefs.getString(NVS_PASS, "");
g_prefs.end();
if (ssid.length() == 0 || ssid.length() > WIFI_PROV_SSID_MAX) {
return;
}
strncpy(g_ssid, ssid.c_str(), WIFI_PROV_SSID_MAX);
g_ssid[WIFI_PROV_SSID_MAX] = '\0';
strncpy(g_pass, pass.c_str(), WIFI_PROV_PASS_MAX);
g_pass[WIFI_PROV_PASS_MAX] = '\0';
g_has_creds = true;
}
static void begin_sta_connect(const char *ssid, const char *password) {
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
WiFi.disconnect(false, true);
delay(50);
WiFi.begin(ssid, password);
g_want_connect = true;
g_connect_started_ms = millis();
g_last_reconnect_ms = millis();
set_state(WIFI_PROV_CONNECTING);
Serial.printf("[wifi] connecting to \"%s\"...\n", ssid);
}
static String html_escape(const String &s) {
String o;
o.reserve(s.length());
for (size_t i = 0; i < s.length(); ++i) {
const char c = s[i];
if (c == '&') {
o += "&amp;";
} else if (c == '<') {
o += "&lt;";
} else if (c == '>') {
o += "&gt;";
} else if (c == '"') {
o += "&quot;";
} else {
o += c;
}
}
return o;
}
static String portal_page() {
WifiScanItem items[WIFI_PROV_MAX_SCAN];
const int n = wifi_prov_scan(items, WIFI_PROV_MAX_SCAN);
String html;
html.reserve(2048);
html += F("<!DOCTYPE html><html><head><meta charset=utf-8>"
"<meta name=viewport content=\"width=device-width,initial-scale=1\">"
"<title>CYD WiFi</title>"
"<style>"
"body{font-family:system-ui,sans-serif;margin:16px;background:#0f1419;color:#eaf0f6}"
"h1{font-size:1.25rem}label{display:block;margin:.6rem 0 .2rem;color:#9ab}"
"input,select,button{width:100%;box-sizing:border-box;padding:.7rem;border-radius:8px;"
"border:1px solid #334;background:#1a2330;color:#eaf0f6;font-size:1rem}"
"button{margin-top:1rem;background:#1a6aa8;border:none;font-weight:600}"
".hint{color:#7a8b9c;font-size:.85rem;margin-top:1rem}"
"</style></head><body>"
"<h1>WiFi Setup</h1>"
"<form method=POST action=/save>"
"<label>Network</label><select name=ssid>");
if (n <= 0) {
html += F("<option value=\"\">(no networks — type below)</option>");
} else {
for (int i = 0; i < n; ++i) {
html += F("<option value=\"");
html += html_escape(String(items[i].ssid));
html += F("\">");
html += html_escape(String(items[i].ssid));
html += F(" (");
html += String(items[i].rssi);
html += F(" dBm)</option>");
}
}
html += F("</select>"
"<label>Or SSID</label><input name=ssid_manual placeholder=\"manual SSID\">"
"<label>Password</label><input name=pass type=password placeholder=\"WiFi password\">"
"<button type=submit>Save &amp; Connect</button>"
"</form>"
"<p class=hint>AP: ");
html += html_escape(String(g_ap_ssid));
html += F(" · http://192.168.4.1/</p></body></html>");
return html;
}
static void handle_root() {
g_http->send(200, "text/html", portal_page());
}
static void handle_save() {
String ssid = g_http->arg("ssid_manual");
ssid.trim();
if (ssid.length() == 0) {
ssid = g_http->arg("ssid");
ssid.trim();
}
String pass = g_http->arg("pass");
g_http->send(200, "text/html",
F("<!DOCTYPE html><html><body style=\"font-family:sans-serif;background:#0f1419;"
"color:#eaf0f6;padding:24px\"><h2>Connecting...</h2>"
"<p>You can close this page. Device will leave setup AP.</p></body></html>"));
if (ssid.length() > 0) {
delay(200);
wifi_prov_portal_stop();
wifi_prov_connect_to(ssid.c_str(), pass.c_str());
}
}
static void handle_captive() {
g_http->sendHeader("Location", "http://192.168.4.1/", true);
g_http->send(302, "text/plain", "");
}
static void handle_not_found() {
handle_captive();
}
void wifi_prov_set_state_cb(WifiProvStateCb cb, void *user) {
g_cb = cb;
g_cb_user = user;
}
bool wifi_prov_begin() {
load_creds();
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
if (g_has_creds) {
begin_sta_connect(g_ssid, g_pass);
} else {
set_state(WIFI_PROV_IDLE);
Serial.println("[wifi] no credentials");
}
return g_has_creds;
}
bool wifi_prov_has_credentials() { return g_has_creds; }
bool wifi_prov_is_connected() {
return WiFi.status() == WL_CONNECTED;
}
WifiProvState wifi_prov_state() { return g_state; }
String wifi_prov_ssid() {
if (WiFi.status() == WL_CONNECTED) {
return WiFi.SSID();
}
return String(g_ssid);
}
String wifi_prov_ip() {
if (WiFi.status() != WL_CONNECTED) {
return String();
}
return WiFi.localIP().toString();
}
int8_t wifi_prov_rssi() {
if (WiFi.status() != WL_CONNECTED) {
return 0;
}
return (int8_t)WiFi.RSSI();
}
void wifi_prov_save(const char *ssid, const char *password) {
if (!ssid || !ssid[0]) {
return;
}
strncpy(g_ssid, ssid, WIFI_PROV_SSID_MAX);
g_ssid[WIFI_PROV_SSID_MAX] = '\0';
if (password) {
strncpy(g_pass, password, WIFI_PROV_PASS_MAX);
} else {
g_pass[0] = '\0';
}
g_pass[WIFI_PROV_PASS_MAX] = '\0';
g_has_creds = true;
if (g_prefs.begin(NVS_NS, false)) {
g_prefs.putString(NVS_SSID, g_ssid);
g_prefs.putString(NVS_PASS, g_pass);
g_prefs.end();
}
Serial.printf("[wifi] saved \"%s\"\n", g_ssid);
}
void wifi_prov_clear() {
g_ssid[0] = '\0';
g_pass[0] = '\0';
g_has_creds = false;
g_want_connect = false;
if (g_prefs.begin(NVS_NS, false)) {
g_prefs.clear();
g_prefs.end();
}
WiFi.disconnect(true, true);
set_state(WIFI_PROV_IDLE);
Serial.println("[wifi] credentials cleared");
}
void wifi_prov_connect_saved() {
if (!g_has_creds) {
set_state(WIFI_PROV_FAILED);
return;
}
begin_sta_connect(g_ssid, g_pass);
}
void wifi_prov_connect_to(const char *ssid, const char *password) {
wifi_prov_save(ssid, password ? password : "");
begin_sta_connect(g_ssid, g_pass);
}
int wifi_prov_scan(WifiScanItem *out, int max_items) {
if (!out || max_items <= 0) {
return 0;
}
if (max_items > WIFI_PROV_MAX_SCAN) {
max_items = WIFI_PROV_MAX_SCAN;
}
// SoftAP 时用 APSTA,否则扫描会关掉 AP
if (g_portal) {
WiFi.mode(WIFI_AP_STA);
} else {
WiFi.mode(WIFI_STA);
}
const int found = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);
if (found <= 0) {
WiFi.scanDelete();
return 0;
}
const int n = found < max_items ? found : max_items;
for (int i = 0; i < n; ++i) {
String s = WiFi.SSID(i);
strncpy(out[i].ssid, s.c_str(), WIFI_PROV_SSID_MAX);
out[i].ssid[WIFI_PROV_SSID_MAX] = '\0';
out[i].rssi = WiFi.RSSI(i);
out[i].auth = WiFi.encryptionType(i);
}
WiFi.scanDelete();
return n;
}
void wifi_prov_portal_start(const char *ap_ssid) {
if (g_portal) {
return;
}
if (ap_ssid && ap_ssid[0]) {
strncpy(g_ap_ssid, ap_ssid, sizeof(g_ap_ssid) - 1);
g_ap_ssid[sizeof(g_ap_ssid) - 1] = '\0';
}
WiFi.mode(WIFI_AP_STA);
WiFi.softAP(g_ap_ssid);
delay(100);
g_dns = new DNSServer();
g_http = new WebServer(80);
g_dns->start(53, "*", WiFi.softAPIP());
g_http->on("/", HTTP_GET, handle_root);
g_http->on("/save", HTTP_POST, handle_save);
g_http->on("/generate_204", HTTP_GET, handle_captive);
g_http->on("/gen_204", HTTP_GET, handle_captive);
g_http->on("/hotspot-detect.html", HTTP_GET, handle_root);
g_http->on("/connecttest.txt", HTTP_GET, handle_captive);
g_http->on("/ncsi.txt", HTTP_GET, handle_captive);
g_http->onNotFound(handle_not_found);
g_http->begin();
g_portal = true;
set_state(WIFI_PROV_PORTAL);
Serial.printf("[wifi] portal AP \"%s\" @ %s\n", g_ap_ssid,
WiFi.softAPIP().toString().c_str());
}
void wifi_prov_portal_stop() {
if (!g_portal) {
return;
}
if (g_http) {
g_http->stop();
delete g_http;
g_http = nullptr;
}
if (g_dns) {
g_dns->stop();
delete g_dns;
g_dns = nullptr;
}
WiFi.softAPdisconnect(true);
g_portal = false;
Serial.println("[wifi] portal stopped");
}
bool wifi_prov_portal_active() { return g_portal; }
void wifi_prov_loop() {
if (g_portal && g_dns && g_http) {
g_dns->processNextRequest();
g_http->handleClient();
}
const wl_status_t st = WiFi.status();
const uint32_t now = millis();
if (g_want_connect || g_state == WIFI_PROV_CONNECTING) {
if (st == WL_CONNECTED) {
g_want_connect = false;
if (g_portal) {
wifi_prov_portal_stop();
}
set_state(WIFI_PROV_CONNECTED);
Serial.printf("[wifi] connected %s ip=%s rssi=%d\n", WiFi.SSID().c_str(),
WiFi.localIP().toString().c_str(), WiFi.RSSI());
return;
}
if (now - g_connect_started_ms >= WIFI_PROV_CONNECT_TIMEOUT_MS ||
st == WL_CONNECT_FAILED || st == WL_NO_SSID_AVAIL) {
g_want_connect = false;
set_state(WIFI_PROV_FAILED);
Serial.println("[wifi] connect failed");
return;
}
}
// 已配置且非门户:断线后周期性重连
if (g_has_creds && !g_portal && g_state == WIFI_PROV_CONNECTED &&
st != WL_CONNECTED) {
set_state(WIFI_PROV_CONNECTING);
g_want_connect = true;
g_connect_started_ms = now;
}
if (g_has_creds && !g_portal && st != WL_CONNECTED &&
g_state != WIFI_PROV_CONNECTING &&
now - g_last_reconnect_ms >= WIFI_PROV_RECONNECT_MS) {
g_last_reconnect_ms = now;
begin_sta_connect(g_ssid, g_pass);
}
}