fix:修补蓝牙配网bug

This commit is contained in:
yangzhijia
2026-08-12 09:22:28 +08:00
parent 93112dcdfd
commit 08a055b611
32 changed files with 13266 additions and 327 deletions

View File

@@ -19,7 +19,12 @@
"Bash(sed -i 's/#define BTN_PAGE_SWITCH_PIN 10/#define BTN_PAGE_SWITCH_PIN 5/' include/config.h)", "Bash(sed -i 's/#define BTN_PAGE_SWITCH_PIN 10/#define BTN_PAGE_SWITCH_PIN 5/' include/config.h)",
"Bash(sed -i 's/#define BTN_LONG_PRESS_MS 3000/#define BTN_LONG_PRESS_MS 1500/' include/config.h)", "Bash(sed -i 's/#define BTN_LONG_PRESS_MS 3000/#define BTN_LONG_PRESS_MS 1500/' include/config.h)",
"Bash(sed -i 's/#define WATER_SENSOR_PIN 6 /#define WATER_SENSOR_PIN 45/' include/config.h)", "Bash(sed -i 's/#define WATER_SENSOR_PIN 6 /#define WATER_SENSOR_PIN 45/' include/config.h)",
"Bash(sed -i 's/#define BUZZER_PIN 7 /#define BUZZER_PIN 21/' include/config.h)" "Bash(sed -i 's/#define BUZZER_PIN 7 /#define BUZZER_PIN 21/' include/config.h)",
"PowerShell(Get-ChildItem \"$env:USERPROFILE\\\\.claude\" -ErrorAction SilentlyContinue; if \\(-not $?\\) { Write-Host \"No .claude at USERPROFILE\" })",
"Read(//c/Users/杨志佳/.claude/**)",
"Read(//d/ClaudeData/.claude/**)",
"Read(//c/Users/杨志佳/.claude/projects/d--PlatformIO1-Projects-Healthv2-0/memory/**)",
"Read(//d/PlatformIO1/Projects/Healthv2.0/ai/sessions/memory/**)"
] ]
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"agentType":"Explore","description":"Research U8g2 Chinese font options","toolUseId":"call_01_W4SVsAkWKYYPD3AllArZ6173","spawnDepth":1}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"agentType":"Explore","description":"Explore existing patterns and GPIO","toolUseId":"call_00_wiJSGewBjcUTBxzRBDKV0217","spawnDepth":1}

View File

