diff --git a/platformio.ini b/platformio.ini index e560ea9..dc473fc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,17 +15,13 @@ monitor_speed = 9600 platform = atmelsam board = nano_33_iot framework = arduino - -lib_deps = - WiFiNINA - MQTT +lib_deps = + WiFiNINA + MQTT [env:d1_mini] platform = espressif8266 board = d1_mini framework = arduino upload_protocol = esptool - -lib_deps = - MQTT - +lib_deps = MQTT diff --git a/src/main.cpp b/src/main.cpp index 3cd5736..8896f1e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,58 +3,41 @@ #include #include "config.h" -#if !defined(HAS_WIFI) -#error ("This example program has been created for specific WiFi enabled hosts only.") -#endif - WiFiClient client; -// Buffers for assembling http POST requests +// Buffers for assembling http POST requests. char postBuffer[450] = {0}; char fieldBuffer[70] = {0}; -// Structs for data +// Structs for holding measurement data. AirData_t airData = {0}; AirQualityData_t airQualityData = {0}; LightData_t lightData = {0}; ParticleData_t particleData = {0}; SoundData_t soundData = {0}; -// Define the display attributes of data sent to Home Assistant. -// The chosen name, unit and icon will appear in on the overview -// dashboard in Home Assistant. The icons can be chosen from -// https://cdn.materialdesignicons.com/5.3.45/ -// (remove the "mdi-" part from the icon name). +// Definition of the display attributes of data sent to Home Assistant. // The attribute fields are: {name, unit, icon, decimal places} -HA_Attributes_t pressure = {"Pressure","Pa","weather-cloudy",0}; +HA_Attributes_t pressure = {"Pressure", "hPa", "weather-cloudy", 0}; HA_Attributes_t humidity = {"Humidity","%","water-percent",1}; +HA_Attributes_t temperature = {"Temperature", CELSIUS_SYMBOL, "thermometer", 1}; HA_Attributes_t illuminance = {"Illuminance","lx","white-balance-sunny",2}; HA_Attributes_t soundLevel = {"Sound level","dBA","microphone",1}; HA_Attributes_t peakAmplitude = {"Sound peak","mPa","waveform",2}; HA_Attributes_t estimatedCO2 = {"Estimated CO2","ppm","chart-bubble",1}; HA_Attributes_t equivalentBreathVOC = {"Equivalent breath VOC","ppm","chart-bubble",2}; -HA_Attributes_t AQI = {"Air Quality Index"," ","thought-bubble-outline",1}; +HA_Attributes_t AQ_index = {"Air Quality Index", " ", "thought-bubble-outline", 1}; HA_Attributes_t AQ_assessment = {"Air quality assessment","","flower-tulip",0}; HA_Attributes_t AQ_calibration = {"Air quality calibration","","flower-tulip",0}; -#if (PARTICLE_SENSOR == PARTICLE_SENSOR_PPD42) - HA_Attributes_t particulates = {"Particle concentration","ppL","chart-bubble",0}; -#else - HA_Attributes_t particulates = {"Particle concentration",SDS011_UNIT_SYMBOL,"chart-bubble",2}; -#endif -#ifdef USE_FAHRENHEIT - HA_Attributes_t temperature = {"Temperature",FAHRENHEIT_SYMBOL,"thermometer",1}; -#else - HA_Attributes_t temperature = {"Temperature",CELSIUS_SYMBOL,"thermometer",1}; -#endif - - -void setup() { - // Initialize the host's pins, set up the serial port and reset: - SensorHardwareSetup(I2C_ADDRESS); +HA_Attributes_t particleConcentration = {"Particle concentration", "ppL", "chart-bubble", 0}; +HA_Attributes_t particleCalibration = {"Particle calibration", "", "chart-bubble", 0}; +void setup() +{ + SensorHardwareSetup(I2C_ADDRESS); connectToWiFi(WIFI_SSID, WIFI_PASSWORD); - - // Apply settings to the MS430 and enter cycle mode + + // Apply settings to the Metriful MS430 board and enter cycle mode. uint8_t particleSensorCode = PARTICLE_SENSOR; uint8_t cycle_period = CYCLE_PERIOD; TransmitI2C(I2C_ADDRESS, PARTICLE_SENSOR_SELECT_REG, &particleSensorCode, 1); @@ -63,12 +46,12 @@ void setup() { TransmitI2C(I2C_ADDRESS, CYCLE_MODE_CMD, 0, 0); } -// Send the data to Home Assistant as an HTTP POST request. +// Send data to Home Assistant as an HTTP POST request. void http_POST_Home_Assistant(const HA_Attributes_t * attributes, const char * valueText) { client.stop(); if (client.connect(HOME_ASSISTANT_IP, 8123)) { // Form the URL from the name but replace spaces with underscores - strcpy(fieldBuffer,attributes->name); + strcpy(fieldBuffer, attributes->name); for (uint8_t i=0; iunit, attributes->name, attributes->icon); - + sprintf(postBuffer, + "{\"state\":%s,\"attributes\":{\"unit_of_measurement\"" + ":\"%s\",\"friendly_name\":\"%s\",\"icon\":\"mdi:%s\"}}", + valueText, attributes->unit, attributes->name, attributes->icon); + sprintf(fieldBuffer,"Content-Length: %u", strlen(postBuffer)); client.println(fieldBuffer); client.println(); @@ -95,7 +79,7 @@ void http_POST_Home_Assistant(const HA_Attributes_t * attributes, const char * v } } -// Send numeric data with specified sign, integer and fractional parts +// Send numeric data with specified sign, integer and fractional parts. void sendNumericData(const HA_Attributes_t * attributes, uint32_t valueInteger, uint8_t valueDecimal, bool isPositive) { char valueText[20] = {0}; @@ -115,22 +99,24 @@ void sendNumericData(const HA_Attributes_t * attributes, uint32_t valueInteger, http_POST_Home_Assistant(attributes, valueText); } -// Send a text string: must have quotation marks added +// Send a text string: must have quotation marks added. void sendTextData(const HA_Attributes_t * attributes, const char * valueText) { char quotedText[20] = {0}; sprintf(quotedText,"\"%s\"", valueText); http_POST_Home_Assistant(attributes, quotedText); } -void loop() { - - // Wait for the next new data release, indicated by a falling edge on READY - Serial.println("Waiting for new data ..."); +void wait_for_new_readings() +{ + Serial.println("Waiting for new data from sensors ..."); while (!ready_assertion_event) { yield(); } ready_assertion_event = false; +} +void read_sensor_data_from_i2c() +{ // Read data from the MS430 into the data structs. Serial.println("Reading data from sensors"); ReceiveI2C(I2C_ADDRESS, AIR_DATA_READ, (uint8_t *) &airData, AIR_DATA_BYTES); @@ -138,41 +124,64 @@ void loop() { ReceiveI2C(I2C_ADDRESS, LIGHT_DATA_READ, (uint8_t *) &lightData, LIGHT_DATA_BYTES); ReceiveI2C(I2C_ADDRESS, SOUND_DATA_READ, (uint8_t *) &soundData, SOUND_DATA_BYTES); ReceiveI2C(I2C_ADDRESS, PARTICLE_DATA_READ, (uint8_t *) &particleData, PARTICLE_DATA_BYTES); +} - // Check that WiFi is still connected +void make_sure_wifi_is_connected() { uint8_t wifiStatus = WiFi.status(); if (wifiStatus != WL_CONNECTED) { - // There is a problem with the WiFi connection: attempt to reconnect. - Serial.print("Wifi status: "); + Serial.print("Wifi diconnected! Status: "); Serial.println(interpret_WiFi_status(wifiStatus)); connectToWiFi(WIFI_SSID, WIFI_PASSWORD); ready_assertion_event = false; - } - + } +} + +void send_data_to_home_assistant() { + Serial.println("Send data to Home Assistant"); + uint8_t T_intPart = 0; uint8_t T_fractionalPart = 0; bool isPositive = true; getTemperature(&airData, &T_intPart, &T_fractionalPart, &isPositive); - // Send data to Home Assistant - Serial.println("Send data to Home Assistant"); sendNumericData(&temperature, (uint32_t) T_intPart, T_fractionalPart, isPositive); - sendNumericData(&pressure, (uint32_t) airData.P_Pa, 0, true); + sendNumericData(&pressure, (uint32_t)airData.P_Pa / 100, 0, true); sendNumericData(&humidity, (uint32_t) airData.H_pc_int, airData.H_pc_fr_1dp, true); sendNumericData(&illuminance, (uint32_t) lightData.illum_lux_int, lightData.illum_lux_fr_2dp, true); sendNumericData(&soundLevel, (uint32_t) soundData.SPL_dBA_int, soundData.SPL_dBA_fr_1dp, true); - sendNumericData(&peakAmplitude, (uint32_t) soundData.peak_amp_mPa_int, - soundData.peak_amp_mPa_fr_2dp, true); - sendNumericData(&AQI, (uint32_t) airQualityData.AQI_int, airQualityData.AQI_fr_1dp, true); - if (PARTICLE_SENSOR != PARTICLE_SENSOR_OFF) { - sendNumericData(&particulates, (uint32_t) particleData.concentration_int, - particleData.concentration_fr_2dp, true); - } - sendTextData(&AQ_assessment, interpret_AQI_value(airQualityData.AQI_int)); - sendNumericData(&estimatedCO2, (uint32_t) airQualityData.CO2e_int, airQualityData.CO2e_fr_1dp, true); - sendNumericData(&equivalentBreathVOC, (uint32_t) airQualityData.bVOC_int, airQualityData.bVOC_fr_2dp, true); - sendNumericData(&AQ_calibration, airQualityData.AQI_accuracy, 0, true); + sendNumericData(&peakAmplitude, (uint32_t) soundData.peak_amp_mPa_int, soundData.peak_amp_mPa_fr_2dp, true); + // When the particle sensor is not connected, we get 65535 readings. + // We want to skip those. Also skip sending data while the sensor + // is warming up. This way, invalid data is not logged in + // Home Assistant. + sendNumericData(&particleCalibration, (uint32_t)particleData.valid, 0, true); + if (particleData.valid != 0 && particleData.concentration_int < 60000) + { + sendNumericData(&particleConcentration, (uint32_t)particleData.concentration_int, particleData.concentration_fr_2dp, true); + } + + // While the Metriful board is still in its self-calibration + // phase, do not send air quality data to Home Assistant. + sendNumericData(&AQ_calibration, airQualityData.AQI_accuracy, 0, true); + if (airQualityData.AQI_accuracy > 0) { + sendNumericData(&estimatedCO2, (uint32_t) airQualityData.CO2e_int, airQualityData.CO2e_fr_1dp, true); + sendNumericData(&equivalentBreathVOC, (uint32_t) airQualityData.bVOC_int, airQualityData.bVOC_fr_2dp, true); + sendNumericData(&AQ_index, (uint32_t)airQualityData.AQI_int, airQualityData.AQI_fr_1dp, true); + sendTextData(&AQ_assessment, interpret_AQI_value(airQualityData.AQI_int)); + } +} + +void log_measurements_to_console() { printAirData(&airData, false); printParticleData(&particleData, false, PARTICLE_SENSOR); +} + +void loop() +{ + wait_for_new_readings(); + read_sensor_data_from_i2c(); + make_sure_wifi_is_connected(); + send_data_to_home_assistant(); + log_measurements_to_console(); } \ No newline at end of file