Files
lbuque f954c75072 1. Modify VOX/NOX display issues.
2. Modify the time zone.
   for example, set the GMT+8(Beijing time) time zone
   to GMT-8 time zone. esp-sntp uses this time zone to
   obtain Beijing time.

Signed-off-by: lbuque <lbuque@163.com>
2025-04-16 19:05:43 +08:00

553 lines
17 KiB
C++

#include "AppWeb.hpp"
#include <WebServer.h>
#include <cJSON.h>
#include <LittleFS.h>
#include <WString.h>
#include <SensirionI2CScd4x.h>
#include <SensirionI2CSen5x.h>
#include "config.h"
#include "DataBase.hpp"
WebServer server(80);
static TaskHandle_t webTaskHandler;
bool webServerState = false;
extern SensirionI2CScd4x scd4x;
extern SensirionI2CSen5x sen5x;
static void postWiFiConnect();
static void getWiFiStatus();
static void getWiFiList();
static void postEzDataConfig();
static void getStatus();
static void getInfo();
static void getConfig();
static void postConfig();
static void postAPControl();
static void webTask(void *);
static bool getSCD40MeasurementResult(SensirionI2CScd4x& scd4x, uint16_t& co2, float& temperature, float& humidity);
void appWebServer(void) {
if (webServerState == true) {
return;
}
server.serveStatic("/", FILESYSTEM, "/www/index.html");
// onServeStatic("/www");
server.on("/api/v1/wifi_connect", HTTP_POST, postWiFiConnect);
server.on("/api/v1/wifi_status", HTTP_GET, getWiFiStatus);
server.on("/api/v1/wifi_list", HTTP_GET, getWiFiList);
server.on("/api/v1/ezdata_config", HTTP_POST, postEzDataConfig);
server.on("/api/v1/status", HTTP_GET, getStatus);
server.on("/api/v1/info", HTTP_GET, getInfo);
server.on("/api/v1/config", HTTP_GET, getConfig);
server.on("/api/v1/config", HTTP_POST, postConfig);
server.on("/api/v1/ap_control", HTTP_POST, postAPControl);
// called when the url is not defined here
server.onNotFound([]() {
server.send(404, "text/plain", "FileNotFound");
});
server.begin();
webServerState = true;
log_i("HTTP server started");
xTaskCreatePinnedToCore(webTask, "webTask", 8192, NULL, 5, &webTaskHandler, APP_CPU_NUM);
}
void appWebServerClose(void) {
server.close();
webServerState = false;
vTaskDelete(webTaskHandler);
}
static void webTask(void *) {
for (;;) {
server.handleClient();
delay(10);
}
vTaskDelete(NULL);
}
static void postWiFiConnect() {
cJSON *reqObject = NULL;
cJSON *wifiObject = NULL;
bool flag = false;
String content = server.arg("plain");
log_d("POST /api/v1/wifi_connect content: '%s'", content.c_str());
reqObject = cJSON_Parse(content.c_str());
if (reqObject == NULL) {
log_w("JSON parse error");
log_w("payload: '%s'", server.arg("plain").c_str());
return;
}
wifiObject = cJSON_GetObjectItem(reqObject, "wifi");
if (wifiObject) {
cJSON *ssidObject = cJSON_GetObjectItem(wifiObject, "ssid");
if (ssidObject != NULL) {
db.wifi.ssid = ssidObject->valuestring;
flag = true;
}
cJSON *pskObject = cJSON_GetObjectItem(wifiObject, "password");
if (ssidObject != NULL) {
db.wifi.password = pskObject->valuestring;
flag = true;
}
}
db.factoryState = false;
db.saveToFile();
if (flag || WiFi.isConnected() != true) {
WiFi.disconnect();
delay(200);
WiFi.begin(db.wifi.ssid.c_str(), db.wifi.password.c_str());
log_i("WiFi connect ...");
db.isConfigState = true;
}
server.send(200, "application/json", content);
log_d("POST /api/v1/wifi_connect response: '%s'", content.c_str());
return;
}
static void getWiFiStatus() {
cJSON *rspObject = NULL;
char *str = NULL;
rspObject = cJSON_CreateObject();
if (rspObject == NULL) {
return;
}
String mac = WiFi.macAddress();
if (WiFi.status() == WL_CONNECTED) {
cJSON_AddBoolToObject(rspObject, "status", true);
} else {
cJSON_AddBoolToObject(rspObject, "status", false);
}
cJSON_AddStringToObject(rspObject, "mac", mac.c_str());
cJSON_AddStringToObject(rspObject, "ip", WiFi.localIP().toString().c_str());
cJSON_AddBoolToObject(rspObject, "psk_status", db.pskStatus);
str = cJSON_Print(rspObject);
log_d("GET /api/v1/wifi_status response: '%s'", str);
server.send(200, "application/json", str);
free(str);
cJSON_Delete(rspObject);
return;
}
static void getWiFiList() {
cJSON *rspObject = NULL;
cJSON *apListObject = NULL;
int n = 0;
char *str = NULL;
rspObject = cJSON_CreateObject();
if (rspObject == NULL) {
return ;
}
apListObject = cJSON_CreateArray();
if (apListObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "ap_list", apListObject);
log_i("WiFi Scan start");
n = WiFi.scanNetworks();
for (int i = 0; i < n; ++i) {
log_i("%s", WiFi.SSID(i).c_str());
cJSON_AddItemToArray(apListObject, cJSON_CreateString(WiFi.SSID(i).c_str()));
}
log_i("WiFi Scan done");
WiFi.scanDelete();
str = cJSON_Print(rspObject);
server.send(200, "application/json", str);
log_d("GET /api/v1/wifi_list response: '%s'", str);
OUT:
free(str);
cJSON_Delete(rspObject);
return ;
}
static void postEzDataConfig() {
cJSON *reqObject = NULL;
cJSON *ezdataObject = NULL;
cJSON *rspObject = NULL;
String content = server.arg("plain");
log_d("POST /api/v1/ezdata_config content: '%s'", server.arg("plain").c_str());
reqObject = cJSON_Parse(content.c_str());
if (reqObject == NULL) {
log_w("JSON parse error");
log_w("payload: '%s'", server.arg("plain").c_str());
return;
}
ezdataObject = cJSON_GetObjectItem(reqObject, "ezdata2");
if (ezdataObject) {
cJSON *tokenObject = cJSON_GetObjectItem(ezdataObject, "dev_token");
db.ezdata2.devToken = tokenObject->valuestring;
db.factoryState = false;
}
server.send(200, "application/json", server.arg("plain"));
log_d("POST /api/v1/ezdata_config response: '%s'", server.arg("plain").c_str());
db.saveToFile();
cJSON_Delete(reqObject);
cJSON_Delete(rspObject);
return;
}
static void getStatus() {
cJSON *rspObject = NULL;
cJSON *sen55Object = NULL;
cJSON *scd40Object = NULL;
char *str = NULL;
rspObject = cJSON_CreateObject();
if (rspObject == NULL) {
goto OUT1;
}
sen55Object = cJSON_CreateObject();
if (sen55Object == NULL) {
goto OUT;
}
float massConcentrationPm1p0;
float massConcentrationPm2p5;
float massConcentrationPm4p0;
float massConcentrationPm10p0;
float ambientHumidity;
float ambientTemperature;
float vocIndex;
float noxIndex;
sen5x.readMeasuredValues(
massConcentrationPm1p0,
massConcentrationPm2p5,
massConcentrationPm4p0,
massConcentrationPm10p0,
ambientHumidity,
ambientTemperature,
vocIndex,
noxIndex
);
cJSON_AddItemToObject(rspObject, "sen55", sen55Object);
cJSON_AddNumberToObject(sen55Object, "pm1.0", massConcentrationPm1p0);
cJSON_AddNumberToObject(sen55Object, "pm2.5", massConcentrationPm2p5);
cJSON_AddNumberToObject(sen55Object, "pm4.0", massConcentrationPm4p0);
cJSON_AddNumberToObject(sen55Object, "pm10.0", massConcentrationPm10p0);
cJSON_AddNumberToObject(sen55Object, "humidity", ambientHumidity);
cJSON_AddNumberToObject(sen55Object, "temperature", ambientTemperature);
cJSON_AddNumberToObject(sen55Object, "voc", vocIndex);
cJSON_AddNumberToObject(sen55Object, "nox", noxIndex);
scd40Object = cJSON_CreateObject();
if (scd40Object == NULL) {
goto OUT;
}
uint16_t co2;
float humidity;
float temperature;
getSCD40MeasurementResult(scd4x, co2, humidity, temperature);
cJSON_AddItemToObject(rspObject, "scd40", scd40Object);
cJSON_AddNumberToObject(scd40Object, "co2", co2);
cJSON_AddNumberToObject(scd40Object, "humidity", humidity);
cJSON_AddNumberToObject(scd40Object, "temperature", temperature);
struct tm timeinfo;
getLocalTime(&timeinfo, 1000);
cJSON_AddStringToObject(rspObject, "time", asctime((const struct tm *)&timeinfo));
cJSON_AddNumberToObject(rspObject, "interval", db.rtc.sleepInterval);
str = cJSON_Print(rspObject);
server.send(200, "application/json", str);
log_d("GET /api/v1/status response: '%s'", str);
OUT:
free(str);
cJSON_Delete(rspObject);
OUT1:
return;
}
static void getInfo() {
cJSON *rspObject = NULL;
cJSON *sysObject = NULL;
cJSON *archObject = NULL;
cJSON *memObject = NULL;
cJSON *fsObject = NULL;
cJSON *apObject = NULL;
cJSON *staObject = NULL;
char *str = NULL;
rspObject = cJSON_CreateObject();
if (rspObject == NULL) {
goto OUT1;
}
sysObject = cJSON_CreateObject();
if (sysObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "sys", sysObject);
cJSON_AddStringToObject(sysObject, "model", "M5STACK AirQ");
cJSON_AddStringToObject(sysObject, "fw", APP_VERSION);
cJSON_AddStringToObject(sysObject, "sdk", ESP.getSdkVersion());
archObject = cJSON_CreateObject();
if (archObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "arch", archObject);
cJSON_AddStringToObject(archObject, "mfr", "Espressif");
cJSON_AddStringToObject(archObject, "model", ESP.getChipModel());
cJSON_AddNumberToObject(archObject, "revision", ESP.getChipRevision());
if (!strncmp(ESP.getChipModel(), "ESP32-S3", strlen("ESP32-S3"))) {
cJSON_AddStringToObject(archObject, "cpu", "XTensa® dual-core LX7");
} else if (!strncmp(ESP.getChipModel(), "ESP32-S2", strlen("ESP32-S2"))) {
cJSON_AddStringToObject(archObject, "cpu", "XTensa® single-core LX7");
} else if (!strncmp(ESP.getChipModel(), "ESP32-C3", strlen("ESP32-C3"))) {
cJSON_AddStringToObject(archObject, "cpu", "RISC-V");
} else if (!strncmp(ESP.getChipModel(), "ESP32", strlen("ESP32"))) {
cJSON_AddStringToObject(archObject, "cpu", "XTensa® dual-core LX6");
}
cJSON_AddNumberToObject(archObject, "freq", ESP.getCpuFreqMHz());
memObject = cJSON_CreateObject();
if (memObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "mem", memObject);
cJSON_AddNumberToObject(memObject, "total", ESP.getHeapSize());
cJSON_AddNumberToObject(memObject, "free", ESP.getFreeHeap());
fsObject = cJSON_CreateObject();
if (fsObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "fs", fsObject);
cJSON_AddNumberToObject(fsObject, "total", FILESYSTEM.totalBytes());
cJSON_AddNumberToObject(fsObject, "used", FILESYSTEM.usedBytes());
cJSON_AddNumberToObject(fsObject, "free", FILESYSTEM.totalBytes() - FILESYSTEM.usedBytes());
apObject = cJSON_CreateObject();
if (apObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "ap", apObject);
cJSON_AddStringToObject(apObject, "ssid", WiFi.softAPSSID().c_str());
cJSON_AddNumberToObject(apObject, "num", WiFi.softAPgetStationNum());
staObject = cJSON_CreateObject();
if (staObject == NULL) {
goto OUT;
}
cJSON_AddItemToObject(rspObject, "sta", staObject);
cJSON_AddStringToObject(staObject, "ssid", db.wifi.ssid.c_str());
cJSON_AddStringToObject(staObject, "status", WiFi.isConnected() ? "connected" : "disconnect");
cJSON_AddBoolToObject(rspObject, "factory_state", db.factoryState);
str = cJSON_Print(rspObject);
server.send(200, "application/json", str);
log_d("GET /api/v1/info response: '%s'", str);
OUT:
free(str);
cJSON_Delete(rspObject);
OUT1:
return;
}
static void getConfig() {
File file = FILESYSTEM.open("/db.json", "r");
server.streamFile(file, "application/json");
log_d("GET /api/v1/config response: '/db.json'");
file.close();
return;
}
static void postConfig() {
cJSON *reqObject = NULL;
cJSON *configObject = NULL;
cJSON *wifiObject = NULL;
cJSON *rtcObject = NULL;
cJSON *ntpObject = NULL;
cJSON *ezdataObject = NULL;
cJSON *buzzerObject = NULL;
cJSON *nicknameObject = NULL;
bool flag = false;
String content = server.arg("plain");
log_d("POST /api/v1/config content: '%s'", content.c_str());
reqObject = cJSON_Parse(content.c_str());
if (reqObject == NULL) {
log_w("JSON parse error");
log_w("payload: '%s'", server.arg("plain").c_str());
return;
}
configObject = cJSON_GetObjectItem(reqObject, "config");
wifiObject = cJSON_GetObjectItem(configObject, "wifi");
if (wifiObject) {
cJSON *ssidObject = cJSON_GetObjectItem(wifiObject, "ssid");
if (String(ssidObject->valuestring) != db.wifi.ssid && db.wifi.ssid.length() > 0) {
db.wifi.ssid = ssidObject->valuestring;
flag = true;
}
cJSON *pskObject = cJSON_GetObjectItem(wifiObject, "password");
if (db.wifi.ssid.length() > 0 && String(pskObject->valuestring) != db.wifi.password) {
db.wifi.password = pskObject->valuestring;
flag = true;
}
}
rtcObject = cJSON_GetObjectItem(configObject, "rtc");
if (rtcObject) {
cJSON * sleepIntervalObject = cJSON_GetObjectItem(rtcObject, "sleep_interval");
db.rtc.sleepInterval = sleepIntervalObject->valueint;
}
ntpObject = cJSON_GetObjectItem(configObject, "ntp");
if (ntpObject) {
cJSON *ntpServer0Object = cJSON_GetObjectItem(ntpObject, "server_0");
cJSON *ntpServer1Object = cJSON_GetObjectItem(ntpObject, "server_1");
cJSON *tzObject = cJSON_GetObjectItem(ntpObject, "tz");
if (String(ntpServer0Object->valuestring) != db.ntp.ntpServer0
|| String(ntpServer1Object->valuestring) != db.ntp.ntpServer1
|| String(tzObject->valuestring) != db.ntp.tz
) {
db.ntp.ntpServer0 = String(ntpServer0Object->valuestring);
db.ntp.ntpServer1 = String(ntpServer1Object->valuestring);
db.ntp.tz = String(tzObject->valuestring);
String tz;
extern void TZConvert(const String &old, String &out);
TZConvert(db.ntp.tz, tz);
configTzTime(
tz.c_str(),
db.ntp.ntpServer0.c_str(),
db.ntp.ntpServer1.c_str(),
"pool.ntp.org"
);
}
}
ezdataObject = cJSON_GetObjectItem(configObject, "ezdata2");
if (ezdataObject) {
cJSON *tokenObject = cJSON_GetObjectItem(ezdataObject, "dev_token");
db.ezdata2.devToken = tokenObject->valuestring;
db.factoryState = false;
}
buzzerObject = cJSON_GetObjectItem(configObject, "buzzer");
if (buzzerObject) {
if (cJSON_IsTrue(cJSON_GetObjectItem(buzzerObject, "mute"))) {
db.buzzer.onoff = true;
ledcAttachPin(BUZZER_PIN, 0);
} else {
db.buzzer.onoff = false;
ledcDetachPin(BUZZER_PIN);
}
}
nicknameObject = cJSON_GetObjectItem(configObject, "nickname");
if (nicknameObject) {
db.nickname = nicknameObject->valuestring;
}
cJSON_AddBoolToObject(reqObject, "factory_state", db.factoryState);
db.saveToFile();
server.send(200, "application/json", server.arg("plain"));
log_d("POST /api/v1/config response: '%s'", content.c_str());
if (flag || WiFi.isConnected() != true) {
WiFi.disconnect();
delay(200);
WiFi.begin(db.wifi.ssid.c_str(), db.wifi.password.c_str());
}
cJSON_Delete(reqObject);
return;
}
static void postAPControl() {
cJSON *rspObject = NULL;
char *str = NULL;
rspObject = cJSON_CreateObject();
if (rspObject == NULL) {
return;
}
bool status = WiFi.softAPdisconnect();
cJSON_AddBoolToObject(rspObject, "status", status);
str = cJSON_Print(rspObject);
server.send(200, "application/json", str);
log_d("POST /api/v1/ap_control response: '%s'", str);
delay(1000);
free(str);
cJSON_Delete(rspObject);
return;
}
static bool getSCD40MeasurementResult(SensirionI2CScd4x& scd4x, uint16_t& co2, float& temperature, float& humidity)
{
char _errorMessage[256];
bool isDataReady = false;
uint16_t error = scd4x.getDataReadyFlag(isDataReady);
if (error) {
errorToString(error, _errorMessage, 256);
log_w("Error trying to execute getDataReadyFlag(): %s", _errorMessage);
return false;
}
if (!isDataReady) {
return false;
}
error = scd4x.readMeasurement(co2, temperature, humidity);
if (error) {
errorToString(error, _errorMessage, 256);
log_w("Error trying to execute readMeasurement(): %s", _errorMessage);
return false;
} else if (co2 == 0) {
log_w("Invalid sample detected, skipping.");
return false;
} else {
log_i("SCD40 Measurement Result:");
log_i(" Co2: %d ppm",co2 );
log_i(" Temperature: %f °C", temperature);
log_i(" Humidity: %f %RH", humidity);
return true;
}
return false;
}