diff --git a/.gitignore b/.gitignore index a1ec5b9..584f83c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ build .vscode /.idea/ .pio +.cache +logs + diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 7580bdf..9e268cd 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -45,6 +45,11 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include #include +#include "Libraries/airgradient-client/src/agSerial.h" +#include "Libraries/airgradient-client/src/cellularModuleA7672xx.h" +#include "Libraries/airgradient-client/src/airgradientCellularClient.h" +#include "Libraries/airgradient-client/src/airgradientWifiClient.h" + #define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */ #define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */ @@ -63,6 +68,11 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #define I2C_SCL_PIN 6 #define OLED_I2C_ADDR 0x3C +/** Power pin */ +#define GPIO_POWER_MODULE_PIN 5 +#define GPIO_EXPANSION_CARD_POWER 4 +#define GPIO_IIC_RESET 3 + static MqttClient mqttClient(Serial); static TaskHandle_t mqttTask = NULL; static Configuration configuration(Serial); @@ -80,6 +90,10 @@ static OtaHandler otaHandler; static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector); +static AgSerial *agSerial; +static CellularModule *cell; +static AirgradientClient *agClient; + static uint32_t factoryBtnPressTime = 0; static AgFirmwareMode fwMode = FW_MODE_I_9PSL; @@ -87,7 +101,7 @@ static bool ledBarButtonTest = false; static String fwNewVersion; static void boardInit(void); -static void initializeNetwork(void); +static void initializeNetwork(bool useWifi); static void failedHandler(String msg); static void configurationUpdateSchedule(void); static void updateDisplayAndLedBar(void); @@ -153,24 +167,51 @@ void setup() { oledDisplay.setAirGradient(ag); stateMachine.setAirGradient(ag); wifiConnector.setAirGradient(ag); - apiClient.setAirGradient(ag); openMetrics.setAirGradient(ag); localServer.setAirGraident(ag); measurements.setAirGradient(ag); - /** Example set custom API root URL */ - // apiClient.setApiRoot("https://example.custom.api"); - /** Init sensor */ boardInit(); setMeasurementMaxPeriod(); // Comment below line to disable debug measurement readings - measurements.setDebug(true); + measurements.setDebug(false); - /** Connecting wifi */ - bool connectToWifi = false; - if (ag->isOne()) { + // Check if cellular module available + bool cellularFound = false; + agSerial = new AgSerial(Wire); + agSerial->init(GPIO_IIC_RESET); + if (agSerial->open()) { + Serial.println("Cellular module found"); + // Enable cellular load switch + pinMode(GPIO_EXPANSION_CARD_POWER, OUTPUT); + digitalWrite(GPIO_EXPANSION_CARD_POWER, HIGH); + // Initialize cellular module and use cellular as agClient + cell = new CellularModuleA7672XX(agSerial, GPIO_POWER_MODULE_PIN); + agClient = new AirgradientCellularClient(cell); + cellularFound = true; + + //! NOTE: TMP + configuration.setOfflineModeWithoutSave(false); + } else { + Serial.println("Cellular module not available, using wifi"); + delete agSerial; + agSerial = nullptr; + // Use wifi as agClient + agClient = new AirgradientWifiClient; + cellularFound = false; + } + + if (!agClient->begin()) { + // TODO: Need to do retry when agclient already in other task + Serial.println("Failed start Airgradient Client FAILED"); + assert(0); + } + + // bool connectToWifi = false; + bool connectToNetwork = true; + if (ag->isOne()) { // Offline mode only available for indoor monitor /** Show message confirm offline mode, should me perform if LED bar button * test pressed */ if (ledBarButtonTest == false) { @@ -193,21 +234,23 @@ void setup() { break; } } - connectToWifi = !configuration.isOfflineMode(); + connectToNetwork = !configuration.isOfflineMode(); } else { configuration.setOfflineModeWithoutSave(true); } - } else { - connectToWifi = true; } + // else { + // connectToWifi = true; + // } // Initialize networking configuration - if (connectToWifi) { - initializeNetwork(); + if (connectToNetwork) { + bool useWifi = !cellularFound; + initializeNetwork(useWifi); } /** Set offline mode without saving, cause wifi is not configured */ - if (wifiConnector.hasConfigurated() == false) { + if (wifiConnector.hasConfigurated() == false && !cellularFound) { Serial.println("Set offline mode cause wifi is not configurated"); configuration.setOfflineModeWithoutSave(true); } @@ -274,7 +317,7 @@ void loop() { configUpdateHandle(); /** Firmware check for update handle */ - checkForUpdateSchedule.run(); + // checkForUpdateSchedule.run(); //! Temporary until ota cellular } static void co2Update(void) { @@ -464,10 +507,10 @@ static void checkForFirmwareUpdate(void) { return; } - if (!wifiConnector.isConnected()) { - Serial.println("wifi not connected, ignored"); - return; - } + // if (!wifiConnector.isConnected()) { + // Serial.println("wifi not connected, ignored"); + // return; + // } Serial.println("perform"); otaHandler.setHandlerCallback(otaHandlerCallback); @@ -593,7 +636,13 @@ static void sendDataToAg() { "task_led", 2048, NULL, 5, NULL); delay(1500); - if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount())) { + + // Build payload to check connection to airgradient server + JSONVar root; + root["wifi"] = wifiConnector.RSSI(); + root["boot"] = measurements.bootCount(); + std::string payload = JSON.stringify(root).c_str(); + if (agClient->httpPostMeasures(ag->getDeviceId(), payload)) { if (ag->isOne()) { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected); } @@ -828,39 +877,38 @@ static void failedHandler(String msg) { } } -void initializeNetwork() { - if (!wifiConnector.connect()) { - Serial.println("Cannot initiate wifi connection"); - return; - } - - if (!wifiConnector.isConnected()) { - Serial.println("Failed connect to WiFi"); - if (wifiConnector.isConfigurePorttalTimeout()) { - oledDisplay.showRebooting(); - delay(2500); - oledDisplay.setText("", "", ""); - ESP.restart(); +void initializeNetwork(bool useWifi) { + if (useWifi) { + if (!wifiConnector.connect()) { + Serial.println("Cannot initiate wifi connection"); + return; + } + + if (!wifiConnector.isConnected()) { + Serial.println("Failed connect to WiFi"); + if (wifiConnector.isConfigurePorttalTimeout()) { + oledDisplay.showRebooting(); + delay(2500); + oledDisplay.setText("", "", ""); + ESP.restart(); + } + + // Directly return because the rest of the function applied if wifi is connect only + return; + } + + // Initiate local network configuration + mdnsInit(); + localServer.begin(); + // Apply mqtt connection if configured + initMqtt(); + + // Ignore the rest if cloud connection to AirGradient is disabled + if (configuration.isCloudConnectionDisabled()) { + return; } - - // Directly return because the rest of the function applied if wifi is connect only - return; } - // Initiate local network configuration - mdnsInit(); - localServer.begin(); - // Apply mqtt connection if configured - initMqtt(); - - // Ignore the rest if cloud connection to AirGradient is disabled - if (configuration.isCloudConnectionDisabled()) { - return; - } - - // Initialize api client - apiClient.begin(); - // Send data for the first time to AG server at boot sendDataToAg(); @@ -868,15 +916,17 @@ void initializeNetwork() { #ifdef ESP8266 // ota not supported #else - checkForFirmwareUpdate(); - checkForUpdateSchedule.update(); + // checkForFirmwareUpdate(); //! Temporary until ota cellular + // checkForUpdateSchedule.update(); #endif - apiClient.fetchServerConfiguration(); + std::string config = agClient->httpFetchConfig(ag->getDeviceId()); configSchedule.update(); - if (apiClient.isFetchConfigurationFailed()) { + // Check if fetch configuration failed or fetch succes but parsing failed + if (agClient->isLastFetchConfigSucceed() == false || + configuration.parse(config.c_str(), false) == false) { if (ag->isOne()) { - if (apiClient.isNotAvailableOnDashboard()) { + if (agClient->isRegisteredOnAgServer() == false) { stateMachine.displaySetAddToDashBoard(); stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed); } else { @@ -885,7 +935,8 @@ void initializeNetwork() { } stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); - } else { + } + else { ledBarEnabledUpdate(); } } @@ -895,16 +946,17 @@ static void configurationUpdateSchedule(void) { configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) { Serial.println("Ignore fetch server configuration. Either mode is offline or cloud connection " "disabled or configurationControl set to local"); - apiClient.resetFetchConfigurationStatus(); + agClient->resetFetchConfigurationStatus(); return; } - if (wifiConnector.isConnected() == false) { - Serial.println(" WiFi not connected, skipping fetch configuration from AG server"); - return; - } + // if (wifiConnector.isConnected() == false) { + // Serial.println(" WiFi not connected, skipping fetch configuration from AG server"); + // return; + // } - if (apiClient.fetchServerConfiguration()) { + std::string config = agClient->httpFetchConfig(ag->getDeviceId()); + if (agClient->isLastFetchConfigSucceed() && configuration.parse(config.c_str(), false)) { configUpdateHandle(); } } @@ -1001,11 +1053,11 @@ static void updateDisplayAndLedBar(void) { return; } - if (wifiConnector.isConnected() == false) { - stateMachine.displayHandle(AgStateMachineWiFiLost); - stateMachine.handleLeds(AgStateMachineWiFiLost); - return; - } + // if (wifiConnector.isConnected() == false) { + // stateMachine.displayHandle(AgStateMachineWiFiLost); + // stateMachine.handleLeds(AgStateMachineWiFiLost); + // return; + // } if (configuration.isCloudConnectionDisabled()) { // Ignore API related check since cloud is disabled @@ -1015,14 +1067,15 @@ static void updateDisplayAndLedBar(void) { } AgStateMachineState state = AgStateMachineNormal; - if (apiClient.isFetchConfigurationFailed()) { + if (agClient->isLastFetchConfigSucceed() == false) { state = AgStateMachineSensorConfigFailed; - if (apiClient.isNotAvailableOnDashboard()) { + if (agClient->isRegisteredOnAgServer() == false) { stateMachine.displaySetAddToDashBoard(); } else { stateMachine.displayClearAddToDashBoard(); } - } else if (apiClient.isPostToServerFailed() && configuration.isPostDataToAirGradient()) { + } else if (agClient->isLastPostMeasureSucceed() == false && + configuration.isPostDataToAirGradient()) { state = AgStateMachineServerLost; } @@ -1190,13 +1243,14 @@ static void sendDataToServer(void) { return; } - if (wifiConnector.isConnected() == false) { - Serial.println("WiFi not connected, skipping data transmission to AG server"); - return; - } + // if (wifiConnector.isConnected() == false) { + // Serial.println("WiFi not connected, skipping data transmission to AG server"); + // return; + // } - String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI()); - if (apiClient.postToServer(syncData)) { + String measures = measurements.toString(false, fwMode, wifiConnector.RSSI()); + std::string payload = std::string(measures.c_str()); + if (agClient->httpPostMeasures(ag->getDeviceId(), payload)) { Serial.println(); Serial.println("Online mode and isPostToAirGradient = true"); Serial.println(); diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index a1b70d9..c8bec43 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -85,3 +85,7 @@ String AirGradient::deviceId(void) { mac.toLowerCase(); return mac; } + +std::string AirGradient::getDeviceId(void) { + return std::string(deviceId().c_str()); +} diff --git a/src/AirGradient.h b/src/AirGradient.h index 386710b..06139b5 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -212,6 +212,8 @@ public: */ String deviceId(void); + std::string getDeviceId(void); + private: BoardType boardType; };