diff --git a/data/airqmon.css b/data/airqmon.css index 9d23b2cd06ab3c598f3c8aa6552f5156a30a4c37..1921c8c6e22df26cc238f02ef7f6f06aced53449 100644 --- a/data/airqmon.css +++ b/data/airqmon.css @@ -1,3 +1,7 @@ +body { + background-color: rgba(0, 0, 0, .1); +} + h1 { font-weight: bolder; letter-spacing: 3px; diff --git a/data/airqmon.js b/data/airqmon.js index 336f8f104eb5de1deeaffdd520f713a1f9a379c3..9d3a889e3dc59b442ca2002db1d998bf3b7ba884 100644 --- a/data/airqmon.js +++ b/data/airqmon.js @@ -81,7 +81,7 @@ var chartOptions = { responsive: true, millisPerPixel: 100, grid: { - fillStyle:'rgba(255,255,255, 0.75)', + fillStyle:'rgba(255,255,255, 1)', strokeStyle:'rgba(128,128,128, 0.10)', verticalSections: 5 }, @@ -124,7 +124,9 @@ function startWebsocket() { websock.onmessage = function(evt) { data = JSON.parse(evt.data); //console.log(data); - handleWebsocketMessage(data); + if (data !== null) { + handleWebsocketMessage(data); + } }; } @@ -156,7 +158,7 @@ function updateMetric(name, value, accuracy) { timeseries[name] = new TimeSeries(); timeseries[name].append(new Date().getTime(), numericValue); var metric = $('<li class="list-group-item list-group-item-action" id="metric_'+name+'">'+ - '<div class="row my-2 rowMetric">'+ + '<div class="row rowMetric">'+ '<div class="d-flex w-100 justify-content-between">'+ '<div class="sensorName">'+metrics[name].name+'</div>'+ '<div class="sensorValue">'+value+'</div>'+ @@ -178,7 +180,7 @@ function toggleChart(name) { charts[name] = new SmoothieChart(chartOptions); charts[name].addTimeSeries(timeseries[name], lineOptions); $('#metric_'+name).append($('<div class="row rowChart">'+ - '<div class="chart my-2"><canvas id="chart_'+name+'"></canvas></div>'+ + '<div class="chart mt-2"><canvas id="chart_'+name+'"></canvas></div>'+ '</div>')); charts[name].streamTo(document.getElementById("chart_"+name), 1000); } else { diff --git a/data/index.html b/data/index.html index 2b9f250d683c211a14f6b72806662c1c198d7493..8ff16e14d1a559c36dda01679db1f252563424b8 100644 --- a/data/index.html +++ b/data/index.html @@ -23,16 +23,15 @@ <main class="container flex-column justify-content-center align-items-center"> - <div class="mb-3 text-center my-4"> + <div class="row my-3 py-3 g-0 col-12 col-lg-6 offset-lg-3 bg-white text-center shadow shadow-sm rounded"> <h1>Air Quality Monitor</h1> + <div class="mt-2"> + <canvas id="gauge"></canvas> + </div> </div> - <div class="d-flex flex-row justify-content-center mb-3"> - <canvas id="gauge"></canvas> - </div> - - <div class="row mx-1 mb-3"> - <div class="list-group col-12 col-lg-6 offset-lg-3" id="metrics"></div> + <div class="row mb-3 g-0"> + <div class="list-group col-12 col-lg-6 offset-lg-3 shadow shadow-sm" id="metrics"></div> </div> <div class="spinner-border" role="status" id="wsSpinner"> diff --git a/src/main.cpp b/src/main.cpp index 7ccd1d0dfde271324f7a5dae8fd76aa510f255f0..669a81715293438a2b9c9acacb97f56b90c08242 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,8 @@ #include "config.h" #include <Arduino.h> +#include <EEPROM.h> #include <SoftwareSerial.h> #include <ESP8266WiFi.h> -#include <FS.h> #include <LittleFS.h> #include <ArduinoOTA.h> #include <ESP8266mDNS.h> @@ -17,6 +17,7 @@ #define PMS_TX D3 #define PMS_RX D4 +#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // 360 minutes - 4 times a day SoftwareSerial swSerial; WiFiClient wifiClient; @@ -27,9 +28,14 @@ SerialPM pms(PMS5003, PMS_RX, PMS_TX); Bsec iaqSensor; PubSubClient mqttClient(wifiClient); IPAddress mqttServer; - SimpleMap<String, double> *sensorData; +const uint8_t bsec_config_iaq[] = { +#include "config/generic_33v_3s_4d/bsec_iaq.txt" +}; +uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; +uint16_t stateUpdateCounter = 0; + void logLine(String line, bool newline = true) { Serial.print(line); @@ -280,11 +286,72 @@ void checkIaqSensorStatus(void) } } +void loadBmeState(void) +{ + if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE) { + // Existing state in EEPROM + Serial.println("Reading state from EEPROM"); + + for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) { + bsecState[i] = EEPROM.read(i + 1); + Serial.println(bsecState[i], HEX); + } + + iaqSensor.setState(bsecState); + checkIaqSensorStatus(); + } else { + // Erase the EEPROM with zeroes + Serial.println("Erasing EEPROM"); + + for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++) + EEPROM.write(i, 0); + + EEPROM.commit(); + } +} + +void updateBmeState(void) +{ + bool update = false; + /* Set a trigger to save the state. Here, the state is saved every STATE_SAVE_PERIOD with the first state being saved once the algorithm achieves full calibration, i.e. iaqAccuracy = 3 */ + if (stateUpdateCounter == 0) { + if (iaqSensor.iaqAccuracy >= 3) { + update = true; + stateUpdateCounter++; + } + } else { + /* Update every STATE_SAVE_PERIOD milliseconds */ + if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { + update = true; + stateUpdateCounter++; + } + } + + if (update) { + iaqSensor.getState(bsecState); + checkIaqSensorStatus(); + + Serial.println("Writing state to EEPROM"); + + for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE ; i++) { + EEPROM.write(i + 1, bsecState[i]); + Serial.println(bsecState[i], HEX); + } + + EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE); + EEPROM.commit(); + } +} + void setupBme() { + EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1); Wire.begin(); iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire); checkIaqSensorStatus(); + iaqSensor.setConfig(bsec_config_iaq); + checkIaqSensorStatus(); + loadBmeState(); bsec_virtual_sensor_t sensorList[10] = { BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, @@ -401,6 +468,7 @@ void readBme() sensorData->put("eco2_acc", (int)iaqSensor.co2Accuracy); sensorData->put("bvoc", iaqSensor.breathVocEquivalent); sensorData->put("bvoc_acc", (int)iaqSensor.breathVocAccuracy); + updateBmeState(); } else {