@@ -0,0 +1,252 @@
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:31:WiFiManagerParameter::WiFiManagerParameter(const char *custom) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:37: _customHTML = custom;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:48:WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:49: init(id, label, defaultValue, length, custom, WFM_LABEL_DEFAULT);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:52:WiFiManagerParameter::WiFiManagerParameter(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:53: init(id, label, defaultValue, length, custom, labelPlacement);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:56:void WiFiManagerParameter::init(const char *id, const char *label, const char *defaultValue, int length, const char *custom, int labelPlacement) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:60: _customHTML = custom;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:124:const char* WiFiManagerParameter::getCustomHTML() const {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:125: return _customHTML;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:159: DEBUG_WM(WM_DEBUG_DEV,F("Updated _max_params:"),_max_params);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:220: if(_debug && _debugLevel >= WM_DEBUG_DEV) debugPlatformInfo();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:254: WiFi.persistent(false); // disable persistent so scannetworks and mode switching do not cause overwrites
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:283: // bool wifiIsSaved = getWiFiIsSaved();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:284: bool wifiIsSaved = true; // workaround until I can check esp32 wifiisinit and has nvs
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:287: setupHostname(true);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:302: // check if wifi is saved, (has autoconnect) to speed up cp start
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:304: if(wifiIsSaved){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:308: // attempt to connect using saved settings, on fail fallback to AP config portal
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:320: if(esp32persistent) WiFi.persistent(false); // disable persistent for esp32 after esp_wifi_start or else saves wont work
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:332: setupHostname(true);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:338: // @todo check if correct ssid == saved ssid when already connected
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:374: DEBUG_WM(F("No Credentials are Saved, skipping connect"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:392:bool WiFiManager::setupHostname(bool restart){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:455: // WiFi.reconnect(); // This does not reset dhcp
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:457: delay(200); // do not remove, need a delay for disconnect to change status()
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:481: // setup optional soft AP static ip config
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:484: DEBUG_WM(F("Custom AP IP/GW/Subnet:"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:560: setupConfigPortal();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:618:void WiFiManager::setupHTTPServer(){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:626: DEBUG_WM(WM_DEBUG_VERBOSE,F("http server started with custom port: "),_httpPort); // @todo not showing ip
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:630: server.reset(new WM_WebServer(_httpPort));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:631: // This is not the safest way to reset the webserver, it can cause crashes on callbacks initilized before this and since its a shared pointer...
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:641: /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:646: server->on(WM_G(R_wifinoscan), std::bind(&WiFiManager::handleWifi, this, false));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:647: server->on(WM_G(R_wifisave), std::bind(&WiFiManager::handleWifiSave, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:648: server->on(WM_G(R_info), std::bind(&WiFiManager::handleInfo, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:650: server->on(WM_G(R_paramsave), std::bind(&WiFiManager::handleParamSave, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:651: server->on(WM_G(R_restart), std::bind(&WiFiManager::handleReset, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:652: server->on(WM_G(R_exit), std::bind(&WiFiManager::handleExit, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:653: server->on(WM_G(R_close), std::bind(&WiFiManager::handleClose, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:658: server->on(WM_G(R_update), std::bind(&WiFiManager::handleUpdate, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:659: server->on(WM_G(R_updatedone), HTTP_POST, std::bind(&WiFiManager::handleUpdateDone, this), std::bind(&WiFiManager::handleUpdating, this));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:667:void WiFiManager::setupDNSD(){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:668: dnsServer.reset(new DNSServer());
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:670: /* Setup the DNS server redirecting all the domains to the apIP */
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:679:void WiFiManager::setupConfigPortal() {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:680: setupHTTPServer();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:681: _lastscan = 0; // reset network scan cache
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:682: if(_preloadwifiscan) WiFi_scanNetworks(true,true); // preload wifiscan , async
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:707: //setup AP
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:719: // HANDLE issues with STA connections, shutdown sta if not connected, or else this will hang channel scanning and softap will not respond
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:759: DEBUG_WM(WM_DEBUG_DEV,F("setupConfigPortal"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:761: setupConfigPortal();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:764: DEBUG_WM(WM_DEBUG_DEV,F("setupDNSD"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:766: setupDNSD();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:819: DEBUG_WM(WM_DEBUG_NOTIFY,F("config portal exiting"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:832: MDNS.update();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:837: if(_allowExit && (configPortalHasTimeout() || abort)){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:861: * returns WL_IDLE_STATUS or WL_CONNECTED/WL_CONNECT_FAILED upon connect/save flag
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:874: // Waiting for save...
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:878: DEBUG_WM(WM_DEBUG_VERBOSE,F("processing save"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:880: if(_enableCaptivePortal) delay(_cpclosedelay); // keeps the captiveportal from closing to fast.
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:885: DEBUG_WM(WM_DEBUG_VERBOSE,F("No ssid, skipping wifi save"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:890: uint8_t res = connectWifi(_ssid, _pass, _connectonsave) == WL_CONNECTED;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:891: if (res || (!_connectonsave)) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:893: if(!_connectonsave){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:894: DEBUG_WM(F("SAVED with no connect to new AP"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:896: DEBUG_WM(F("Connect to new AP [SUCCESS]"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:902: if ( _savewificallback != NULL) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:904: DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] _savewificallback calling"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:906: _savewificallback(); // @CALLBACK
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:908: if(!_connectonsave) return WL_IDLE_STATUS;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:913: DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] Connect to new AP Failed"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:919: // do save callback
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:920: // @todo this is more of an exiting callback than a save, clarify when this should actually occur
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:921: // confirm or verify data was saved to make this more accurate callback
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:922: if ( _savewificallback != NULL) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:924: DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] WiFi/Param save callback"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:926: _savewificallback(); // @CALLBACK
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:932: // clear save strings
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:975: server.reset();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:977: WiFi.scanDelete(); // free wifi scan results
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:982: dnsServer.reset();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1010: DEBUG_WM(WM_DEBUG_VERBOSE,F("configportal closed"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1028: // make sure sta is on before `begin` so it does not call enablesta->mode while persistent is ON ( which would save WM AP state to eeprom !)
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1044: // if ssid argument provided connect to that
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1048: // @todo connect=false seems to disconnect sta in begin() so not sure if _connectonsave is useful at all
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1051: if(_saveTimeout > 0){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1052: connRes = waitForConnectResult(_saveTimeout); // use default save timeout for saves to prevent bugs in esp->waitforconnectresult loop
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1060: // connect using saved ssid if there is one
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1067: DEBUG_WM(F("No wifi saved, skipping"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1089: if(connRes != WL_SCAN_COMPLETED){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1090: updateConxResult(connRes);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1097: * connect to a new wifi ap
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1102: * @return connect only save if false
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1122: * connect to stored wifi
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1130: DEBUG_WM(F("Connecting to SAVED AP:"),WiFi_SSID(true));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1164: DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom static IP/GW/Subnet/DNS"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1168: DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom static DNS"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1174: DEBUG_WM(WM_DEBUG_VERBOSE,F("Custom STA IP/GW/Subnet"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1193:void WiFiManager::updateConxResult(uint8_t status){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1281: page += _customHeadElement;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1336: String str = FPSTR(HTTP_ROOT_MAIN); // @todo custom title
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1346: if(_preloadwifiscan) WiFi_scanNetworks(_scancachetime,true); // preload wifiscan throttled, async
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1355:void WiFiManager::handleWifi(boolean scan) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1361: if (scan) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1365: WiFi_scanNetworks(server->hasArg(F("refresh")),false); //wifiscan, force if arg refresh
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1366: page += getScanItemOut();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1371: pitem.replace(FPSTR(T_v), F("wifisave")); // set form action
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1396: page += FPSTR(HTTP_SCAN_LINK);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1421: pitem.replace(FPSTR(T_v), F("paramsave"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1443: if((String)_menutokens[menuId] == "custom" && _customMenuHTML!=NULL){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1444: page += _customMenuHTML;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1454:// // is it possible in softap mode to detect aps without scanning
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1455:// bool WiFiManager::WiFi_scanNetworksForAP(bool force){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1456:// WiFi_scanNetworks(force);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1459:void WiFiManager::WiFi_scanComplete(int networksFound){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1460: _lastscan = millis();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1463: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC completed"), "in "+(String)(_lastscan - _startscan)+" ms");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1464: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC found:"),_numNetworks);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1468:bool WiFiManager::WiFi_scanNetworks(){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1469: return WiFi_scanNetworks(false,false);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1472:bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime,bool async){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1473: return WiFi_scanNetworks(millis()-_lastscan > cachetime,async);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1475:bool WiFiManager::WiFi_scanNetworks(unsigned int cachetime){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1476: return WiFi_scanNetworks(millis()-_lastscan > cachetime,false);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1478:bool WiFiManager::WiFi_scanNetworks(bool force,bool async){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1480: // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks async:",async == true);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1481: // DEBUG_WM(WM_DEBUG_DEV,_numNetworks,(millis()-_lastscan ));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1482: // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks force:",force == true);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1485: // if 0 networks, rescan @note this was a kludge, now disabling to test real cause ( maybe wifi not init etc)
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1487: if(_numNetworks == 0 && _autoforcerescan){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1488: DEBUG_WM(WM_DEBUG_DEV,"NO APs found forcing new scan");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1492: // if scan is empty or stale (last scantime > _scancachetime), this avoids fast reloading wifi page and constant scan delayed page loads appearing to freeze.
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1493: if(!_lastscan || (_lastscan>0 && (millis()-_lastscan > _scancachetime))){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1499: _startscan = millis();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1500: if(async && _asyncScan){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1504: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1507: WiFi.scanNetworksAsync(std::bind(&WiFiManager::WiFi_scanComplete,this,_1));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1509: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1510: res = WiFi.scanNetworks();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1514: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan ASYNC started"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1516: res = WiFi.scanNetworks(true);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1521: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan SYNC started"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1522: res = WiFi.scanNetworks();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1524: if(res == WIFI_SCAN_FAILED){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1526: DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan failed"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1529: else if(res == WIFI_SCAN_RUNNING){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1531: DEBUG_WM(WM_DEBUG_ERROR,F("[ERROR] scan waiting"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1533: while(WiFi.scanComplete() == WIFI_SCAN_RUNNING){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1539: _numNetworks = WiFi.scanComplete();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1542: _lastscan = millis();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1544: DEBUG_WM(WM_DEBUG_VERBOSE,F("WiFi Scan completed"), "in "+(String)(_lastscan - _startscan)+" ms");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1550: DEBUG_WM(WM_DEBUG_VERBOSE,F("Scan is cached"),(String)(millis()-_lastscan )+" ms ago");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1556:String WiFiManager::WiFiManager::getScanItemOut(){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1559: if(!_numNetworks) WiFi_scanNetworks(); // scan in case this gets called before any scans
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1564: DEBUG_WM(F("No networks found"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1617: HTTP_ITEM_STR.replace("{h}",_scanDispOptions ? "" : "h");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1619: HTTP_ITEM_STR.replace("{h}",_scanDispOptions ? "h" : "");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1697: // @todo how can we get these accurate settings from memory , wifi_get_ip_info does not seem to reveal if struct ip_info is static or not
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1769: // if no ID use customhtml for item, else generate from param string
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1779: if(tok_c)pitem.replace(FPSTR(T_c), _params[i]->getCustomHTML()); // T_c meant for additional attributes, not html, but can stuff
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1781: pitem = _params[i]->getCustomHTML();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1805: * HTTPD CALLBACK save form and redirect to WLAN config page again
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1807:void WiFiManager::handleWifiSave() {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1809: DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP WiFi save "));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1814: //SAVE/connect here
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1821: DEBUG_WM(WM_DEBUG_VERBOSE,F("Detected WiFi password change"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1826: String requestinfo = "SERVER_REQUEST\n----------------\n";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1827: requestinfo += "URI: ";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1828: requestinfo += server->uri();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1829: requestinfo += "\nMethod: ";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1830: requestinfo += (server->method() == HTTP_GET) ? "GET" : "POST";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1831: requestinfo += "\nArguments: ";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1832: requestinfo += server->args();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1833: requestinfo += "\n";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1835: requestinfo += " " + server->argName(i) + ": " + server->arg(i) + "\n";
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1838: DEBUG_WM(WM_DEBUG_MAX,requestinfo);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1872: if (_presavewificallback != NULL) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1873: _presavewificallback(); // @CALLBACK
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1876: if(_paramsInWifi) doParamSave();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1881: page = getHTTPHead(FPSTR(S_titlewifisettings)); // @token titleparamsaved
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1882: page += FPSTR(HTTP_PARAMSAVED);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1885: page = getHTTPHead(FPSTR(S_titlewifisaved)); // @token titlewifisaved
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1886: page += FPSTR(HTTP_SAVED);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1896: DEBUG_WM(WM_DEBUG_DEV,F("Sent wifi save page"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1899: connect = true; //signal ready to connect/reset process in processConfigPortal
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1902:void WiFiManager::handleParamSave() {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1905: DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Param save "));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1912: doParamSave();
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1914: String page = getHTTPHead(FPSTR(S_titleparamsaved)); // @token titleparamsaved
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1915: page += FPSTR(HTTP_PARAMSAVED);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1922: DEBUG_WM(WM_DEBUG_DEV,F("Sent param save page"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1926:void WiFiManager::doParamSave(){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1927: // @todo use new callback for before paramsaves, is this really needed?
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1928: if ( _presaveparamscallback != NULL) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1929: _presaveparamscallback(); // @CALLBACK
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1966: if ( _saveparamscallback != NULL) {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1967: _saveparamscallback(); // @CALLBACK
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1973: * HTTPD CALLBACK info page
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1975:void WiFiManager::handleInfo() {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1977: DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Info"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1980: String page = getHTTPHead(FPSTR(S_titleinfo)); // @token titleinfo
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1983: uint16_t infos = 0;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1986: //@todo wrap in build flag to remove all info code for memory saving
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1988: infos = 28;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:1989: String infoids[] = {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2002: F("lastreset"),
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2021: // add esp_chip_info ?
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2022: infos = 27;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2023: String infoids[] = {
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2034: F("lastreset"),
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2055: for(size_t i=0; i<infos;i++){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2056: if(infoids[i] != NULL) page += getInfoData(infoids[i]);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2061: page += getInfoData("aboutver");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2062: page += getInfoData("aboutarduinover");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2063: page += getInfoData("aboutidfver");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2064: page += getInfoData("aboutdate");
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2067: if(_showInfoUpdate){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2071: if(_showInfoErase) page += FPSTR(HTTP_ERASEBTN);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2079: DEBUG_WM(WM_DEBUG_DEV,F("Sent info page"));
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2083:String WiFiManager::getInfoData(String id){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2087: p = FPSTR(HTTP_INFO_esphead);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2093: p = FPSTR(HTTP_INFO_wifihead);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2098: p = FPSTR(HTTP_INFO_uptime);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2103: p = FPSTR(HTTP_INFO_chipid);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2108: p = FPSTR(HTTP_INFO_chiprev);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2120: p = FPSTR(HTTP_INFO_fchipid);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2125: p = FPSTR(HTTP_INFO_idesize);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2130: p = FPSTR(HTTP_INFO_flashsize);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2133: p = FPSTR(HTTP_INFO_psrsize);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2139: p = FPSTR(HTTP_INFO_corever);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2145: p = FPSTR(HTTP_INFO_bootver);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2150: p = FPSTR(HTTP_INFO_cpufreq);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2154: p = FPSTR(HTTP_INFO_freeheap);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2158: p = FPSTR(HTTP_INFO_memsketch);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2163: p = FPSTR(HTTP_INFO_memsmeter);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2167: else if(id==F("lastreset")){
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2169: p = FPSTR(HTTP_INFO_lastreset);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2170: p.replace(FPSTR(T_1),(String)ESP.getResetReason());
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2173: p = FPSTR(HTTP_INFO_lastreset);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2175: int reason = rtc_get_reset_reason(i);
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2180: case 1 : p.replace(tok,F("Vbat power on reset"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2181: case 3 : p.replace(tok,F("Software reset digital core"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2182: case 4 : p.replace(tok,F("Legacy watch dog reset digital core"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2183: case 5 : p.replace(tok,F("Deep Sleep reset digital core"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2184: case 6 : p.replace(tok,F("Reset by SLC module, reset digital core"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2185: case 7 : p.replace(tok,F("Timer Group0 Watch dog reset digital core"));break;
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp:2186: case 8 : p.replace(tok,F("Timer Group1 Watch dog reset digital core"));break;
[Showing results with pagination = limit: 250]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"agentType":"Explore","description":"查找报警和水浸逻辑","toolUseId":"call_00_wPSOk3gDw0mP3Mj0s8Xb0494","spawnDepth":1}

File diff suppressed because one or more lines are too long

View File

@@ -19,7 +19,7 @@
// --- 雷达型号选择 --- // --- 雷达型号选择 ---
#define RADAR_TYPE_ABD1 0 // R60ABD1: 心率/呼吸精准, 跌倒一般 #define RADAR_TYPE_ABD1 0 // R60ABD1: 心率/呼吸精准, 跌倒一般
#define RADAR_TYPE_AFD1 1 // R60AFD1: 跌倒/姿态精准, 无心率呼吸 #define RADAR_TYPE_AFD1 1 // R60AFD1: 跌倒/姿态精准, 无心率呼吸
#define RADAR_TYPE RADAR_TYPE_AFD1 // 演示用 AFD1 (跌倒精准) #define RADAR_TYPE RADAR_TYPE_AFD1 // AFD1 (跌倒/姿态精准)
// --- 毫米波雷达 --- // --- 毫米波雷达 ---
#define RADAR_UART_PORT Serial2 #define RADAR_UART_PORT Serial2
@@ -120,8 +120,9 @@
// 6. 上报配置 (协议规范 §4.1, §7.2) // 6. 上报配置 (协议规范 §4.1, §7.2)
// ============================================================ // ============================================================
#define REPORT_INTERVAL_MS 1000 // 遥测上报周期 1秒 #define REPORT_INTERVAL_MS 1000 // 遥测上报周期 1秒 (有人时)
#define REPORT_QOS 0 // 遥测 QoS 0 #define REPORT_QOS 0 // 遥测 QoS 0
#define PRESENCE_HEARTBEAT_MS 60000 // 无人心跳间隔 60秒 (4G省流量)
#define STATUS_QOS 1 // 状态/响应 QoS 1 #define STATUS_QOS 1 // 状态/响应 QoS 1
// ============================================================ // ============================================================
@@ -173,10 +174,11 @@
struct TelemetryData { struct TelemetryData {
// --- 设备标识 --- // --- 设备标识 ---
char saleid[12]; // 站点编号 "10100001" char saleid[12]; // 站点编号 (已弃用)
char gateid[16]; // 网关编号 "10100001001" char gateid[16]; // 网关编号
char meterId[8]; // 节点ID "ENV001" char meterId[8]; // 节点ID (已弃用, 新用 gateid+后缀)
char meterName[16]; // 设备型号 "RadarDHT11" char meterName[16]; // 设备型号 "RadarDHT11"
char deviceType[16]; // 设备类型枚举 bedroom/bathroom/...
// --- 环境数据 (DHT11) --- // --- 环境数据 (DHT11) ---
float temp; // 温度 ℃ float temp; // 温度 ℃
@@ -193,6 +195,8 @@ struct TelemetryData {
uint8_t heartRate; // 心率 bpm (无效=0) uint8_t heartRate; // 心率 bpm (无效=0)
uint8_t breathRate; // 呼吸 次/分 (无效=0) uint8_t breathRate; // 呼吸 次/分 (无效=0)
uint8_t body; // 人体能量/体征值 uint8_t body; // 人体能量/体征值
uint8_t sleepState; // 睡眠状态 0=清醒 1=浅睡 2=深睡
uint8_t sleepScore; // 睡眠评分 0~100
bool radarValid; // 雷达数据有效性 bool radarValid; // 雷达数据有效性
bool vitalValid; // 心率呼吸有效性 bool vitalValid; // 心率呼吸有效性

View File

@@ -83,6 +83,9 @@ public:
/** 本地报警上报 status/{gateid}/alarm (§4.7 扩展) */ /** 本地报警上报 status/{gateid}/alarm (§4.7 扩展) */
void publishLocalAlarm(uint8_t alarmType, bool triggered, const char* desc); void publishLocalAlarm(uint8_t alarmType, bool triggered, const char* desc);
/** 无人极简报文 (仅 Presence+RadarValid, 省4G流量) */
void publishPresenceOnly(bool presence, bool radarValid, uint32_t sequence);
// ============================================================ // ============================================================
// 回调注册 (协议规范 §5) // 回调注册 (协议规范 §5)
// ============================================================ // ============================================================

View File

@@ -21,6 +21,8 @@ struct RadarRawData {
uint8_t breathRate; // 呼吸 uint8_t breathRate; // 呼吸
uint8_t body; // 体动/能量值 uint8_t body; // 体动/能量值
uint16_t distance; // 距离 cm uint16_t distance; // 距离 cm
uint8_t sleepState; // 睡眠状态: 0=清醒 1=浅睡 2=深睡
uint8_t sleepScore; // 睡眠评分: 0~100
bool radarValid; // 雷达有效性 bool radarValid; // 雷达有效性
bool vitalValid; // 生命体征有效性 bool vitalValid; // 生命体征有效性

View File

@@ -74,6 +74,7 @@ enum SleepStatus : uint8_t {
SLEEP_AWAKE = 0, // 清醒 SLEEP_AWAKE = 0, // 清醒
SLEEP_LIGHT = 1, // 浅睡 SLEEP_LIGHT = 1, // 浅睡
SLEEP_DEEP = 2, // 深睡 SLEEP_DEEP = 2, // 深睡
SLEEP_ABSENT = 3, // 离床/无人/无效
}; };
// ============================================================ // ============================================================

View File

@@ -19,6 +19,10 @@
#include <WebServer.h> #include <WebServer.h>
#include <DNSServer.h> #include <DNSServer.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include "config.h" #include "config.h"
@@ -29,9 +33,10 @@ struct DeviceConfig {
char mqtt_user[20]; char mqtt_user[20];
char mqtt_pass[20]; char mqtt_pass[20];
char device_id[20]; char device_id[20];
char env_note[20]; // 环境备注 char env_note[20]; // 环境备注 (房间名)
char wifi_ssid[33]; // WiFi SSID (持久化) char env_type[16]; // 环境类型 bedroom/bathroom/...
char wifi_pass[65]; // WiFi 密码 (持久化) char wifi_ssid[33]; // WiFi SSID
char wifi_pass[65]; // WiFi 密码
}; };
class WiFiMgmt { class WiFiMgmt {
@@ -50,6 +55,8 @@ public:
const char* getDeviceId() const; const char* getDeviceId() const;
const char* getEnvNote() const; const char* getEnvNote() const;
void startConfigPortal(); void startConfigPortal();
void handleBLEProvision(const char* json); // BLE 配网回调
void enqueueBLEJson(const char* json); // BLE 线程安全入队 (仅暂存)
void resetSettings(); void resetSettings();
private: private:
@@ -70,6 +77,10 @@ private:
void _updateWifiScan(); // WiFi 扫描缓存 void _updateWifiScan(); // WiFi 扫描缓存
String _htmlPage(); // 返回配网 HTML 页面 String _htmlPage(); // 返回配网 HTML 页面
// BLE 蓝牙配网
void _startBLE();
void _stopBLE();
WebServer* _server; WebServer* _server;
DNSServer* _dns; DNSServer* _dns;
DeviceConfig _config; DeviceConfig _config;
@@ -91,6 +102,17 @@ private:
// LED 独立任务 // LED 独立任务
TaskHandle_t _ledTaskHandle; TaskHandle_t _ledTaskHandle;
volatile uint8_t _ledMode; // 0=灭 1=慢闪 2=快闪 3=常亮 volatile uint8_t _ledMode; // 0=灭 1=慢闪 2=快闪 3=常亮
// BLE
BLEServer* _bleServer;
BLECharacteristic* _bleInfoChar; // READ: 设备信息
BLECharacteristic* _bleChar; // WRITE+NOTIFY: 配网
BLEAdvertising* _bleAdv;
// BLE 回调保护: 不在 BTC_TASK 栈里做 Flash 写操作
String _blePendingJson; // 待处理的 BLE 配网 JSON
bool _blePendingFlag; // 有未处理的 BLE 数据
void _processBLEPending(); // 在 loop() 上下文处理
}; };
#endif // WIFI_MANAGER_H #endif // WIFI_MANAGER_H

View File

@@ -1,262 +0,0 @@
/**
* wm_strings_zh.h
* 中文语言包 for WiFiManager
* 基于 wm_strings_en.h 翻译
*
* WiFiManager, a library for the ESP8266/Arduino platform
* for configuration of WiFi credentials using a Captive Portal
*
* @author Creator tzapu
* @author tablatronix
* @version 0.0.0
* @license MIT
*/
#ifndef _WM_STRINGS_ZH_H_
#define _WM_STRINGS_ZH_H_
// strings files must include a consts file!
#include "wm_consts_en.h" // include constants, tokens, routes
const char WM_LANGUAGE[] PROGMEM = "zh-CN"; // i18n lang code
const char HTTP_HEAD_START[] PROGMEM = "<!DOCTYPE html>"
"<html lang='zh-CN'><head>"
"<meta name='format-detection' content='telephone=no'>"
"<meta charset='UTF-8'>"
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
"<title>{v}</title>";
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){"
"document.getElementById('s').value=l.getAttribute('data-ssid')||l.innerText||l.textContent;"
"p = l.nextElementSibling.classList.contains('l');"
"document.getElementById('p').disabled = !p;"
"if(p)document.getElementById('p').focus();};"
"function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}"
"</script>";
const char HTTP_HEAD_END[] PROGMEM = "</head><body class='{c}'><div class='wrap'>"; // {c} = _bodyclass
const char HTTP_ROOT_MAIN[] PROGMEM = "<h1>{t}</h1><h3>{v}</h3>";
const char * const HTTP_PORTAL_MENU[] PROGMEM = {
"<form action='/wifi' method='get'><button>配置 WiFi</button></form><br/>\n", // MENU_WIFI
"<form action='/0wifi' method='get'><button>配置 WiFi (不扫描)</button></form><br/>\n", // MENU_WIFINOSCAN
"<form action='/info' method='get'><button>系统信息</button></form><br/>\n", // MENU_INFO
"<form action='/param' method='get'><button>参数设置</button></form><br/>\n", // MENU_PARAM
"<form action='/close' method='get'><button>关闭页面</button></form><br/>\n", // MENU_CLOSE
"<form action='/restart' method='get'><button>重启设备</button></form><br/>\n", // MENU_RESTART
"<form action='/exit' method='get'><button>退出配网</button></form><br/>\n", // MENU_EXIT
"<form action='/erase' method='get'><button class='D'>清除配置</button></form><br/>\n", // MENU_ERASE
"<form action='/update' method='get'><button>固件升级</button></form><br/>\n", // MENU_UPDATE
"<hr><br/>" // MENU_SEP
};
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "";
const char HTTP_ITEM_QI[] PROGMEM = "<div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i} {h}'></div>";
const char HTTP_ITEM_QP[] PROGMEM = "<div class='q {h}'>{r}%</div>";
const char HTTP_ITEM[] PROGMEM = "<div><a href='#p' onclick='c(this)' data-ssid='{V}'>{v}</a>{qi}{qp}</div>";
const char HTTP_FORM_START[] PROGMEM = "<form method='POST' action='{v}'>";
const char HTTP_FORM_WIFI[] PROGMEM = "<label for='s'>WiFi 名称 (SSID)</label><input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'><br/><label for='p'>WiFi 密码</label><input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' id='showpass' onclick='f()'> <label for='showpass'>显示密码</label><br/>";
const char HTTP_FORM_WIFI_END[] PROGMEM = "";
const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "<hr><br/>";
const char HTTP_FORM_END[] PROGMEM = "<br/><br/><button type='submit'>保存配置</button></form>";
const char HTTP_FORM_LABEL[] PROGMEM = "<label for='{i}'>{t}</label>";
const char HTTP_FORM_PARAM_HEAD[] PROGMEM = "<hr><br/>";
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n";
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>重新扫描</button></form>";
const char HTTP_SAVED[] PROGMEM = "<div class='msg'>正在保存配置<br/>尝试连接 WiFi...<br/>如果连接失败,请重新连接 AP 再试一次</div>";
const char HTTP_PARAMSAVED[] PROGMEM = "<div class='msg S'>配置已保存<br/></div>";
const char HTTP_END[] PROGMEM = "</div></body></html>";
const char HTTP_ERASEBTN[] PROGMEM = "<br/><form action='/erase' method='get'><button class='D'>清除 WiFi 配置</button></form>";
const char HTTP_UPDATEBTN[] PROGMEM = "<br/><form action='/update' method='get'><button>固件升级</button></form>";
const char HTTP_BACKBTN[] PROGMEM = "<hr><br/><form action='/' method='get'><button>返回首页</button></form>";
const char HTTP_STATUS_ON[] PROGMEM = "<div class='msg S'><strong>已连接</strong> {v}<br/><em><small>IP 地址: {i}</small></em></div>";
const char HTTP_STATUS_OFF[] PROGMEM = "<div class='msg {c}'><strong>未连接</strong> {v}{r}</div>";
const char HTTP_STATUS_OFFPW[] PROGMEM = "<br/>WiFi 密码错误";
const char HTTP_STATUS_OFFNOAP[] PROGMEM = "<br/>未找到该 WiFi";
const char HTTP_STATUS_OFFFAIL[] PROGMEM = "<br/>无法连接";
const char HTTP_STATUS_NONE[] PROGMEM = "<div class='msg'>未设置 WiFi</div>";
const char HTTP_BR[] PROGMEM = "<br/>";
const char HTTP_STYLE[] PROGMEM = "<style>"
".c,body{text-align:center;font-family:'PingFang SC','Microsoft YaHei','Helvetica Neue',sans-serif}div,input,select{padding:5px;font-size:1em;margin:5px 0;box-sizing:border-box}"
"input,button,select,.msg{border-radius:.3rem;width: 100%}input[type=radio],input[type=checkbox]{width:auto}"
"button,input[type='button'],input[type='submit']{cursor:pointer;border:0;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%}"
"input[type='file']{border:1px solid #1fa3ec}"
".wrap {text-align:left;display:inline-block;min-width:260px;max-width:500px}"
"a{color:#000;font-weight:700;text-decoration:none}a:hover{color:#1fa3ec;text-decoration:underline}"
".q{height:16px;margin:0;padding:0 5px;text-align:right;min-width:38px;float:right}.q.q-0:after{background-position-x:0}.q.q-1:after{background-position-x:-16px}.q.q-2:after{background-position-x:-32px}.q.q-3:after{background-position-x:-48px}.q.q-4:after{background-position-x:-64px}.q.l:before{background-position-x:-80px;padding-right:5px}.ql .q{float:left}.q:after,.q:before{content:'';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;"
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');}"
"@media (-webkit-min-device-pixel-ratio: 2),(min-resolution: 192dpi){.q:before,.q:after {"
"background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');"
"background-size: 95px 16px;}}"
".msg{padding:20px;margin:20px 0;border:1px solid #eee;border-left-width:5px;border-left-color:#777}.msg h4{margin-top:0;margin-bottom:5px}.msg.P{border-left-color:#1fa3ec}.msg.P h4{color:#1fa3ec}.msg.D{border-left-color:#dc3630}.msg.D h4{color:#dc3630}.msg.S{border-left-color: #5cb85c}.msg.S h4{color: #5cb85c}"
"dt{font-weight:bold}dd{margin:0;padding:0 0 0.5em 0;min-height:12px}"
"td{vertical-align: top;}"
".h{display:none}"
"button{transition: 0s opacity;transition-delay: 3s;transition-duration: 0s;cursor: pointer}"
"button.D{background-color:#dc3630}"
"button:active{opacity:50% !important;cursor:wait;transition-delay: 0s}"
"body.invert,body.invert a,body.invert h1 {background-color:#060606;color:#fff;}"
"body.invert .msg{color:#fff;background-color:#282828;border-top:1px solid #555;border-right:1px solid #555;border-bottom:1px solid #555;}"
"body.invert .q[role=img]{-webkit-filter:invert(1);filter:invert(1);}"
":disabled {opacity: 0.5;}"
"</style>";
#ifndef WM_NOHELP
const char HTTP_HELP[] PROGMEM =
"<br/><h3>可用页面</h3><hr>"
"<table class='table'>"
"<thead><tr><th>页面</th><th>功能</th></tr></thead><tbody>"
"<tr><td><a href='/'>/</a></td>"
"<td>主菜单页面</td></tr>"
"<tr><td><a href='/wifi'>/wifi</a></td>"
"<td>显示 WiFi 扫描结果并配置连接 (/0wifi 不扫描)</td></tr>"
"<tr><td><a href='/wifisave'>/wifisave</a></td>"
"<td>保存 WiFi 配置并连接设备</td></tr>"
"<tr><td><a href='/param'>/param</a></td>"
"<td>参数设置页面</td></tr>"
"<tr><td><a href='/info'>/info</a></td>"
"<td>系统信息页面</td></tr>"
"<tr><td><a href='/u'>/u</a></td>"
"<td>固件 OTA 升级</td></tr>"
"<tr><td><a href='/close'>/close</a></td>"
"<td>关闭弹出窗口,配网服务保持运行</td></tr>"
"<tr><td>/exit</td>"
"<td>退出配网模式,配网服务将关闭</td></tr>"
"<tr><td>/restart</td>"
"<td>重启设备</td></tr>"
"<tr><td>/erase</td>"
"<td>清除 WiFi 配置并重启。清除后设备不会自动连接,需重新配网</td></tr>"
"</table>"
"<p/>GitHub <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
#else
const char HTTP_HELP[] PROGMEM = "";
#endif
const char HTTP_UPDATE[] PROGMEM = "上传新固件<br/><form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>开始升级</button></form><small><a href='http://192.168.4.1/update' target='_blank'>* 如果在配网页面内无法使用,请在浏览器中打开 http://192.168.4.1</a><small>";
const char HTTP_UPDATE_FAIL[] PROGMEM = "<div class='msg D'><strong>升级失败!</strong><Br/>请重启设备后重试</div>";
const char HTTP_UPDATE_SUCCESS[] PROGMEM = "<div class='msg S'><strong>升级成功!</strong><Br/>设备正在重启...</div>";
#ifdef WM_JSTEST
const char HTTP_JS[] PROGMEM =
"<script>function postAjax(url, data, success) {"
" var params = typeof data == 'string' ? data : Object.keys(data).map("
" function(k){ return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) }"
" ).join('&');"
" var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject(\"Microsoft.XMLHTTP\");"
" xhr.open('POST', url);"
" xhr.onreadystatechange = function() {"
" if (xhr.readyState>3 && xhr.status==200) { success(xhr.responseText); }"
" };"
" xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');"
" xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');"
" xhr.send(params);"
" return xhr;}"
"postAjax('/status', 'p1=1&p2=Hello+World', function(data){ console.log(data); });"
"postAjax('/status', { p1: 1, p2: 'Hello World' }, function(data){ console.log(data); });"
"</script>";
#endif
// Info html
#ifdef ESP32
const char HTTP_INFO_esphead[] PROGMEM = "<h3>ESP32 信息</h3><hr><dl>";
const char HTTP_INFO_chiprev[] PROGMEM = "<dt>芯片版本</dt><dd>{1}</dd>";
const char HTTP_INFO_lastreset[] PROGMEM = "<dt>上次复位原因</dt><dd>CPU0: {1}<br/>CPU1: {2}</dd>";
const char HTTP_INFO_aphost[] PROGMEM = "<dt>AP 主机名</dt><dd>{1}</dd>";
const char HTTP_INFO_psrsize[] PROGMEM = "<dt>PSRAM 大小</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_temp[] PROGMEM = "<dt>芯片温度</dt><dd>{1}°C / {2}°F</dd>";
const char HTTP_INFO_hall[] PROGMEM = "<dt>霍尔传感器</dt><dd>{1}</dd>";
#else
const char HTTP_INFO_esphead[] PROGMEM = "<h3>ESP8266 信息</h3><hr><dl>";
const char HTTP_INFO_fchipid[] PROGMEM = "<dt>Flash 芯片 ID</dt><dd>{1}</dd>";
const char HTTP_INFO_corever[] PROGMEM = "<dt>Core 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_bootver[] PROGMEM = "<dt>Boot 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_lastreset[] PROGMEM = "<dt>上次复位原因</dt><dd>{1}</dd>";
const char HTTP_INFO_flashsize[] PROGMEM = "<dt>实际 Flash 大小</dt><dd>{1} 字节</dd>";
#endif
const char HTTP_INFO_memsmeter[] PROGMEM = "<br/><progress value='{1}' max='{2}'></progress></dd>";
const char HTTP_INFO_memsketch[] PROGMEM = "<dt>内存 - 程序大小</dt><dd>已用 / 总计<br/>{1} / {2} 字节";
const char HTTP_INFO_freeheap[] PROGMEM = "<dt>内存 - 可用堆</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_wifihead[] PROGMEM = "<br/><h3>WiFi 信息</h3><hr>";
const char HTTP_INFO_uptime[] PROGMEM = "<dt>运行时间</dt><dd>{1} 分 {2} 秒</dd>";
const char HTTP_INFO_chipid[] PROGMEM = "<dt>芯片 ID</dt><dd>{1}</dd>";
const char HTTP_INFO_idesize[] PROGMEM = "<dt>Flash 大小</dt><dd>{1} 字节</dd>";
const char HTTP_INFO_sdkver[] PROGMEM = "<dt>SDK 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_cpufreq[] PROGMEM = "<dt>CPU 频率</dt><dd>{1}MHz</dd>";
const char HTTP_INFO_apip[] PROGMEM = "<dt>AP IP 地址</dt><dd>{1}</dd>";
const char HTTP_INFO_apmac[] PROGMEM = "<dt>AP MAC 地址</dt><dd>{1}</dd>";
const char HTTP_INFO_apssid[] PROGMEM = "<dt>AP SSID</dt><dd>{1}</dd>";
const char HTTP_INFO_apbssid[] PROGMEM = "<dt>BSSID</dt><dd>{1}</dd>";
const char HTTP_INFO_stassid[] PROGMEM = "<dt>Station SSID</dt><dd>{1}</dd>";
const char HTTP_INFO_staip[] PROGMEM = "<dt>Station IP</dt><dd>{1}</dd>";
const char HTTP_INFO_stagw[] PROGMEM = "<dt>网关地址</dt><dd>{1}</dd>";
const char HTTP_INFO_stasub[] PROGMEM = "<dt>子网掩码</dt><dd>{1}</dd>";
const char HTTP_INFO_dnss[] PROGMEM = "<dt>DNS 服务器</dt><dd>{1}</dd>";
const char HTTP_INFO_host[] PROGMEM = "<dt>主机名</dt><dd>{1}</dd>";
const char HTTP_INFO_stamac[] PROGMEM = "<dt>Station MAC</dt><dd>{1}</dd>";
const char HTTP_INFO_conx[] PROGMEM = "<dt>连接状态</dt><dd>{1}</dd>";
const char HTTP_INFO_autoconx[] PROGMEM = "<dt>自动连接</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutver[] PROGMEM = "<dt>WiFiManager 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutarduino[] PROGMEM = "<dt>Arduino 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutsdk[] PROGMEM = "<dt>ESP-SDK/IDF 版本</dt><dd>{1}</dd>";
const char HTTP_INFO_aboutdate[] PROGMEM = "<dt>构建日期</dt><dd>{1}</dd>";
// ============================================================
// 文本常量
// ============================================================
const char S_brand[] PROGMEM = "健康监测配网";
const char S_debugPrefix[] PROGMEM = "*wm:";
const char S_y[] PROGMEM = "";
const char S_n[] PROGMEM = "";
const char S_enable[] PROGMEM = "已启用";
const char S_disable[] PROGMEM = "已禁用";
const char S_GET[] PROGMEM = "GET";
const char S_POST[] PROGMEM = "POST";
const char S_NA[] PROGMEM = "未知";
const char S_passph[] PROGMEM = "********";
const char S_titlewifisaved[] PROGMEM = "WiFi 已保存";
const char S_titlewifisettings[] PROGMEM = "设置已保存";
const char S_titlewifi[] PROGMEM = "配置 WiFi";
const char S_titleinfo[] PROGMEM = "系统信息";
const char S_titleparam[] PROGMEM = "参数设置";
const char S_titleparamsaved[] PROGMEM = "设置已保存";
const char S_titleexit[] PROGMEM = "退出配网";
const char S_titlereset[] PROGMEM = "重置";
const char S_titleerase[] PROGMEM = "清除配置";
const char S_titleclose[] PROGMEM = "关闭页面";
const char S_options[] PROGMEM = "选项";
const char S_nonetworks[] PROGMEM = "未发现 WiFi 网络,请点击刷新重新扫描。";
const char S_staticip[] PROGMEM = "静态 IP";
const char S_staticgw[] PROGMEM = "静态网关";
const char S_staticdns[] PROGMEM = "静态 DNS";
const char S_subnet[] PROGMEM = "子网掩码";
const char S_exiting[] PROGMEM = "正在退出配网模式";
const char S_resetting[] PROGMEM = "设备将在几秒后重启";
const char S_closing[] PROGMEM = "可以关闭此页面,配网服务将继续运行";
const char S_error[] PROGMEM = "发生错误";
const char S_notfound[] PROGMEM = "页面未找到\n\n";
const char S_uri[] PROGMEM = "URI: ";
const char S_method[] PROGMEM = "\n请求方法: ";
const char S_args[] PROGMEM = "\n参数: ";
const char S_parampre[] PROGMEM = "param_";
// debug strings
const char D_HR[] PROGMEM = "--------------------";
// softap ssid default prefix
#ifdef ESP8266
const char S_ssidpre[] PROGMEM = "ESP";
#elif defined(ESP32)
const char S_ssidpre[] PROGMEM = "ESP32";
#else
const char S_ssidpre[] PROGMEM = "WM";
#endif
#endif

View File

@@ -222,11 +222,13 @@ TelemetryData buildTelemetry(const RadarRawData& radar) {
memset(&t, 0, sizeof(t)); memset(&t, 0, sizeof(t));
// 设备标识 // 设备标识
strncpy(t.saleid, SALEID_DEFAULT, sizeof(t.saleid) - 1);
strncpy(t.gateid, g_mqtt.getGateId(), sizeof(t.gateid) - 1); strncpy(t.gateid, g_mqtt.getGateId(), sizeof(t.gateid) - 1);
strncpy(t.meterId, METER_ID_DEFAULT, sizeof(t.meterId) - 1);
strncpy(t.meterName, METER_NAME_DEFAULT, sizeof(t.meterName) - 1); strncpy(t.meterName, METER_NAME_DEFAULT, sizeof(t.meterName) - 1);
// 环境类型: 从配网配置读取 (默认 bedroom)
DeviceConfig cfg = g_wifi.getConfig();
strncpy(t.deviceType, cfg.env_type[0] ? cfg.env_type : "bedroom", sizeof(t.deviceType)-1);
// DHT11 // DHT11
t.temp = g_sensor.getTemp(); t.temp = g_sensor.getTemp();
t.humi = g_sensor.getHumi(); t.humi = g_sensor.getHumi();
@@ -240,6 +242,8 @@ TelemetryData buildTelemetry(const RadarRawData& radar) {
t.heartRate = radar.heartRate; t.heartRate = radar.heartRate;
t.breathRate = radar.breathRate; t.breathRate = radar.breathRate;
t.body = radar.body; t.body = radar.body;
t.sleepState = radar.sleepState;
t.sleepScore = radar.sleepScore;
t.radarValid = radar.radarValid; t.radarValid = radar.radarValid;
t.vitalValid = radar.vitalValid; t.vitalValid = radar.vitalValid;
@@ -271,11 +275,18 @@ void onLocalAlarmChange(uint8_t type, bool triggered, const char* desc) {
// ============================================================ // ============================================================
void checkLocalAlarms(const RadarRawData& radar) { void checkLocalAlarms(const RadarRawData& radar) {
// 环境类型 → 报警开关
const char* env = g_wifi.getConfig().env_type;
bool enFall = (strcmp(env, "bathroom") == 0 || strcmp(env, "hallway") == 0 || strcmp(env, "livingroom") == 0);
bool enVital = (strcmp(env, "bedroom") == 0);
bool enWater = (strcmp(env, "bathroom") == 0);
bool enEnv = (strcmp(env, "bedroom") == 0 || strcmp(env, "balcony") == 0 || strcmp(env, "kitchen") == 0);
// ─── 前置条件: 有人才检测人体生命体征 ─── // ─── 前置条件: 有人才检测人体生命体征 ───
bool humanPresent = radar.presence && radar.radarValid; bool humanPresent = radar.presence && radar.radarValid;
// ─── ★★★★★ 人身安全: 跌倒 (有人+30s+2s确认, 5s清除) ─── // ─── ★★★★★ 人身安全: 跌倒 (仅浴室/走廊/客厅) ───
if (radar.fall && humanPresent && !g_fallDismissed && millis() > 30000) { if (enFall && radar.fall && humanPresent && !g_fallDismissed && millis() > 30000) {
if (!g_localFallActive) { if (!g_localFallActive) {
// 持续检测中: 必须满 FALL_DETECT_MS 才报警 // 持续检测中: 必须满 FALL_DETECT_MS 才报警
if (g_fallDetectMs == 0) { if (g_fallDetectMs == 0) {
@@ -311,9 +322,9 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
} }
// ─── ★★★★★ 人身安全: 心率异常 (仅 ABD1, 防抖3次, 30秒后生效) ─── // ─── ★★★★★ 人身安全: 心率异常 (仅卧室+ABD1, 防抖3次, 30s) ───
#if RADAR_TYPE != RADAR_TYPE_AFD1 #if RADAR_TYPE != RADAR_TYPE_AFD1
if (millis() > 30000 && humanPresent && radar.vitalValid && radar.heartRate > 0) { if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.heartRate > 0) {
bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX); bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
if (heartAbn) { if (heartAbn) {
if (++g_heartAbnCnt >= ALARM_HYSTERESIS && !g_localHeartAbnActive) { if (++g_heartAbnCnt >= ALARM_HYSTERESIS && !g_localHeartAbnActive) {
@@ -336,9 +347,9 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
#endif // RADAR_TYPE != AFD1 #endif // RADAR_TYPE != AFD1
// ─── ★★★★★ 人身安全: 呼吸异常 (仅 ABD1, 防抖3次, 30秒后生效) ─── // ─── ★★★★★ 人身安全: 呼吸异常 (仅卧室+ABD1, 防抖3次, 30秒后生效) ───
#if RADAR_TYPE != RADAR_TYPE_AFD1 #if RADAR_TYPE != RADAR_TYPE_AFD1
if (millis() > 30000 && humanPresent && radar.vitalValid && radar.breathRate > 0) { if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.breathRate > 0) {
bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX); bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
if (breathAbn) { if (breathAbn) {
if (++g_breathAbnCnt >= ALARM_HYSTERESIS && !g_localBreathAbnActive) { if (++g_breathAbnCnt >= ALARM_HYSTERESIS && !g_localBreathAbnActive) {
@@ -361,7 +372,8 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
#endif // RADAR_TYPE != AFD1 #endif // RADAR_TYPE != AFD1
// ─── ★★★ 环境安全: 水浸 ─── // ─── ★★★ 环境安全: 水浸 (仅浴室) ───
if (enWater) {
bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid(); bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
if (waterLeak && !g_localWaterActive) { if (waterLeak && !g_localWaterActive) {
g_localWaterActive = true; g_localWaterActive = true;
@@ -370,9 +382,10 @@ void checkLocalAlarms(const RadarRawData& radar) {
g_localWaterActive = false; g_localWaterActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK); g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
} }
} // enWater
// ─── ★★ 环境舒适: 温度异常 (防抖3次, 30秒启动静默) ─── // ─── ★★ 环境舒适: 温湿度异常 (仅卧室/阳台/厨房, 防抖3次, 30s) ───
if (millis() > 30000 && g_sensor.isDhtValid()) { if (enEnv && millis() > 30000 && g_sensor.isDhtValid()) {
float temp = g_sensor.getTemp(); float temp = g_sensor.getTemp();
bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH); bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH);
if (tempAbn) { if (tempAbn) {
@@ -588,20 +601,22 @@ void loop() {
static bool wasDisconnected = true; static bool wasDisconnected = true;
if (g_mqtt.isConnected()) { if (g_mqtt.isConnected()) {
if (wasDisconnected) { if (wasDisconnected) {
// 刚恢复连接, 同步时间
syncTime(); syncTime();
wasDisconnected = false; wasDisconnected = false;
} }
// 配网成功后更新 MQTT 配置 (如果有改动) 并上报
if (g_wifi.justProvisioned()) {
DeviceConfig cfg = g_wifi.getConfig();
g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
cfg.mqtt_user, cfg.mqtt_pass);
g_mqtt.publishProvisionStatus("success", "");
}
} else { } else {
wasDisconnected = true; wasDisconnected = true;
} }
// 配网成功后: 先更新 MQTT 配置, 再强制重连
if (g_wifi.justProvisioned()) {
DeviceConfig cfg = g_wifi.getConfig();
g_mqtt.disconnect();
g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
cfg.mqtt_user, cfg.mqtt_pass);
g_mqtt.connect();
g_mqtt.publishProvisionStatus("success", "");
}
} }
// --- 3. 传感器 (独立,任何传感器故障不影响其他) --- // --- 3. 传感器 (独立,任何传感器故障不影响其他) ---
@@ -626,19 +641,42 @@ void loop() {
// --- 5. 报警模块更新 (定时器/闪烁) --- // --- 5. 报警模块更新 (定时器/闪烁) ---
g_alarm.loop(); g_alarm.loop();
// --- 6. 遥测上报 (1秒周期) --- // --- 6. 遥测上报 ---
if (now - g_lastReportTime >= REPORT_INTERVAL_MS) { // 有人时: 每秒全量上报
g_lastReportTime = now; // 无人时: 仅状态变化时发极简报文 + 60秒心跳 (省4G流量)
static bool lastPresence = false;
static uint32_t lastHeartbeat = 0; // 无人心跳计时
if (radar.presence) {
// ── 有人: 每秒完整上报 ──
if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {
g_lastReportTime = now;
TelemetryData tele = buildTelemetry(radar);
g_mqtt.publishAllPoints(tele, g_sequence);
g_sequence++;
lastHeartbeat = now; // 有人时重置心跳计时
printDebug(tele);
g_display.update(tele, wifiOk, g_alarm.isBuzzerOn(),
g_alarm.hasActiveAlarm(), g_alarm.getActiveAlarmStr());
}
} else {
// ── 无人: 状态翻转发一次极简报文, 之后每60秒心跳 ──
if (lastPresence != radar.presence || now - lastHeartbeat >= PRESENCE_HEARTBEAT_MS) {
g_mqtt.publishPresenceOnly(radar.presence, radar.radarValid, g_sequence);
g_sequence++;
lastHeartbeat = now;
// 调试输出
Serial.printf("[上报] 无人极简报文 (Presence=0) 序号:%d\n", g_sequence - 1);
}
// OLED 还是照常刷新 (显示无人状态)
TelemetryData tele = buildTelemetry(radar); TelemetryData tele = buildTelemetry(radar);
g_mqtt.publishAllPoints(tele, g_sequence);
g_sequence++;
// 调试输出 + OLED 显示
printDebug(tele);
g_display.update(tele, wifiOk, g_alarm.isBuzzerOn(), g_display.update(tele, wifiOk, g_alarm.isBuzzerOn(),
g_alarm.hasActiveAlarm(), g_alarm.getActiveAlarmStr()); g_alarm.hasActiveAlarm(), g_alarm.getActiveAlarmStr());
} }
lastPresence = radar.presence;
// --- 7. MQTT 配网状态 (WiFiManager 触发时) --- // --- 7. MQTT 配网状态 (WiFiManager 触发时) ---
// WiFiManager 的 captive portal 启动/关闭时更新状态 // WiFiManager 的 captive portal 启动/关闭时更新状态

View File

@@ -178,20 +178,23 @@ void MQTTManager::publishAllPoints(const TelemetryData& d, uint32_t sequence) {
snprintf(timeStr, sizeof(timeStr), "2026-01-01 00:00:00"); snprintf(timeStr, sizeof(timeStr), "2026-01-01 00:00:00");
} }
// 构建 JSON (协议规范 §4.1) // 构建 JSON (协议规范 V0.08)
JsonDocument doc; JsonDocument doc;
doc["saleid"] = d.saleid;
doc["gateid"] = d.gateid; doc["gateid"] = d.gateid;
doc["type"] = "report"; doc["type"] = "report";
doc["time"] = timeStr; doc["time"] = timeStr;
doc["sequence"] = String(sequence); doc["sequence"] = String(sequence);
doc["source"] = "da"; doc["source"] = "da";
// meter.id = gateid + 后缀 (如 10100001001 → 10100001001001)
String meterId = String(d.gateid) + "001";
JsonArray meter = doc["meter"].to<JsonArray>(); JsonArray meter = doc["meter"].to<JsonArray>();
JsonObject m = meter.add<JsonObject>(); JsonObject m = meter.add<JsonObject>();
m["id"] = d.meterId; m["id"] = meterId;
m["status"] = "1"; m["status"] = "1";
m["name"] = d.meterName; m["name"] = d.meterName;
m["type"] = d.deviceType; // 设备类型枚举
JsonObject v = m["values"].to<JsonObject>(); JsonObject v = m["values"].to<JsonObject>();
// 环境数据 // 环境数据
@@ -205,6 +208,8 @@ void MQTTManager::publishAllPoints(const TelemetryData& d, uint32_t sequence) {
v["Resident"] = d.resident ? "1" : "0"; v["Resident"] = d.resident ? "1" : "0";
// 生命体征 // 生命体征
v["Body"] = String(d.body); v["Body"] = String(d.body);
v["SleepState"] = String(d.sleepState);
v["SleepScore"] = String(d.sleepScore);
v["HeartRate"] = String(d.heartRate); v["HeartRate"] = String(d.heartRate);
v["BreathRate"] = String(d.breathRate); v["BreathRate"] = String(d.breathRate);
v["RadarValid"] = d.radarValid ? "1" : "0"; v["RadarValid"] = d.radarValid ? "1" : "0";
@@ -416,6 +421,47 @@ void MQTTManager::publishLocalAlarm(uint8_t alarmType, bool triggered, const cha
_mqtt.publish(topic.c_str(), payload.c_str()); _mqtt.publish(topic.c_str(), payload.c_str());
} }
// ============================================================
// 上行: 无人极简报文 (省4G流量, 仅 Presence + RadarValid)
// ============================================================
void MQTTManager::publishPresenceOnly(bool presence, bool radarValid, uint32_t sequence) {
if (!isConnected()) return;
struct tm timeinfo;
char timeStr[24];
if (getLocalTime(&timeinfo)) {
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", &timeinfo);
} else {
snprintf(timeStr, sizeof(timeStr), "2026-01-01 00:00:00");
}
// 极简 JSON: ~120 字节 vs 完整报文 ~500 字节
JsonDocument doc;
doc["gateid"] = _gateid;
doc["type"] = "report";
doc["time"] = timeStr;
doc["sequence"] = String(sequence);
doc["source"] = "da";
String meterId = String(_gateid) + "001";
JsonArray meter = doc["meter"].to<JsonArray>();
JsonObject m = meter.add<JsonObject>();
m["id"] = meterId;
m["status"] = "1";
m["name"] = METER_NAME_DEFAULT;
JsonObject v = m["values"].to<JsonObject>();
v["Presence"] = presence ? "1" : "0";
v["RadarValid"] = radarValid ? "1" : "0";
String payload;
serializeJson(doc, payload);
_mqtt.publish("report/allpoints", payload.c_str());
}
// ============================================================ // ============================================================
// 回调注册 // 回调注册
// ============================================================ // ============================================================

View File

@@ -379,16 +379,15 @@ void RadarManager::_extractSleep(const RadarFrame& frame) {
} }
if (frame.data_length >= 3) { if (frame.data_length >= 3) {
// data[0]: 入床/离床 _data.sleepState = frame.data[1]; // 0=清醒 1=浅睡 2=深睡
// data[1]: 睡眠状态 _data.sleepScore = frame.data[2]; // 0~100
// data[2]: 评分 _data.lastUpdate = millis();
_notifyUpdate();
// 跌倒检测: 可能在睡眠异常标志中 }
if (frame.data_length >= 4) {
_data.fall = (frame.data[3] & 0x01) != 0;
_data.resident = (frame.data[3] & 0x02) != 0;
}
if (frame.data_length >= 4) {
_data.fall = (frame.data[3] & 0x01) != 0;
_data.resident = (frame.data[3] & 0x02) != 0;
_data.lastUpdate = millis(); _data.lastUpdate = millis();
_notifyUpdate(); _notifyUpdate();
} }

View File

@@ -5,6 +5,18 @@
#include "wifi_manager.h" #include "wifi_manager.h"
// BLE 配网回调: 小程序写入 JSON → 仅在 BLE 栈中暂存, 不直接处理
class BLEProvisionCB : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* ch) override {
std::string val = ch->getValue();
if (val.length() > 0 && WiFiMgmt::_instance) {
Serial.printf("[BLE] 收到配网数据 (%d 字节)\n", val.length());
// 仅暂存 → 在 loop() 里处理 (BTC_TASK 栈太小, 写 Flash 会栈溢出)
WiFiMgmt::_instance->enqueueBLEJson(val.c_str());
}
}
};
WiFiMgmt* WiFiMgmt::_instance = nullptr; WiFiMgmt* WiFiMgmt::_instance = nullptr;
WiFiMgmt::WiFiMgmt() WiFiMgmt::WiFiMgmt()
@@ -16,6 +28,8 @@ WiFiMgmt::WiFiMgmt()
, _lastScanTime(0) , _lastScanTime(0)
, _provisionSuccess(false) , _provisionSuccess(false)
, _ledTaskHandle(nullptr), _ledMode(0) , _ledTaskHandle(nullptr), _ledMode(0)
, _bleServer(nullptr), _bleInfoChar(nullptr), _bleChar(nullptr), _bleAdv(nullptr)
, _blePendingFlag(false)
{ {
memset(&_config, 0, sizeof(_config)); memset(&_config, 0, sizeof(_config));
_instance = this; _instance = this;
@@ -55,6 +69,7 @@ bool WiFiMgmt::begin() {
strncpy(_config.mqtt_pass, MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1); strncpy(_config.mqtt_pass, MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1);
strncpy(_config.device_id, MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1); strncpy(_config.device_id, MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1);
strncpy(_config.env_note, MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1); strncpy(_config.env_note, MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1);
strncpy(_config.env_type, "bedroom", sizeof(_config.env_type)-1);
memset(_config.wifi_ssid, 0, sizeof(_config.wifi_ssid)); memset(_config.wifi_ssid, 0, sizeof(_config.wifi_ssid));
memset(_config.wifi_pass, 0, sizeof(_config.wifi_pass)); memset(_config.wifi_pass, 0, sizeof(_config.wifi_pass));
} }
@@ -88,26 +103,34 @@ bool WiFiMgmt::begin() {
// ============================================================ // ============================================================
bool WiFiMgmt::loop() { bool WiFiMgmt::loop() {
// --- Portal 模式 --- // --- BLE 待处理数据 (从 loop 上下文安全处理, 不占 BTC_TASK 栈) ---
if (_portalActive) { _processBLEPending();
if (_portalStopFlag) {
// --- 配网完成 → WiFi 连接 (Portal 内/外 统一处理) ---
if (_portalStopFlag) {
if (_portalActive) {
_stopPortal(); _stopPortal();
_portalStopFlag = false; }
if (_pendingSSID.length() > 0) { _portalStopFlag = false;
Serial.printf("[WiFi] 尝试连接: %s\n", _pendingSSID.c_str()); if (_pendingSSID.length() > 0) {
_connected = _tryConnect(_pendingSSID.c_str(), _pendingPass.c_str()); Serial.printf("[WiFi] 配网连接: %s\n", _pendingSSID.c_str());
_pendingSSID = ""; _pendingPass = ""; _connected = _tryConnect(_pendingSSID.c_str(), _pendingPass.c_str());
if (_connected) { _pendingSSID = ""; _pendingPass = "";
_setLEDMode(3); if (_connected) {
_provisionSuccess = true; // 标记配网成功 _setLEDMode(3);
} else { _provisionSuccess = true; // 标记配网成功 → main 里触发 MQTT 重连
_setLEDMode(2); } else {
_startPortal(); _setLEDMode(2);
if (!_portalActive) {
_startPortal(); // 连接失败 → 开 AP 重试
} }
} }
return _connected;
} }
return _connected;
}
// --- Portal 模式 ---
if (_portalActive) {
_dns->processNextRequest(); _dns->processNextRequest();
_server->handleClient(); _server->handleClient();
delay(10); delay(10);
@@ -173,7 +196,11 @@ bool WiFiMgmt::_connectWiFi() {
} }
bool WiFiMgmt::_tryConnect(const char* ssid, const char* pass) { bool WiFiMgmt::_tryConnect(const char* ssid, const char* pass) {
// ★ 先从 AP 模式彻底断开再切 STA和 _connectWiFi() 保持一致
WiFi.disconnect(true);
delay(200);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
delay(100);
WiFi.persistent(true); // 确保持久化 WiFi 密码到 NVS (断电不丢) WiFi.persistent(true); // 确保持久化 WiFi 密码到 NVS (断电不丢)
WiFi.begin(ssid, pass); WiFi.begin(ssid, pass);
int retry = 0; int retry = 0;
@@ -198,7 +225,6 @@ void WiFiMgmt::_startPortal() {
if (_portalActive) return; if (_portalActive) return;
WiFi.disconnect(true); WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
delay(200); delay(200);
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0)); WiFi.softAPConfig(IPAddress(192,168,4,1), IPAddress(192,168,4,1), IPAddress(255,255,255,0));
@@ -220,10 +246,13 @@ void WiFiMgmt::_startPortal() {
_portalStartTime = millis(); _portalStartTime = millis();
Serial.println(("[WiFi] 手机连 AP 后浏览器打开 http://192.168.4.1")); Serial.println(("[WiFi] 手机连 AP 后浏览器打开 http://192.168.4.1"));
_startBLE(); // 同时启动 BLE 配网广播
} }
void WiFiMgmt::_stopPortal() { void WiFiMgmt::_stopPortal() {
if (!_portalActive) return; if (!_portalActive) return;
_stopBLE();
if (_server) { _server->stop(); delete _server; _server = nullptr; } if (_server) { _server->stop(); delete _server; _server = nullptr; }
if (_dns) { _dns->stop(); delete _dns; _dns = nullptr; } if (_dns) { _dns->stop(); delete _dns; _dns = nullptr; }
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
@@ -246,6 +275,7 @@ void WiFiMgmt::_handleSave() {
strncpy(_config.wifi_ssid, _pendingSSID.c_str(), sizeof(_config.wifi_ssid)-1); strncpy(_config.wifi_ssid, _pendingSSID.c_str(), sizeof(_config.wifi_ssid)-1);
strncpy(_config.wifi_pass, _pendingPass.c_str(), sizeof(_config.wifi_pass)-1); strncpy(_config.wifi_pass, _pendingPass.c_str(), sizeof(_config.wifi_pass)-1);
if (_server->hasArg("device_id")) strncpy(_config.device_id, _server->arg("device_id").c_str(), sizeof(_config.device_id)-1); if (_server->hasArg("device_id")) strncpy(_config.device_id, _server->arg("device_id").c_str(), sizeof(_config.device_id)-1);
if (_server->hasArg("env_type")) strncpy(_config.env_type, _server->arg("env_type").c_str(), sizeof(_config.env_type)-1);
if (_server->hasArg("env_note")) strncpy(_config.env_note, _server->arg("env_note").c_str(), sizeof(_config.env_note)-1); if (_server->hasArg("env_note")) strncpy(_config.env_note, _server->arg("env_note").c_str(), sizeof(_config.env_note)-1);
if (_server->hasArg("mqtt_server")) strncpy(_config.mqtt_server, _server->arg("mqtt_server").c_str(), sizeof(_config.mqtt_server)-1); if (_server->hasArg("mqtt_server")) strncpy(_config.mqtt_server, _server->arg("mqtt_server").c_str(), sizeof(_config.mqtt_server)-1);
if (_server->hasArg("mqtt_port")) strncpy(_config.mqtt_port, _server->arg("mqtt_port").c_str(), sizeof(_config.mqtt_port)-1); if (_server->hasArg("mqtt_port")) strncpy(_config.mqtt_port, _server->arg("mqtt_port").c_str(), sizeof(_config.mqtt_port)-1);
@@ -342,7 +372,21 @@ String WiFiMgmt::_htmlPage() {
html += "<div class='field'><label>设备编号</label><div class='iwrap'><span class='iicon'>#</span><input type='text' name='device_id' value='"; html += "<div class='field'><label>设备编号</label><div class='iwrap'><span class='iicon'>#</span><input type='text' name='device_id' value='";
html += _config.device_id; html += _config.device_id;
html += "'></div></div>"; html += "'></div></div>";
html += "<div class='field'><label>安装环境</label><div class='iwrap'><span class='iicon'>🏠</span><input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房' value='"; html += "<div class='field'><label>环境类型</label><div class='iwrap'><span class='iicon'>🏷️</span><select name='env_type'>";
const char* envTypes[] = {"bedroom","livingroom","bathroom","hallway","balcony","kitchen"};
const char* envLabels[] = {"🛏️ 卧室","🛋️ 客厅","🛁 浴室/卫生间","🚶 走廊","🌿 阳台","🍳 厨房"};
for (int i = 0; i < 6; i++) {
html += "<option value='";
html += envTypes[i];
html += "'";
if (strcmp(_config.env_type, envTypes[i]) == 0 || (strlen(_config.env_type) == 0 && i == 0))
html += " selected";
html += ">";
html += envLabels[i];
html += "</option>";
}
html += "</select></div></div>";
html += "<div class='field'><label>位置备注</label><div class='iwrap'><span class='iicon'>🏠</span><input type='text' name='env_note' placeholder='如: 主卧 / 客厅 / 老人房' value='";
html += _config.env_note; html += _config.env_note;
html += "'></div></div></div>"; html += "'></div></div></div>";
@@ -363,6 +407,145 @@ String WiFiMgmt::_htmlPage() {
return html; return html;
} }
// ============================================================
// BLE 蓝牙配网
// ============================================================
void WiFiMgmt::_startBLE() {
// 如果 BLE 栈已在运行, 仅重启广播 (避免 deinit→init 失败)
if (_bleServer != nullptr) {
if (_bleAdv) {
// 重设广告数据 (stop 后 start 需要重新配置)
BLEAdvertisementData advData;
advData.setCompleteServices(BLEUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"));
advData.setShortName("HealthMon");
_bleAdv->setAdvertisementData(advData);
_bleAdv->setScanResponse(false);
_bleAdv->start();
}
Serial.println("[BLE] ✅ 广播已重开");
return;
}
BLEDevice::init("HealthMonitor");
_bleServer = BLEDevice::createServer();
BLEService* svc = _bleServer->createService("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// 特征1: 设备信息 (READ) — 小程序连接后读取设备编号等
_bleInfoChar = svc->createCharacteristic(
"4fafc202-1fb5-459e-8fcc-c5c9c331914b",
BLECharacteristic::PROPERTY_READ
);
// 设备信息 JSON (最多 512 字节)
JsonDocument info;
info["device_id"] = _config.device_id;
info["mqtt_server"] = _config.mqtt_server;
info["mqtt_port"] = _config.mqtt_port;
info["fw_version"] = FW_VERSION;
String infoJson;
serializeJson(info, infoJson);
_bleInfoChar->setValue(infoJson.c_str());
// 特征2: 配网写入 (WRITE + NOTIFY) — 小程序发配网数据, 设备回复
_bleChar = svc->createCharacteristic(
"4fafc203-1fb5-459e-8fcc-c5c9c331914b",
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY
);
_bleChar->setCallbacks(new BLEProvisionCB());
_bleChar->addDescriptor(new BLE2902()); // 启用 notify
_bleChar->setValue(""); // 初始为空
svc->start();
_bleAdv = _bleServer->getAdvertising();
BLEAdvertisementData advData;
advData.setCompleteServices(BLEUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"));
advData.setShortName("HealthMon");
_bleAdv->setAdvertisementData(advData);
_bleAdv->setScanResponse(false); // 不需要 scan response, 全在主包
_bleAdv->start();
Serial.printf("[BLE] ✅ 配网广播: HealthMonitor device_id=%s\n", _config.device_id);
}
void WiFiMgmt::_stopBLE() {
if (!_bleServer) return;
// ★ 核心修复: 先断开所有 BLE 客户端连接,
// 否则手机端缓存连接状态, 下次扫描不到设备
size_t connCount = _bleServer->getConnectedCount();
if (connCount > 0) {
Serial.printf("[BLE] 断开 %d 个已连接客户端\n", connCount);
for (size_t i = 0; i < connCount; i++) {
_bleServer->disconnect(i);
}
delay(300); // 等待 BLE 协议栈完成断开流程
}
if (_bleAdv) { _bleAdv->stop(); }
Serial.println("[BLE] 广播已暂停");
}
// BLE 线程安全入队 (仅暂存, 不在 BTC_TASK 回调里处理)
void WiFiMgmt::enqueueBLEJson(const char* json) {
_blePendingJson = json;
_blePendingFlag = true;
}
// 在 loop() 上下文中处理 BLE 配网数据 (安全 — 不在 BTC_TASK 栈中)
void WiFiMgmt::_processBLEPending() {
if (!_blePendingFlag) return;
_blePendingFlag = false;
Serial.printf("[BLE] loop 处理配网: %s\n", _blePendingJson.c_str());
handleBLEProvision(_blePendingJson.c_str());
_blePendingJson = "";
}
void WiFiMgmt::handleBLEProvision(const char* json) {
JsonDocument doc;
DeserializationError err = deserializeJson(doc, json);
if (err) {
Serial.printf("[BLE] JSON 解析失败: %s\n", err.c_str());
if (_bleChar) { _bleChar->setValue("ERR_JSON"); _bleChar->notify(); }
return;
}
// 提取 WiFi 凭证
const char* ssid = doc["s"] | "";
const char* pass = doc["p"] | "";
if (strlen(ssid) == 0) {
Serial.println("[BLE] 未提供 WiFi SSID");
if (_bleChar) { _bleChar->setValue("ERR_NO_SSID"); _bleChar->notify(); }
return;
}
_pendingSSID = ssid;
_pendingPass = pass;
// 保存到 LittleFS (和 Web 配网完全相同的逻辑)
strncpy(_config.wifi_ssid, ssid, sizeof(_config.wifi_ssid) - 1);
strncpy(_config.wifi_pass, pass, sizeof(_config.wifi_pass) - 1);
if (doc.containsKey("device_id")) strncpy(_config.device_id, doc["device_id"] | "", sizeof(_config.device_id) - 1);
if (doc.containsKey("env_note")) strncpy(_config.env_note, doc["env_note"] | "", sizeof(_config.env_note) - 1);
if (doc.containsKey("env_type")) strncpy(_config.env_type, doc["env_type"] | "", sizeof(_config.env_type) - 1);
if (doc.containsKey("mqtt_server")) strncpy(_config.mqtt_server, doc["mqtt_server"] | "", sizeof(_config.mqtt_server) - 1);
if (doc.containsKey("mqtt_port")) strncpy(_config.mqtt_port, doc["mqtt_port"] | "", sizeof(_config.mqtt_port) - 1);
if (doc.containsKey("mqtt_user")) strncpy(_config.mqtt_user, doc["mqtt_user"] | "", sizeof(_config.mqtt_user) - 1);
if (doc.containsKey("mqtt_pass")) strncpy(_config.mqtt_pass, doc["mqtt_pass"] | "", sizeof(_config.mqtt_pass) - 1);
_saveConfig();
_portalStopFlag = true; // 触发延迟关闭 AP+BLE → 连接 WiFi
Serial.printf("[BLE] ✅ 配网完成, SSID=%s\n", ssid);
// 回复 OK
if (_bleChar) { _bleChar->setValue("OK"); _bleChar->notify(); }
delay(100);
}
// ============================================================ // ============================================================
// 重置 // 重置
// ============================================================ // ============================================================
@@ -412,6 +595,7 @@ bool WiFiMgmt::_loadConfig() {
strncpy(_config.mqtt_pass, doc["mqtt_pass"] | MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1); strncpy(_config.mqtt_pass, doc["mqtt_pass"] | MQTT_DEFAULT_PASS, sizeof(_config.mqtt_pass)-1);
strncpy(_config.device_id, doc["device_id"] | MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1); strncpy(_config.device_id, doc["device_id"] | MQTT_DEFAULT_DEVICE, sizeof(_config.device_id)-1);
strncpy(_config.env_note, doc["env_note"] | MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1); strncpy(_config.env_note, doc["env_note"] | MQTT_DEFAULT_ENV, sizeof(_config.env_note)-1);
strncpy(_config.env_type, doc["env_type"] | "bedroom", sizeof(_config.env_type)-1);
strncpy(_config.wifi_ssid, doc["wifi_ssid"] | "", sizeof(_config.wifi_ssid)-1); strncpy(_config.wifi_ssid, doc["wifi_ssid"] | "", sizeof(_config.wifi_ssid)-1);
strncpy(_config.wifi_pass, doc["wifi_pass"] | "", sizeof(_config.wifi_pass)-1); strncpy(_config.wifi_pass, doc["wifi_pass"] | "", sizeof(_config.wifi_pass)-1);
Serial.printf("[WiFi] 配置加载, 已存WiFi: %s\n", _config.wifi_ssid[0] ? _config.wifi_ssid : "(空)"); Serial.printf("[WiFi] 配置加载, 已存WiFi: %s\n", _config.wifi_ssid[0] ? _config.wifi_ssid : "(空)");
@@ -426,6 +610,7 @@ bool WiFiMgmt::_saveConfig() {
doc["mqtt_pass"] = _config.mqtt_pass; doc["mqtt_pass"] = _config.mqtt_pass;
doc["device_id"] = _config.device_id; doc["device_id"] = _config.device_id;
doc["env_note"] = _config.env_note; doc["env_note"] = _config.env_note;
doc["env_type"] = _config.env_type;
doc["wifi_ssid"] = _config.wifi_ssid; doc["wifi_ssid"] = _config.wifi_ssid;
doc["wifi_pass"] = _config.wifi_pass; doc["wifi_pass"] = _config.wifi_pass;
File file = LittleFS.open(CONFIG_FILE_DEVICE, "w"); File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");