diff --git a/.gitignore b/.gitignore index 66fc7a3..1c7318d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .pio/ +.vscode/c_cpp_properties.json +.gitignore +**/DS.store \ No newline at end of file diff --git a/CO2_sensor.cpp b/CO2_sensor.cpp index bb21c8b..2427666 100644 --- a/CO2_sensor.cpp +++ b/CO2_sensor.cpp @@ -42,22 +42,32 @@ void initCO2sensor(void) void handleCO2sensor(void) { uint32_t currentmillis = millis(); - if ((currentmillis - CO2_lastUpdate > (g_pms_report_period * 1000)) || (!CO2_lastUpdate) && (scd30.dataReady())) + if ((currentmillis - CO2_lastUpdate > (g_pms_report_period * 1000)) || (!CO2_lastUpdate) ) { + if (!scd30.read()) { Serial.println("SCD30: read error!"); return; } - SCD30_temperature.set(uint32_t(scd30.temperature)); - SCD30_Humidity.set(uint32_t(scd30.relative_humidity)); - SCD30_CO2.set(uint32_t(scd30.CO2)); + if(!scd30.dataReady()) + { + return; + } + else + { + SCD30_temperature.set(uint32_t(scd30.temperature)); + SCD30_Humidity.set(uint32_t(scd30.relative_humidity)); + SCD30_CO2.set(uint32_t(scd30.CO2)); - SCD30_temperature.publish(); - SCD30_Humidity.publish(); - SCD30_CO2.publish(); + SCD30_temperature.publish(); + SCD30_Humidity.publish(); + SCD30_CO2.publish(); + CO2_lastUpdate = currentmillis; + } + - CO2_lastUpdate = currentmillis; + } } \ No newline at end of file diff --git a/VOC_sensor.cpp b/VOC_sensor.cpp index dfc3b5f..a83223f 100644 --- a/VOC_sensor.cpp +++ b/VOC_sensor.cpp @@ -4,14 +4,15 @@ Adafruit_SGP30 sgp; uint8_t VOC_samples = 3600 / g_pms_report_period; #define VOC_MIN 0 #define VOC_MAX 10000 +#define VOC_RAW_MAX 30000 #define VOC_INTERVAL 120000 //ms uint32_t lastVOCtime = 0; //sensors AQSSensor SGP30_tvoc("TVOC", SGP30_TVOC, "ppb", device_name, VOC_samples, VOC_MIN, VOC_MAX); AQSSensor SGP30_eco2("eCO2", SGP30_eCO2, "ppm", device_name, VOC_samples, VOC_MIN, VOC_MAX); -AQSSensor SGP30_rawh2("Raw_H2", SGP30_rawH2, "#", device_name, VOC_samples, VOC_MIN, VOC_MAX); -AQSSensor SGP30_rawethanol("Raw_Ethanol", SGP30_rawEthanol, "#", device_name, VOC_samples, VOC_MIN, VOC_MAX); +AQSSensor SGP30_rawh2("Raw_H2", SGP30_rawH2, "#", device_name, VOC_samples, VOC_MIN, VOC_RAW_MAX); +AQSSensor SGP30_rawethanol("Raw_Ethanol", SGP30_rawEthanol, "#", device_name, VOC_samples, VOC_MIN, VOC_RAW_MAX); void initVOCsensor(void) { @@ -31,7 +32,7 @@ void initVOCsensor(void) sgp.IAQmeasure(); sgp.IAQmeasureRaw(); - + Serial.println("VOCSensor: Init OK"); } } @@ -50,7 +51,7 @@ void handleVOCsensor(void) SGP30_eco2.set(sgp.eCO2); SGP30_tvoc.publish(); -SGP30_eco2.publish(); + SGP30_eco2.publish(); if (!sgp.IAQmeasureRaw()) { diff --git a/button.h b/button.h index 9c684f8..de32615 100644 --- a/button.h +++ b/button.h @@ -2,6 +2,7 @@ #include "Arduino.h" #include "config.h" +#include "lcd.h" void initButtons( void ); void handleButtons( void ); \ No newline at end of file diff --git a/buttons.cpp b/buttons.cpp index fc9a552..1b6dd9a 100644 --- a/buttons.cpp +++ b/buttons.cpp @@ -27,6 +27,7 @@ void handleButtons(void) return; } Serial.println("Button pressed"); + backlightRefresh(); // Increment display state g_last_debounce_time = millis(); } diff --git a/config.h b/config.h index 60de90f..e555cf0 100644 --- a/config.h +++ b/config.h @@ -28,6 +28,11 @@ /* ----------------- Hardware-specific Config ---------------------- */ /* Mode button connection (momentary between this pin and GND) */ #define MODE_BUTTON_PIN 0 +#define TFT_BL 4 // LED back-light control pin +#define TFT_BACKLIGHT_ON HIGH // Level to turn ON back-light (HIGH or LOW) +#define TFT_BL_PWMCHANNEL 1 +#define TFT_BL_FREQ 5000 +#define TFT_BL_BITS 8 /* I2C */ #define I2C_SDA_PIN 21 diff --git a/esp_ota.cpp b/esp_ota.cpp new file mode 100644 index 0000000..c9fbbd2 --- /dev/null +++ b/esp_ota.cpp @@ -0,0 +1,46 @@ +#include "esp_ota.h" + +void initEspOta(void) +{ + Serial.println("ESPOTA: init"); + ArduinoOTA + .onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("ESPOTA: Start updating " + type); + ProgressbarVisible(true); + ledcWrite(TFT_BL_PWMCHANNEL, 64); + }) + .onEnd([]() { + Serial.println("\nESPOTA: End"); + }) + .onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("ESPOTA: Progress: %u%%\r", (progress / (total / 100))); + setOTAProgress((progress / (total / 100))); + }) + .onError([](ota_error_t error) { + Serial.printf("ESPOTA: Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("ESPOTA: Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("ESPOTA: Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("ESPOTA: Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("ESPOTA: Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("ESPOTA: End Failed"); + }); + + ArduinoOTA.begin(); + + Serial.print("ESPOTA: IP address: "); + Serial.println(WiFi.localIP()); + Serial.println("ESPOTA: init OK"); + +} + +void handleEspOta(void) +{ + ArduinoOTA.handle(); +} \ No newline at end of file diff --git a/esp_ota.h b/esp_ota.h new file mode 100644 index 0000000..0266609 --- /dev/null +++ b/esp_ota.h @@ -0,0 +1,8 @@ +#pragma once + +#include "Arduino.h" +#include +#include "lcd.h" + +void initEspOta(void); +void handleEspOta(void); \ No newline at end of file diff --git a/lcd.cpp b/lcd.cpp index 0412d9e..de3a054 100644 --- a/lcd.cpp +++ b/lcd.cpp @@ -38,6 +38,7 @@ TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle TFT_eSprite spr = TFT_eSprite(&tft); // Sprite for meter reading TFT_eSprite nameSpr = TFT_eSprite(&tft); TFT_eSprite unitSpr = TFT_eSprite(&tft); +TFT_eSprite ProgressBar = TFT_eSprite(&tft); // Jpeg image array attached to this sketch #include "dial.h" @@ -49,6 +50,7 @@ uint16_t *tft_buffer; bool buffer_loaded = false; uint16_t spr_width = 0; uint16_t name_spr_width = 0; +bool progessbarActive = false; void createNeedle(void); void plotNeedle(int16_t angle, uint16_t ms_delay); @@ -69,29 +71,58 @@ bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) return 1; } -// ======================================================================================= -// Setup -// ======================================================================================= -void initLCD() +void setBacklight(uint8_t value) { - Serial.print("InitLCD:"); - // The byte order can be swapped (set true for TFT_eSPI) - TJpgDec.setSwapBytes(true); + ledcWrite(TFT_BL_PWMCHANNEL, value); +} - // The jpeg decoder must be given the exact name of the rendering function above - TJpgDec.setCallback(tft_output); +void createProgressBar(void) +{ + ProgressBar.createSprite(220, 30); + ProgressBar.fillSprite(TFT_BLACK); + ProgressBar.drawRoundRect(0, 2, 220, 22, 5, TFT_BLUE); + ProgressBar.pushSprite(10, 270); +} - tft.begin(); - tft.setRotation(0); - tft.fillScreen(TFT_BLACK); +void ProgressbarVisible(bool visible) +{ + progessbarActive = visible; + if (!visible) + { + ProgressBar.fillSprite(TFT_BLACK); + ProgressBar.pushSprite(10, 270); + } +} - pinMode(TFT_BL, OUTPUT); +void setOTAProgress(uint8_t value) +{ + uint16_t progress = map(value, 0, 100, 0, 212); + ProgressBar.drawRoundRect(0, 2, 220, 22, 5, TFT_BLUE); + ProgressBar.fillRoundRect(4, 5, progress, 15, 3, TFT_BLUE); + if (progessbarActive) + { + ProgressBar.pushSprite(10, 270); + } +} - // Draw the dial - TJpgDec.drawJpg(0, 0, dial, sizeof(dial)); - tft.drawCircle(DIAL_CENTRE_X, DIAL_CENTRE_Y, NEEDLE_RADIUS - NEEDLE_LENGTH, TFT_DARKGREY); +void createNameSprite(void) +{ + nameSpr.setTextFont(2); + name_spr_width = nameSpr.textWidth("---Sensor Name---"); + nameSpr.createSprite(name_spr_width, nameSpr.fontHeight() * 2 + 2); + uint16_t bg_color = tft.readPixel(120, 120); // Get colour from dial centre - // Load the font and create the Sprite for reporting the value + nameSpr.fillSprite(bg_color); + nameSpr.setTextColor(TFT_WHITE, bg_color); + nameSpr.setTextDatum(MC_DATUM); + nameSpr.setTextPadding(name_spr_width); + nameSpr.drawString("Sensor Name", name_spr_width / 2, nameSpr.fontHeight() / 2); + nameSpr.drawString("Unit", name_spr_width / 2, nameSpr.fontHeight() / 2 * 3 + 2); + nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2); +} + +void createValueSprinte(void) +{ spr.loadFont(AA_FONT_LARGE); spr_width = spr.textWidth("277"); spr.createSprite(spr_width, spr.fontHeight()); @@ -102,25 +133,45 @@ void initLCD() spr.setTextPadding(spr_width); spr.drawNumber(0, spr_width / 2, spr.fontHeight() / 2); spr.pushSprite(DIAL_CENTRE_X - spr_width / 2, DIAL_CENTRE_Y - spr.fontHeight() / 2); +} - // Plot the label text - nameSpr.setTextFont(2); - name_spr_width = nameSpr.textWidth("---Sensor Name---"); - nameSpr.createSprite(name_spr_width, nameSpr.fontHeight() * 2 + 2); - nameSpr.fillSprite(bg_color); - nameSpr.setTextColor(TFT_WHITE, bg_color); - nameSpr.setTextDatum(MC_DATUM); - nameSpr.setTextPadding(name_spr_width); - nameSpr.drawString("Sensor Name", name_spr_width / 2, nameSpr.fontHeight() / 2); - nameSpr.drawString("Unit", name_spr_width / 2, nameSpr.fontHeight() / 2 * 3 + 2); nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2); - nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2); +void createDail(void) +{ + TJpgDec.setSwapBytes(true); + + // The jpeg decoder must be given the exact name of the rendering function above + TJpgDec.setCallback(tft_output); + // Draw the dial + TJpgDec.drawJpg(0, 0, dial, sizeof(dial)); + tft.drawCircle(DIAL_CENTRE_X, DIAL_CENTRE_Y, NEEDLE_RADIUS - NEEDLE_LENGTH, TFT_DARKGREY); // Define where the needle pivot point is on the TFT before // creating the needle so boundary calculation is correct tft.setPivot(DIAL_CENTRE_X, DIAL_CENTRE_Y); +} - // Create the needle Sprite +// ======================================================================================= +// Setup +// ======================================================================================= +void initLCD() +{ + Serial.print("InitLCD:"); + // The byte order can be swapped (set true for TFT_eSPI) + + tft.begin(); + tft.setRotation(0); + tft.fillScreen(TFT_BLACK); + + // Create the Sprites + createProgressBar(); + createDail(); + createValueSprinte(); createNeedle(); + createNameSprite(); + + ledcSetup(TFT_BL_PWMCHANNEL, TFT_BL_FREQ, TFT_BL_BITS); + ledcAttachPin(TFT_BL, TFT_BL_PWMCHANNEL); + ledcWrite(TFT_BL_PWMCHANNEL, 64); // Reset needle position to 0 plotNeedle(0, 0, 0); @@ -150,6 +201,47 @@ uint32_t display_last_update = 0; #define MINGUAGE 0 #define DISPLAY_ROTATE 15 //sec +uint32_t backlightTimeout = 0; +#define BACKLIGHTTIMEOUT 60000 +#define BACKLIGHTONBRGT 129 +#define BACKLIGHTSTEP 5 +uint8_t backloghtBrightness = BACKLIGHTONBRGT; + +void backlightRefresh(void) +{ + backlightTimeout = millis(); +} + +void handleBacklight(void) +{ + uint32_t timeNow = millis(); + if (!backlightTimeout) + { + backlightRefresh(); + } + if (timeNow - backlightTimeout > BACKLIGHTTIMEOUT) + { + if (backloghtBrightness > BACKLIGHTSTEP) + { + backloghtBrightness -= BACKLIGHTSTEP; + Serial.printf("LCD: backlight %d\n", backloghtBrightness); + } + else + { + backloghtBrightness = 0; + } + } + else + { + if (backloghtBrightness < BACKLIGHTONBRGT) + { + backloghtBrightness += BACKLIGHTSTEP; + Serial.printf("LCD: backlight %d", backloghtBrightness); + } + } + ledcWrite(TFT_BL_PWMCHANNEL, backloghtBrightness); +} + void handleLCD() { static uint16_t angle; @@ -226,6 +318,8 @@ void handleLCD() nameSpr.drawString(sensor->getName().c_str(), name_spr_width / 2, nameSpr.fontHeight() / 2); nameSpr.drawString(sensor->getUnit().c_str(), name_spr_width / 2, nameSpr.fontHeight() / 2 * 3 + 2); nameSpr.pushSprite(DIAL_CENTRE_X - name_spr_width / 2, DIAL_CENTRE_Y + 40, 2); + + handleBacklight(); } // ======================================================================================= diff --git a/lcd.h b/lcd.h index d07e80c..671094f 100644 --- a/lcd.h +++ b/lcd.h @@ -7,3 +7,7 @@ void initLCD(); void handleLCD(); void plotNeedle(int16_t angle, uint16_t ms_delay, uint32_t value); +void setOTAProgress(uint8_t value); +void ProgressbarVisible(bool visible); + +void backlightRefresh(void); \ No newline at end of file diff --git a/main.cpp b/main.cpp index 9c8e6f8..d631d53 100644 --- a/main.cpp +++ b/main.cpp @@ -41,6 +41,7 @@ #include "CO2_sensor.h" #include "VOC_sensor.h" #include "button.h" +#include "esp_ota.h" /*--------------------------- Program ---------------------------------------*/ /** @@ -52,17 +53,33 @@ void setup() Serial.println(); Serial.print("Air Quality Sensor starting up, v"); Serial.println(VERSION); - - digitalWrite(5, false); - - initWifi(); - initMQTT(); - initSensor(); - initParticles(); - initCO2sensor(); - initVOCsensor(); + ProgressbarVisible(true); initLCD(); + setOTAProgress(10); + initButtons(); + setOTAProgress(20); + initWifi(); + setOTAProgress(30); + + initEspOta(); + setOTAProgress(40); + + initMQTT(); + setOTAProgress(50); + + initSensor(); + setOTAProgress(60); + + initParticles(); + setOTAProgress(70); + + initCO2sensor(); + setOTAProgress(80); + + initVOCsensor(); + setOTAProgress(90); + ProgressbarVisible(false); } /** @@ -77,4 +94,5 @@ void loop() handleSensor(); handleLCD(); handleMQTT(); + handleEspOta(); } diff --git a/particles.cpp b/particles.cpp index 8924d4d..1824c0e 100644 --- a/particles.cpp +++ b/particles.cpp @@ -12,7 +12,7 @@ char g_command_topic[50]; // MQTT topic for receiving commands #define PMS_STATE_READY 2 // Warmed up, ready to give data #define PMSMAX 50 #define PSMMIN 0 -#define PPDMAX 2000 +#define PPDMAX 3000 uint8_t g_pms_state = PMS_STATE_WAKING_UP; uint32_t g_pms_state_start = 0; // Timestamp when PMS state last changed diff --git a/platformio.ini b/platformio.ini index 04be749..007b65f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,3 +33,5 @@ lib_deps = ${common.lib_deps_builtin} ${common.lib_deps} adafruit/Adafruit SGP30 Sensor@^2.0.0 +upload_protocol = espota +upload_port = 192.168.2.236 diff --git a/sensor.h b/sensor.h index 7573315..1f2d5ae 100644 --- a/sensor.h +++ b/sensor.h @@ -42,6 +42,7 @@ class AQSSensor bool _publish = false; const uint32_t _scaleMin; const uint32_t _scaleMax; + bool _valid; public: AQSSensor(String name, sensor_e sensor, String unit, @@ -58,14 +59,23 @@ public: sprintf(_topic, "Sensors/%s/%s", deviceID.c_str(), name.c_str()); sprintf(_topic_1h, "Sensors/%s/%s/1h/", deviceID.c_str(), name.c_str()); sprintf(_topic_24h, "Sensors/%s/%s/24h/", deviceID.c_str(), name.c_str()); + _valid = false; } void publish(void) { - _publish = true; + if (_valid) + { + _publish = true; + } + else + { + Serial.printf("Sensor %s : Not published (invalid)\n", _name.c_str()); + } } - String getName(void) + String + getName(void) { return _name; } @@ -88,17 +98,18 @@ public: virtual void set(uint32_t value) { - if((value > _scaleMin) && (value < _scaleMax)) + if ((value > _scaleMin) && (value < _scaleMax)) { _value = value; _average1h.add(_value); _average24h.add(_value); + _valid = true; } else { - Serial.printf("Sensor %s: value out of bounds (%d)\n", _name.c_str(), value); + Serial.printf("Sensor %s set(%d): value out of bounds\n", _name.c_str(), _value); + _valid = false; } - } uint32_t value() { return _value; } @@ -113,7 +124,7 @@ public: sensor_e getSensor(void) { return _sensor; } - String getUnit(void) { return _unit;} + String getUnit(void) { return _unit; } uint32_t getMin(void) { return _scaleMin; } uint32_t getMax(void) { return _scaleMax; }