First test

This commit is contained in:
samuelbles07
2025-10-02 18:17:18 +07:00
parent 75be7d9fc5
commit d075d12011
2 changed files with 136 additions and 93 deletions

View File

@@ -87,6 +87,11 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define I2C_SCL_PIN 6 #define I2C_SCL_PIN 6
#define OLED_I2C_ADDR 0x3C #define OLED_I2C_ADDR 0x3C
#include <Arduino.h>
#include <NimBLEDevice.h>
static NimBLEServer *pServer;
/** Power pin */ /** Power pin */
#define GPIO_POWER_MODULE_PIN 5 #define GPIO_POWER_MODULE_PIN 5
#define GPIO_EXPANSION_CARD_POWER 4 #define GPIO_EXPANSION_CARD_POWER 4
@@ -100,21 +105,15 @@ static Configuration configuration(Serial);
static Measurements measurements(configuration); static Measurements measurements(configuration);
static AirGradient *ag; static AirGradient *ag;
static OledDisplay oledDisplay(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements, static StateMachine stateMachine(oledDisplay, Serial, measurements, configuration);
configuration); static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration);
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
configuration);
static OpenMetrics openMetrics(measurements, configuration, wifiConnector); static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
static LocalServer localServer(Serial, openMetrics, measurements, configuration, static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector);
wifiConnector);
static AgSerial *agSerial; static AgSerial *agSerial;
static CellularModule *cellularCard; static CellularModule *cellularCard;
static AirgradientClient *agClient; static AirgradientClient *agClient;
enum NetworkOption { enum NetworkOption { UseWifi, UseCellular };
UseWifi,
UseCellular
};
NetworkOption networkOption; NetworkOption networkOption;
TaskHandle_t handleNetworkTask = NULL; TaskHandle_t handleNetworkTask = NULL;
static bool firmwareUpdateInProgress = false; static bool firmwareUpdateInProgress = false;
@@ -162,8 +161,7 @@ static void networkSignalCheck();
static void networkingTask(void *args); static void networkingTask(void *args);
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar); AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL, AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL, configurationUpdateSchedule);
configurationUpdateSchedule);
AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer); AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer);
AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle); AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
@@ -175,6 +173,8 @@ AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmware
AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck); AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck);
AgSchedule printMeasurementsSchedule(6000, printMeasurements); AgSchedule printMeasurementsSchedule(6000, printMeasurements);
static void setupBLE();
void setup() { void setup() {
/** Serial for print debug message */ /** Serial for print debug message */
Serial.begin(115200); Serial.begin(115200);
@@ -221,21 +221,24 @@ void setup() {
boardInit(); boardInit();
setMeasurementMaxPeriod(); setMeasurementMaxPeriod();
setupBLE();
oledDisplay.setText("BT", "ON", "");
Serial.println("Bluetooth server ready");
while(1) {delay(100);}
bool connectToNetwork = true; bool connectToNetwork = true;
if (ag->isOne()) { // Offline mode only available for indoor monitor if (ag->isOne()) { // Offline mode only available for indoor monitor
/** Show message confirm offline mode, should me perform if LED bar button /** Show message confirm offline mode, should me perform if LED bar button
* test pressed */ * test pressed */
if (ledBarButtonTest == false) { if (ledBarButtonTest == false) {
oledDisplay.setText( oledDisplay.setText("Press now for",
"Press now for",
configuration.isOfflineMode() ? "online mode" : "offline mode", ""); configuration.isOfflineMode() ? "online mode" : "offline mode", "");
uint32_t startTime = millis(); uint32_t startTime = millis();
while (true) { while (true) {
if (ag->button.getState() == ag->button.BUTTON_PRESSED) { if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
configuration.setOfflineMode(!configuration.isOfflineMode()); configuration.setOfflineMode(!configuration.isOfflineMode());
oledDisplay.setText( oledDisplay.setText("Offline Mode",
"Offline Mode",
configuration.isOfflineMode() ? " = True" : " = False", ""); configuration.isOfflineMode() ? " = True" : " = False", "");
delay(1000); delay(1000);
break; break;
@@ -274,7 +277,6 @@ void setup() {
delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} }
if (networkOption == UseCellular) { if (networkOption == UseCellular) {
// If using cellular re-set scheduler interval // If using cellular re-set scheduler interval
configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL); configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL);
@@ -302,11 +304,9 @@ void setup() {
// Log monitor mode for debugging purpose // Log monitor mode for debugging purpose
if (configuration.isOfflineMode()) { if (configuration.isOfflineMode()) {
Serial.println("Running monitor in offline mode"); Serial.println("Running monitor in offline mode");
} } else if (configuration.isCloudConnectionDisabled()) {
else if (configuration.isCloudConnectionDisabled()) {
Serial.println("Running monitor without connection to AirGradient server"); Serial.println("Running monitor without connection to AirGradient server");
} }
} }
void loop() { void loop() {
@@ -353,7 +353,7 @@ void loop() {
static bool pmsConnected = false; static bool pmsConnected = false;
if (pmsConnected != ag->pms5003.connected()) { if (pmsConnected != ag->pms5003.connected()) {
pmsConnected = ag->pms5003.connected(); pmsConnected = ag->pms5003.connected();
Serial.printf("PMS sensor %s \n", pmsConnected?"connected":"removed"); Serial.printf("PMS sensor %s \n", pmsConnected ? "connected" : "removed");
} }
} }
} else { } else {
@@ -392,9 +392,7 @@ static void co2Update(void) {
} }
} }
void printMeasurements() { void printMeasurements() { measurements.printCurrentAverage(); }
measurements.printCurrentAverage();
}
static void mdnsInit(void) { static void mdnsInit(void) {
if (!MDNS.begin(localServer.getHostname().c_str())) { if (!MDNS.begin(localServer.getHostname().c_str())) {
@@ -403,8 +401,7 @@ static void mdnsInit(void) {
} }
MDNS.addService("_airgradient", "_tcp", 80); MDNS.addService("_airgradient", "_tcp", 80);
MDNS.addServiceTxt("_airgradient", "_tcp", "model", MDNS.addServiceTxt("_airgradient", "_tcp", "model", AgFirmwareModeName(fwMode));
AgFirmwareModeName(fwMode));
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId()); MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId());
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion()); MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion());
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient"); MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
@@ -428,8 +425,7 @@ static void createMqttTask(void) {
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI()); String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
String topic = "airgradient/readings/" + ag->deviceId(); String topic = "airgradient/readings/" + ag->deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
payload.length())) {
Serial.println("MQTT sync success"); Serial.println("MQTT sync success");
} else { } else {
Serial.println("MQTT sync failure"); Serial.println("MQTT sync failure");
@@ -447,8 +443,7 @@ static void createMqttTask(void) {
static void initMqtt(void) { static void initMqtt(void) {
String mqttUri = configuration.getMqttBrokerUri(); String mqttUri = configuration.getMqttBrokerUri();
if (mqttUri.isEmpty()) { if (mqttUri.isEmpty()) {
Serial.println( Serial.println("MQTT is not configured, skipping initialization of MQTT client");
"MQTT is not configured, skipping initialization of MQTT client");
return; return;
} }
@@ -509,7 +504,7 @@ static void factoryConfigReset(void) {
Serial.println("Factory reset successful"); Serial.println("Factory reset successful");
} }
delay(3000); delay(3000);
oledDisplay.setText("","",""); oledDisplay.setText("", "", "");
ESP.restart(); ESP.restart();
} }
} }
@@ -714,8 +709,7 @@ static void sendDataToAg() {
for (;;) { for (;;) {
// ledSmHandler(); // ledSmHandler();
stateMachine.handleLeds(); stateMachine.handleLeds();
if (stateMachine.getLedState() != if (stateMachine.getLedState() != AgStateMachineWiFiOkServerConnecting) {
AgStateMachineWiFiOkServerConnecting) {
break; break;
} }
delay(LED_BAR_ANIMATION_PERIOD); delay(LED_BAR_ANIMATION_PERIOD);
@@ -761,8 +755,7 @@ static void oneIndoorInit(void) {
/** Show boot display */ /** Show boot display */
Serial.println("Firmware Version: " + ag->getVersion()); Serial.println("Firmware Version: " + ag->getVersion());
oledDisplay.setText("AirGradient ONE", oledDisplay.setText("AirGradient ONE", "FW Version: ", ag->getVersion().c_str());
"FW Version: ", ag->getVersion().c_str());
delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
ag->ledBar.begin(); ag->ledBar.begin();
@@ -793,9 +786,9 @@ static void oneIndoorInit(void) {
WiFi.begin("airgradient", "cleanair"); WiFi.begin("airgradient", "cleanair");
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'"); oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
delay(2500); delay(2500);
oledDisplay.setText("Rebooting...", "",""); oledDisplay.setText("Rebooting...", "", "");
delay(2500); delay(2500);
oledDisplay.setText("","",""); oledDisplay.setText("", "", "");
ESP.restart(); ESP.restart();
} }
} }
@@ -921,8 +914,7 @@ static void openAirInit(void) {
} }
if (fwMode == FW_MODE_O_1PP) { if (fwMode == FW_MODE_O_1PP) {
int count = (configuration.hasSensorPMS1 ? 1 : 0) + int count = (configuration.hasSensorPMS1 ? 1 : 0) + (configuration.hasSensorPMS2 ? 1 : 0);
(configuration.hasSensorPMS2 ? 1 : 0);
if (count == 1) { if (count == 1) {
fwMode = FW_MODE_O_1P; fwMode = FW_MODE_O_1P;
} }
@@ -1070,15 +1062,13 @@ void initializeNetwork() {
} }
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed); stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} } else {
else {
ledBarEnabledUpdate(); ledBarEnabledUpdate();
} }
} }
static void configurationUpdateSchedule(void) { static void configurationUpdateSchedule(void) {
if (configuration.getConfigurationControl() == if (configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration, configurationControl set to local"); Serial.println("Ignore fetch server configuration, configurationControl set to local");
agClient->resetFetchConfigurationStatus(); agClient->resetFetchConfigurationStatus();
return; return;
@@ -1112,8 +1102,7 @@ static void configUpdateHandle() {
} }
if (configuration.hasSensorSGP) { if (configuration.hasSensorSGP) {
if (configuration.noxLearnOffsetChanged() || if (configuration.noxLearnOffsetChanged() || configuration.tvocLearnOffsetChanged()) {
configuration.tvocLearnOffsetChanged()) {
ag->sgp41.end(); ag->sgp41.end();
int oldTvocOffset = ag->sgp41.getTvocLearningOffset(); int oldTvocOffset = ag->sgp41.getTvocLearningOffset();
@@ -1124,14 +1113,12 @@ static void configUpdateHandle() {
resultStr = "failure"; resultStr = "failure";
} }
if (oldTvocOffset != configuration.getTvocLearningOffset()) { if (oldTvocOffset != configuration.getTvocLearningOffset()) {
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", oldTvocOffset,
oldTvocOffset, configuration.getTvocLearningOffset(), configuration.getTvocLearningOffset(), resultStr);
resultStr);
} }
if (oldNoxOffset != configuration.getNoxLearningOffset()) { if (oldNoxOffset != configuration.getNoxLearningOffset()) {
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", oldNoxOffset,
oldNoxOffset, configuration.getNoxLearningOffset(), configuration.getNoxLearningOffset(), resultStr);
resultStr);
} }
} }
} }
@@ -1153,7 +1140,7 @@ static void configUpdateHandle() {
if (configuration.getLedBarBrightness() == 0) { if (configuration.getLedBarBrightness() == 0) {
ag->ledBar.setEnable(false); ag->ledBar.setEnable(false);
} else { } else {
if(configuration.getLedBarMode() == LedBarMode::LedBarModeOff) { if (configuration.getLedBarMode() == LedBarMode::LedBarModeOff) {
ag->ledBar.setEnable(false); ag->ledBar.setEnable(false);
} else { } else {
ag->ledBar.setEnable(true); ag->ledBar.setEnable(true);
@@ -1191,8 +1178,7 @@ static void updateDisplayAndLedBar(void) {
stateMachine.handleLeds(AgStateMachineWiFiLost); stateMachine.handleLeds(AgStateMachineWiFiLost);
return; return;
} }
} } else if (networkOption == UseCellular) {
else if (networkOption == UseCellular) {
if (agClient->isClientReady() == false) { if (agClient->isClientReady() == false) {
// Same action as wifi // Same action as wifi
stateMachine.displayHandle(AgStateMachineWiFiLost); stateMachine.displayHandle(AgStateMachineWiFiLost);
@@ -1390,8 +1376,8 @@ void postUsingWifi() {
} }
/** /**
* forcePost to force post without checking transmit cycle * forcePost to force post without checking transmit cycle
*/ */
void postUsingCellular(bool forcePost) { void postUsingCellular(bool forcePost) {
// Aquire queue mutex to get queue size // Aquire queue mutex to get queue size
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY); xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
@@ -1531,7 +1517,6 @@ int calculateMaxPeriod(int updateInterval) {
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval; return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
} }
void networkSignalCheck() { void networkSignalCheck() {
if (networkOption == UseWifi) { if (networkOption == UseWifi) {
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI()); Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
@@ -1557,12 +1542,11 @@ void networkSignalCheck() {
} }
/** /**
* If in 2 hours cellular client still not ready, then restart system * If in 2 hours cellular client still not ready, then restart system
*/ */
void restartIfCeClientIssueOverTwoHours() { void restartIfCeClientIssueOverTwoHours() {
if (agCeClientProblemDetectedTime > 0 && if (agCeClientProblemDetectedTime > 0 &&
(MINUTES() - agCeClientProblemDetectedTime) > (MINUTES() - agCeClientProblemDetectedTime) > TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY) {
TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY) {
// Give up wait // Give up wait
Serial.println("Rebooting because CE client issues for 2 hours detected"); Serial.println("Rebooting because CE client issues for 2 hours detected");
int i = 3; int i = 3;
@@ -1613,8 +1597,7 @@ void networkingTask(void *args) {
delay(1000); delay(1000);
continue; continue;
} }
} } else if (networkOption == UseCellular) {
else if (networkOption == UseCellular) {
if (agClient->isClientReady() == false) { if (agClient->isClientReady() == false) {
// Start time if value still default // Start time if value still default
if (agCeClientProblemDetectedTime == 0) { if (agCeClientProblemDetectedTime == 0) {
@@ -1695,3 +1678,62 @@ void newMeasurementCycle() {
} }
} }
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override {
Serial.printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
}
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override {
Serial.printf("Client disconnected - start advertising\n");
NimBLEDevice::startAdvertising();
}
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override {
Serial.println("\n========== PAIRING COMPLETE ==========");
Serial.printf("Peer Address: %s\n", connInfo.getAddress().toString().c_str());
Serial.printf("Encrypted: %s\n", connInfo.isEncrypted() ? "YES" : "NO");
Serial.printf("Authenticated: %s\n", connInfo.isAuthenticated() ? "YES" : "NO");
Serial.printf("Key Size: %d bits\n", connInfo.getSecKeySize() * 8);
Serial.println("======================================\n");
}
};
/** Handler class for characteristic actions */
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override {
Serial.printf("%s : onRead(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override {
Serial.printf("%s : onWrite(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
};
void setupBLE() {
NimBLEDevice::init("AirGradient");
NimBLEDevice::setPower(3); /** +3db */
/** bonding, MITM, don't need BLE secure connections as we are using passkey pairing */
NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT);
NimBLEServer *pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
NimBLEService *pService = pServer->createService("acbcfea8-e541-4c40-9bfd-17820f16c95c");
NimBLECharacteristic *pSecureCharacteristic =
pService->createCharacteristic("703fa252-3d2a-4da9-a05c-83b0d9cacb8e",
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC |
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC);
pSecureCharacteristic->setCallbacks(new CharacteristicCallbacks());
pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(pService->getUUID());
pAdvertising->start();
}

View File

@@ -26,6 +26,7 @@ lib_deps =
WiFiClientSecure WiFiClientSecure
Update Update
DNSServer DNSServer
h2zero/NimBLE-Arduino@^2.1.0
[env:esp8266] [env:esp8266]
platform = espressif8266 platform = espressif8266