/* * derived from the sketch used for Jar /home/laudix/code/ESP8266_WifiLEDs_AdvancedWebServer * * With an ESP8266: https://www.amazon.com/gp/product/B081PX9YFV * and browse output via a web page: http://10.0.3.106 * * with BME680 connected,send results to the websocket clients * derived from /home/laudix/code/ESP8266_BME860-basic * * to repair compiler errors for pre-compiled code, * update: /home/laudix/.arduino15/packages/esp8266/hardware/esp8266/2.7.4/platform.txt * "M:\MediaLibraries\Documents\Projects\Raspberry\ESP8266 sensor.docx" * * The settings for WiFi and the MQTT are stored in EEPROM; read, then used to initialize * (see: /home/laudix/code/ESP8266_EEPROM_update/ESP8266_EEPROM_update.ino) * * The BME680 gives in accurate readinga for Temperature, and likely Humidity,. I assume this * is due to the internal heater for teh gas analysis. * Using the BME280 for better readings. * https://www.amazon.com/gp/product/B07KR24P6P * (see: /home/laudix/code/ESP8266_BME280_basic/ESP8266_BME280_basic.ino) * * The following parameters will be displayed every 3 minutes: Timestamp in milliseconds Raw Temperature in °C Pressure in hPa Raw Relative Humidity in % Raw data from the gas sensor as a resistance value in Ohm IAQ index IAQ Accuracy (begins at 0 after start up, goes to 1 after a few minutes and reaches 3 when the sensor is calibrated). Temperature in °C Relative Humidity in % Static IAQ CO2 equivalent (estimation of the CO2 equivalent in ppm in the environment) Breath VOC equivalent output (estimates the total VOC concentration in ppm in the environment) * The initial value of the IAQ index is 25.00. and it stays that way for a good while. * Only after several minutes does the IAQ value starts to drift. * * If pin RX (GPIO3) is low (gnd) at boot * Create a WiFi access point and provide a web server on it. * otherwise, connect to wifi and display normally * * see also * PIN TO USE: https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/ * POC: /home/laudix/code/ESP8266_mqtt_reconnect_nonblocking */ #include #include #include #include #include #include #include #include #include "index_html.h" #include "config_html.h" #include "bsec.h" #include "eeprom.h" #include "Max44009.h" #include "SparkFunBME280.h" #define __VERSION __DATE__ " " __TIME__ //a better way? https://forum.arduino.cc/index.php?topic=616098.0 // WiFi Parameters #define bootModePin 3 char ssid[ssidLengthMax]; char pskPwd[pskPwdLengthMax]; // Define websockets parameters #define DEBUG_WEBSOCKETS_PORT Serial #define _WEBSOCKETS_LOGLEVEL_ 3 // Debug Level from 0 to 4 char hostName[hostnameStoredLengthMax]; using namespace websockets2_generic; #define WEBSOCKETS_PORT 8080 const byte maxClients = 2; // Define how many clients we accept simultaneously. long sensorsLastWebsocketPublished = 0; //when was the sensor json last sent long websocketPublishInterval; // how long to wait before sending another update to the websocket clients StaticJsonDocument<512> jsonDoc; // how much stack memory to reserve JsonObject jsonRoot = jsonDoc.to(); // an empty object to put JSON in (used for sensor results) // EEPROM Parameters #define eepromBufferSize 220 // EEPROM only holds 512 bytes but we don't need all of it // Define MQTT parameters //const char* mqttServer = "mqtt.hosav.com"; char mqttServer[mqttServerStoredLengthMax]; //const IPAddress mqttServer(10,0,3,199); char mqttLocation[mqttLocationStoredLengthMax]; const int mqttPort = 8883; //#define mqttTopicRootDefault "myLocation/" //"52Apache/" char mqttTopicRoot[mqttLocationStoredLengthMax+hostnameStoredLengthMax]; long lastMqttReconnectAttempt = 0; const int mqttReconnectInterval = 5000; //long lastMqttPublished = 0; long mqttPublishInterval; // how long to wait before sending another update to the MQTT Server unsigned long lastMsg = 0; #define MSG_BUFFER_SIZE (50) char chrPayload[MSG_BUFFER_SIZE]; char chrTopic[MSG_BUFFER_SIZE]; long bme680LastWebsocketPublished = millis()-websocketPublishInterval; long bme680LastMqttPublished = millis()-mqttPublishInterval; // BME680 parameters String outputStr; const int led = LED_BUILTIN; bool bme680Enabled = true; const int bme680ReconnectInterval = 30000; long bme680LastReconnectAttempt = millis() - bme680ReconnectInterval -1; // MAX44009 parameters bool max44009Enabled = true; long max44009LastWebsocketPublished = millis()-websocketPublishInterval; long max44009LastMqttPublished = millis()-mqttPublishInterval; //BME280 parameters bool bme280Enabled = true; long bme280LastWebsocketPublished = millis()-websocketPublishInterval; long bme280LastMqttPublished = millis()-mqttPublishInterval; ESP8266WebServer server(80); WebsocketsClient clients[maxClients]; WebsocketsServer SocketServer; WiFiClientSecure wifiClient; PubSubClient mqttClient(wifiClient); Bsec iaqSensor; Max44009 max44009Lux(0x4A); BME280 bme280Thp; //Temperature, Humidity, Pressure void doReboot(){ // this will cause THIS device (the ESP8266) to restart // losing all accumulated data, but does reinitiallize all sensors // disconnect from websocket clients for (byte i = 10; i > 0; i--) { Serial.print(i); Serial.println(F(" REBOOT in progress")); Serial.flush(); delay(250); } Serial.println(F("\n######> REBOOT NOW <######\n\n\n\n")); for (byte i = 0; i < maxClients; i++) { if (clients[i].available()) { clients[i].close(); } } // disconnect MQTT mqttClient.disconnect(); // restart the sdk //https://circuits4you.com/2017/12/31/software-reset-esp8266/ ESP.restart(); } void getEeprom() { //read from the EEPROM and fill in the variables EEPROM.begin(eepromBufferSize); readEeprom(ssid, sizeof(ssid)/sizeof(ssid[0]), ssidAddress); readEeprom(pskPwd, sizeof(pskPwd)/sizeof(pskPwd[0]), pskPwdAddress); readEeprom(hostName, sizeof(hostName)/sizeof(hostName[0]), hostnameStoredAddress); readEeprom(mqttServer, sizeof(mqttServer)/sizeof(mqttServer[0]), mqttServerStoredAddress); readEeprom(mqttLocation, sizeof(mqttLocation)/sizeof(mqttLocation[0]), mqttLocationStoredAddress); readEeprom(chrTopic, websocketPublishIntervalLengthMax, websocketPublishIntervalAddress); //borrow the chrTopic to temporarily hold teh retreived value websocketPublishInterval = strtol(chrTopic, NULL, 10); // convert the char-array to a long readEeprom(chrTopic, mqttPublishIntervalLengthMax, mqttPublishIntervalAddress); //borrow the chrTopic to temporarily hold teh retreived value mqttPublishInterval = strtol(chrTopic, NULL, 10); // convert the char-array to a long readEeprom(chrTopic, bme680EnabledLengthMax, bme680EnabledAddress); //borrow the chrTopic to temporarily hold teh retreived value bme680Enabled = atoi(chrTopic); // convert the char-array to a bool readEeprom(chrTopic, bme280EnabledLengthMax, bme280EnabledAddress); //borrow the chrTopic to temporarily hold teh retreived value bme280Enabled = atoi(chrTopic); // convert the char-array to a bool readEeprom(chrTopic, max44009EnabledLengthMax, max44009EnabledAddress); //borrow the chrTopic to temporarily hold teh retreived value max44009Enabled = atoi(chrTopic); // convert the char-array to a bool EEPROM.end(); //commit all (though no changes were made) and remove the buffer from RAM } void updateEeprom(const char* buffer, int bufferLength, int startAddress) { // update the values in the EEPROM EEPROM.begin(eepromBufferSize); int i = 0; for (i=0; i incomingJsonDoc; DeserializationError err = deserializeJson(incomingJsonDoc, data); switch (err.code()) { case DeserializationError::Ok: Serial.println(F("Deserialization succeeded")); Serial.print("set \"StaticJsonDocument settingsJson;\" to :"); Serial.println(incomingJsonDoc.memoryUsage()); //Use this function at design time to measure the required capacity for the JsonDocument. break; case DeserializationError::InvalidInput: Serial.println(F("Invalid json input!")); break; case DeserializationError::NoMemory: Serial.println(F("Not enough memory")); break; default: Serial.println(F("Deserialization failed")); break; } if (incomingJsonDoc.isNull()) { // incoming data is NOT json if (data == "syn") { //reply with acknowledgement client.send("ack"); settingsSend(); } else if (data == "reboot") { doReboot(); } else { // Echo message client.send("Echo: " + data); } } else { // incoming data IS json serializeJsonPretty(incomingJsonDoc, Serial); Serial.println(""); // for (JsonPair kvPair : incomingJsonDoc.as()) { // Serial.println(kvPair.key().c_str()); // } JsonObject incomingJsonObjNested = incomingJsonDoc["updateSettings"]; for (JsonPair kvPair : incomingJsonObjNested) { // https://arduinojson.org/v6/api/jsonobject/begin_end/ // https://arduinojson.org/v5/doc/tricks/ Serial.print(kvPair.key().c_str()); Serial.print(": "); Serial.println(kvPair.value().as()); Serial.println(kvPair.value().as()); // strcmp returns 0 if there are zero differences (eg: 0=no delta) if (!strcmp(kvPair.key().c_str(), "hostName")) { updateEeprom(kvPair.value().as(), hostnameStoredLengthMax, hostnameStoredAddress); } if (!strcmp(kvPair.key().c_str(), "websocketPublishInterval")) { updateEeprom(kvPair.value().as(), websocketPublishIntervalLengthMax, websocketPublishIntervalAddress); } if (!strcmp(kvPair.key().c_str(), "mqttPublishInterval")) { updateEeprom(kvPair.value().as(), mqttPublishIntervalLengthMax, mqttPublishIntervalAddress); } if (!strcmp(kvPair.key().c_str(), "mqttServer")) { updateEeprom(kvPair.value().as(), mqttServerStoredLengthMax, mqttServerStoredAddress); } if (!strcmp(kvPair.key().c_str(), "mqttLocation")) { updateEeprom(kvPair.value().as(), mqttLocationStoredLengthMax, mqttLocationStoredAddress); } if (!strcmp(kvPair.key().c_str(), "ssid")) { updateEeprom(kvPair.value().as(), ssidLengthMax, ssidAddress); } if (!strcmp(kvPair.key().c_str(), "pskPwd")) { updateEeprom(kvPair.value().as(), pskPwdLengthMax, pskPwdAddress); } if (!strcmp(kvPair.key().c_str(), "bme680Enabled")) { updateEeprom(kvPair.value().as(), bme680EnabledLengthMax, bme680EnabledAddress); } if (!strcmp(kvPair.key().c_str(), "bme280Enabled")) { updateEeprom(kvPair.value().as(), bme280EnabledLengthMax, bme280EnabledAddress); } if (!strcmp(kvPair.key().c_str(), "max44009Enabled")) { updateEeprom(kvPair.value().as(), max44009EnabledLengthMax, max44009EnabledAddress); } } settingsSend(); //once all settings are updated, inform the websocket clients } } void handleEvent(WebsocketsClient &client, WebsocketsEvent event, String data) { if (event == WebsocketsEvent::ConnectionClosed) { Serial.println(F("Connection closed")); } } int8_t getFreeClientIndex() { // If a client in our list is not available, it's connection is closed and we // can use it for a new client. for (byte i = 0; i < maxClients; i++) { if (!clients[i].available()) return i; } return -1; } void clientSendAll(char* msg) { // loop through all attached clients and send a message for (byte i = 0; i < maxClients; i++) { if (clients[i].available()) { clients[i].send(msg); } } } void clientSendAll(String msg) { // loop through all attached clients and send a message for (byte i = 0; i < maxClients; i++) { if (clients[i].available()) { clients[i].send(msg); } } } void SocketListenForClients() { if (SocketServer.poll()) { int8_t freeIndex = getFreeClientIndex(); if (freeIndex >= 0) { WebsocketsClient newClient = SocketServer.accept(); Serial.printf("Waiting for new websockets client at index %d\n", freeIndex); newClient.onMessage(handleMessage); newClient.onEvent(handleEvent); clients[freeIndex] = newClient; } } } void SocketPollClients() { for (byte i = 0; i < maxClients; i++) { clients[i].poll(); } } // a helper function to round doubles // rounds a number to given decimal places (default 2) // example: roundfloat(3.14159, 3) -> 3.142 // https://arduinojson.org/v6/how-to/configure-the-serialization-of-floats/ float roundfloat(float value, int places = 2) { return (int)(value * pow(10, places) + 0.5) / pow(10, places); } void bme680Config() { //NOTE: the initial start seems fast, but it takes 5 min when this routine reruns //The error clears, but the sensor is in idle state for 5 min (no idea why) long isNow = millis(); if (isNow - bme680LastReconnectAttempt > bme680ReconnectInterval) { bme680LastReconnectAttempt = isNow; bsec_virtual_sensor_t sensorList[10] = { BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, BSEC_OUTPUT_RAW_HUMIDITY, BSEC_OUTPUT_RAW_GAS, BSEC_OUTPUT_IAQ, BSEC_OUTPUT_STATIC_IAQ, BSEC_OUTPUT_CO2_EQUIVALENT, BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, }; //iaqSensor.delay_ms(500); //Task that delays for a ms period of time https://github.com/BoschSensortec/BSEC-Arduino-library/blob/master/src/bsec.h#L92 iaqSensor.begin(BME680_I2C_ADDR_SECONDARY, Wire); //BME680_I2C_ADDR_PRIMARY outputStr = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix); Serial.println(outputStr); //checkIaqSensorStatus(); iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP); } } void checkIaqSensorStatus(void) { if (iaqSensor.status != BSEC_OK) { if (iaqSensor.status < BSEC_OK) { outputStr = "BSEC error code : " + String(iaqSensor.status); Serial.println(outputStr); bme680Config(); delay(1000); // for (;;) // errLeds(); /* Halt in case of failure */ } else { outputStr = "BSEC warning code : " + String(iaqSensor.status); Serial.println(outputStr); } } if (iaqSensor.bme680Status != BME680_OK) { if (iaqSensor.bme680Status < BME680_OK) { outputStr = "BME680 error code : " + String(iaqSensor.bme680Status); Serial.println(outputStr); bme680Config(); delay(1000); // for (;;) // errLeds(); /* Halt in case of failure */ } else { outputStr = "BME680 warning code : " + String(iaqSensor.bme680Status); Serial.println(outputStr); } } } void errLeds(void) { pinMode(LED_BUILTIN, OUTPUT); digitalWrite(led, HIGH); delay(100); digitalWrite(led, LOW); delay(100); } void checkBME680() { long isNow = millis(); bool websocketTime = isNow - bme680LastWebsocketPublished > websocketPublishInterval; // is it time to update the web socket clients? bool mqttTime = isNow - bme680LastMqttPublished > mqttPublishInterval; // time to update MQTT? if (bme680Enabled && (websocketTime || mqttTime)) { // somebody needs an update, read the sensor if (iaqSensor.run()) { // If new data is available //class members: https://github.com/BoschSensortec/BSEC-Arduino-library/blob/master/keywords.txt if (websocketTime) { bme680LastWebsocketPublished = isNow; // update the json for the websocket clients // convert Celsius to Fahrenheit (24°C × 9/5) + 32 = 75.2°F jsonRoot["sensors"]["BME680"]["temperatureRaw"] = roundfloat(iaqSensor.rawTemperature*9/5+32); jsonRoot["sensors"]["BME680"]["temperatureCalc"] = roundfloat(iaqSensor.temperature*9/5+32); jsonRoot["sensors"]["BME680"]["humidityRaw"] = roundfloat(iaqSensor.rawHumidity); jsonRoot["sensors"]["BME680"]["humidityCalc"] = roundfloat(iaqSensor.humidity); //convert pascals to inHg https://www.convertunits.com/from/Pa/to/in+Hg jsonRoot["sensors"]["BME680"]["pressureRaw"] = roundfloat(iaqSensor.pressure/3386.389); jsonRoot["sensors"]["BME680"]["gasResistance"] = roundfloat(iaqSensor.gasResistance); jsonRoot["sensors"]["BME680"]["iaq"] = roundfloat(iaqSensor.iaq); jsonRoot["sensors"]["BME680"]["staticIaq"] = roundfloat(iaqSensor.staticIaq); jsonRoot["sensors"]["BME680"]["iaqAccuracy"] = roundfloat(iaqSensor.iaqAccuracy); jsonRoot["sensors"]["BME680"]["co2Equivalent"] = roundfloat(iaqSensor.co2Equivalent); jsonRoot["sensors"]["BME680"]["breathVocEquivalent"] = roundfloat(iaqSensor.breathVocEquivalent); } if (mqttTime) { // update MQTT //time to send another update bme680LastMqttPublished = isNow; if (mqttClient.connected()) { // strcpy(chrTopic, mqttTopicRoot); // strcat(chrTopic, "temperature"); // dtostrf(iaqSensor.temperature*9/5+32, 6, 2, chrPayload); // mqttClient.publish(chrTopic, chrPayload); // // strcpy(chrTopic, mqttTopicRoot); // strcat(chrTopic, "humidity"); // dtostrf(iaqSensor.humidity, 6, 2, chrPayload); // mqttClient.publish(chrTopic, chrPayload); // // strcpy(chrTopic, mqttTopicRoot); // strcat(chrTopic, "pressure"); // dtostrf(iaqSensor.pressure/3386.389, 6, 2, chrPayload); // mqttClient.publish(chrTopic, chrPayload); strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "IAQ"); dtostrf(iaqSensor.staticIaq, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); } } } else { //BME680 sensor didn't run so check its status bme680LastWebsocketPublished = isNow; bme680LastMqttPublished = isNow; checkIaqSensorStatus(); } } } void checkMQTT() { if (!mqttClient.connected()) { long now = millis(); if (now - lastMqttReconnectAttempt > mqttReconnectInterval) { lastMqttReconnectAttempt = now; // Attempt to reconnect if (mqttReconnect()) { lastMqttReconnectAttempt = 0; } } } else { // Client is connected mqttClient.loop(); unsigned long now = millis(); if (now - lastMsg > 5000) { lastMsg = now; } } } boolean mqttReconnect() { char mqttClientName[28] = "MQTT-"; strcat(mqttClientName, WiFi.macAddress().c_str()); //use the mac address as a unique identifier if (mqttClient.connect(mqttClientName)) { //connect with a unique name Serial.print(F("MQTT connected. Name of this client: ")); Serial.println(mqttClientName); // Once connected, publish an announcement... strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "Test"); //mqttClient.publish(chrTopic,"hello world"); // ... and resubscribe strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "#"); //wildcard; subscribe to all topics in this path/branch mqttClient.subscribe(chrTopic); } else { Serial.println(F("MQTT connect FAIL")); } return mqttClient.connected(); } void mqttCallback(char* topic, byte* payload, unsigned int length) { // handle message arrived Serial.print(F("called back: a message, with subscribed topic [")); Serial.print(topic); Serial.print(F("], was received with payload: ")); for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println();} void checkMax44009(){ //note: the raw lux result is sent to MQTT, but the log of lux is send to websocket clients so it fits on the graph long isNow = millis(); bool websocketTime = isNow - max44009LastWebsocketPublished > websocketPublishInterval; // is it time to update the web socket clients? bool mqttTime = isNow - max44009LastMqttPublished > mqttPublishInterval; // time to update MQTT? float lux; int err; if (max44009Enabled && (websocketTime || mqttTime)) { // somebody needs an update, read the sensor lux = max44009Lux.getLux(); err = max44009Lux.getError(); if (err != 0) { Serial.print("MAX 44009 Error:\t"); Serial.println(err); max44009LastWebsocketPublished = isNow; max44009LastMqttPublished = isNow; return; //nothing else to do until the error is corrected } else { // Serial.print("lux:\t"); // Serial.println(lux); } if (websocketTime) { //send update to websocket clients max44009LastWebsocketPublished = isNow; //update json to send available websocket clients jsonRoot["sensors"]["MAX44009"]["lux"] = roundfloat(log(lux+1)); } if (mqttTime) { //send update to MQTT server if (mqttClient.connected()) { max44009LastMqttPublished = isNow; strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "lux"); dtostrf(lux, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); } } } } void checkBME280() { long isNow = millis(); bool websocketTime = isNow - bme280LastWebsocketPublished > websocketPublishInterval; // is it time to update the web socket clients? bool mqttTime = isNow - bme280LastMqttPublished > mqttPublishInterval; // time to update MQTT? if (bme280Enabled && (websocketTime || mqttTime)) { // somebody needs an update, read the sensor if (bme280Thp.readRegister(BME280_CHIP_ID_REG) == 0x60) { // BME280 is responding to requests (0x60 is the chip ID) line 101 of https://github.com/sparkfun/SparkFun_BME280_Arduino_Library/blob/master/src/SparkFunBME280.cpp float newTemperature, newHumidity, newPressure; double newDewpoint; newTemperature = roundfloat(bme280Thp.readTempF()); //round to 2 decimal places newHumidity = roundfloat(bme280Thp.readFloatHumidity()); newPressure = roundfloat(bme280Thp.readFloatPressure()/3386.389); //convert pascals to inHg https://www.convertunits.com/from/Pa/to/in+Hg newDewpoint = roundfloat(bme280Thp.dewPointF()); if (websocketTime) { //send update to websocket clients bme280LastWebsocketPublished = isNow; //update json to send available websocket clients jsonRoot["sensors"]["BME280"]["temperature"] = newTemperature; jsonRoot["sensors"]["BME280"]["humidity"] = newHumidity; jsonRoot["sensors"]["BME280"]["pressure"] = newPressure; jsonRoot["sensors"]["BME280"]["dewpoint"] = newDewpoint; } if (mqttTime) { //send update to MQTT server if (mqttClient.connected()) { bme280LastMqttPublished = isNow; strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "temperature"); dtostrf(newTemperature, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); bme280LastMqttPublished = isNow; strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "humidity"); dtostrf(newHumidity, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); bme280LastMqttPublished = isNow; strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "pressure"); dtostrf(newPressure, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); bme280LastMqttPublished = isNow; strcpy(chrTopic, mqttTopicRoot); strcat(chrTopic, "dewpoint"); dtostrf(newDewpoint, 6, 2, chrPayload); mqttClient.publish(chrTopic, chrPayload); } } } else { // a BME280 error occured Serial.println("BME280 Error"); bme280LastWebsocketPublished = isNow; bme280LastMqttPublished = isNow; } } } void websocketSend() { long isNow = millis(); bool websocketTime = isNow - sensorsLastWebsocketPublished > websocketPublishInterval; // is it time to update the web socket clients? if (websocketTime) { // time to send an update // could send as serializeMsgPack to save bandwidth, https://arduinojson.org/v6/api/msgpack/serializemsgpack/ // but there's is no need yet sensorsLastWebsocketPublished = isNow; serializeJsonPretty(jsonRoot, Serial); // display for debug/development convenience Serial.println(""); outputStr = ""; serializeJson(jsonRoot, outputStr); noInterrupts(); // do NOT let any process 'steal' the focus while the json object is deleted jsonRoot = jsonDoc.to(); // clear the object of data asap so it can be reused interrupts(); clientSendAll(outputStr); // update the websocket clients } } void settingsSend () { // gather all the settings and send to websocket clients // https://arduinojson.org/v6/how-to/reuse-a-json-document/ StaticJsonDocument<275> settingsJson; // get a list of connected clients byte connectedClientIds[maxClients]; byte connectedClientPos = 0; for (byte i = 0; i < maxClients; i++) { if (clients[i].available()) { settingsJson["settings"]["connectedWebsocketClients"].add(i); } } settingsJson["settings"]["bme680Enabled"] = bme680Enabled; settingsJson["settings"]["max44009Enabled"] = max44009Enabled; settingsJson["settings"]["bme280Enabled"] = bme280Enabled; settingsJson["settings"]["websocketPublishInterval"] = websocketPublishInterval; settingsJson["settings"]["mqttPublishInterval"] = mqttPublishInterval; settingsJson["settings"]["hostName"] = hostName; settingsJson["settings"]["mqttServer"] = mqttServer; settingsJson["settings"]["mqttLocation"] = mqttLocation; settingsJson["settings"]["ssid"] = ssid; settingsJson["settings"]["pskPwd"] = pskPwd; settingsJson["settings"]["version"] = __VERSION; serializeJsonPretty(settingsJson, Serial); Serial.println(""); //Serial.print("set \"StaticJsonDocument settingsJson;\" to :"); //Serial.println(settingsJson.memoryUsage()); //Use this function at design time to measure the required capacity for the JsonDocument. outputStr = ""; serializeJson(settingsJson, outputStr); clientSendAll(outputStr); // update the websocket clients } // ######################################################################## void setup(void) { outputStr.reserve(320); // the json out is > 256 chars. avoid memory issues by reserving space pinMode(led, OUTPUT); digitalWrite(led, LOW); Serial.begin(115200); for (int i=0; i<100; i++) { // wait for Serial to be ready, but don't wait forever if (Serial) {Serial.println(F("Serial is ready")); break;} delay(10); } delay(1000); //retrieve the info stored in the EEPROM getEeprom(); // Serial.print("ssid: "); Serial.println(ssid); // Serial.print("pskPwd: "); Serial.println(pskPwd); // Serial.print("hostName: "); Serial.println(hostName); // Serial.print("mqttServer: "); Serial.println(mqttServer); // Serial.print("websocketPublishInterval: "); Serial.println(websocketPublishInterval); // Serial.print("mqttPublishInterval: "); Serial.println(mqttPublishInterval); //determine if this is normal site, or an access point pinMode(bootModePin, INPUT_PULLUP); IPAddress myIP; int bootMode; bootMode = digitalRead(bootModePin); if (bootMode == LOW) { Serial.println(F("Become an access point")); //Assemble a unique name to use char mac[160]; strcpy(mac, WiFi.macAddress().c_str()); char APName[strlen(ssid)+6]; //allow enough space to hold the root name plus 4 chars of the MAC and the terminating null strcpy(APName, hostName); strncat(APName,mac+12,2); //make name unique by appending part of the MAC address strncat(APName,mac+15,2); //make name unique by appending part of the MAC address Serial.print("Access Point Host Name: "); Serial.println(APName); WiFi.softAP(APName); myIP = WiFi.softAPIP(); } else { Serial.println(F("Normal mode")); wifiClient.setInsecure(); WiFi.mode(WIFI_STA); WiFi.hostname(hostName); WiFi.begin(ssid, pskPwd); Serial.println(""); // Wait for connection int iCnt = 0; while (WiFi.status() != WL_CONNECTED) { delay(100); digitalWrite(led, HIGH); delay(150); digitalWrite(led, LOW); delay(100); digitalWrite(led, HIGH); delay(150); digitalWrite(led, LOW); Serial.print(F(".")); iCnt++; if (iCnt % 25 == 0) { Serial.println("");} // wrap line every so often if (iCnt % 150 == 0) { doReboot();} // give up and reboot } Serial.println(""); myIP = WiFi.localIP(); Serial.print(F("Wifi connected to ")); Serial.println(ssid); } Serial.print(F("Host name: ")); Serial.println(hostName); Serial.print(F("IP address: ")); Serial.println(myIP); if (MDNS.begin(hostName)) { Serial.println(F("MDNS responder started")); } server.on("/", handleRoot); server.on("/config", handleConfig); server.onNotFound(handleNotFound); server.begin(); Serial.println(F("HTTP server started")); // Start websockets server. SocketServer.listen(WEBSOCKETS_PORT); Serial.print(SocketServer.available() ? F("WebSocket Server Running and Ready on ") : F("WebSocket Server Not Running on ")); Serial.print(F("IP address: ")); Serial.print(WiFi.localIP()); Serial.print(F(", Port: ")); Serial.println(WEBSOCKETS_PORT); // Websockets Server Port Wire.begin(); // for the BME680 bme680Config(); // for the MQTT connection strcpy(mqttTopicRoot, mqttLocation); strcat(mqttTopicRoot, "/"); strcat(mqttTopicRoot, hostName); //asseble the 'path' associated with this host eg: 52Apache/Shed/ strcat(mqttTopicRoot, "/"); mqttClient.setServer(mqttServer, mqttPort); mqttClient.setCallback(mqttCallback); delay(1500); lastMqttReconnectAttempt = 0; // for the light sensor max44009 Serial.print(F("MAX44009 Library version ")); Serial.println(MAX44009_LIB_VERSION); // for the BME280 bme280Thp.setI2CAddress(0x76); if (bme280Thp.beginI2C() == false) //Begin communication over I2C { Serial.println(F("The BME280 sensor did not respond. Please check wiring.")); } else { Serial.print(F("BME280 chip ID: ")); Serial.println(bme280Thp.readRegister(BME280_CHIP_ID_REG)); } } void loop(void) { server.handleClient(); MDNS.update(); SocketListenForClients(); SocketPollClients(); checkMQTT(); checkBME680(); checkMax44009(); checkBME280(); websocketSend(); }