// Weather Station 3 from AliExpress / TaoBao // // Highly inspired by https://github.com/kquinsland/ws3-to-esphome-bridge/ // // Maybe the code is not optimal but it works. // // Configuration of WS3 : // // JP1 toggle betwen ARS inch mode and professional metric mode // short : inch // open : metric // // JP2 baud rate // short : 2400 // open : 9600 // // Configuration used for this code : // JP1 : open // JP2 : closed // // Data is in the form : // // A4095B000C0000D0000E0000F0000G0000H0000I0000J0000K0000L0218M515N09654O.....*52 // #include //#include // Needed? #include #include #include #include //#include // EspSoftwareSerial (or plain SoftwareSerial) #include // MQTT #include // WiFiManager #include #include // Version 5 ONLY (TODO upgrade this) #ifdef ESP32 #iclude #endif /* ESP32 */ // Domoticz MQTT configuration by WiFiManager //IPAddress MQipAddr; // MQTT IP address from WiFiManager const char *mqtt_server = "portbuild.home.oav.net"; #define mqtt_port 1883 // configuration values char cfg_mqtt_server[40]; char idx_windir[4]="48"; char idx_temp[4] ="52"; char idx_rain[4] ="51"; // To handle Gust volatile unsigned int loopcount=0; // number of loops (300 = 10 minutes, since every Serial loops is 2s) volatile unsigned int WindGust=0; // wind gust data values to keep track of the strongest gust in the last 10 minutes // Definitions const char* Mqtt_clientid = "ESP-Weather-Station"; const char* dom_in = "domoticz/in"; #define MQTT_MAX_PACKET_SIZE 128 char msgToPublish[MQTT_MAX_PACKET_SIZE + 1]; // Defines #define OTAPASSWORD "123" // Password for OTA upload thru Arduino IDE // Debug #define DEBUG 1 #ifdef DEBUG #define debug(x) Serial.print(x) #define debugln(x) Serial.println(x) #else /* DEBUG */ #define debug(x) #define debugln(x) #endif /* DEBUG */ // Baud used to read see JP2 #define WS3_BAUD 2400 // Toggle support for PM2.5 sensor //#define SUPPORT_PM25_SENSOR // Define the length of data #ifdef SUPPORT_PM25_SENSOR // There is 88 bytes per packets #define WS3_PKT_LEN 78 // And the checksum is the last 2 bytes #define WS3_CHK_LEN 2 #define CHK_SUM_DELINEATOR 75 #else /* SUPPORT_PM25_SENSOR */ // There is 88 bytes per packets #define WS3_PKT_LEN 78 // And the checksum is the last 2 bytes #define WS3_CHK_LEN 2 #define CHK_SUM_DELINEATOR 75 #endif /* SUPPRT_PM25_SENSOR */ // Seems the Metric format does not have a correct checksum // In this case we should not test the checksum, just see // if we have a correct dataline #define DONT_CHKSUM 1 // Define ALTITUDE if you need to get pression corrected // Set this to 0 if you don't care #define ALTITUDE 340 // Longwy is at 340m // Place holder for the packet received String pkt_str = ""; // Flag for packet OK volatile byte pkt_ok = false; // Flag for saving data bool shouldSaveConfig = false; SoftwareSerial WS3(15,16); WiFiClient espClient; PubSubClient client(espClient); // After parsing the string of bytes, we'll have an easier to use struct // TODO: this should be it's own file? struct WS3Packet { // The 1st field is "A0000" - Wind direction AD value in real time (0-4096) unsigned int wind_dir; // The 2nd field is "B000" - Wind direct angle value (16 direction) unsigned int wind_angle; // new // The 3rd field is "C0000" - Real time wind speed frequency 1Hz unsigned int wind_freq; // New // The 4th field is "D0000" - Real time wind speed, unit: 0.1 m/s unsigned int wind_speed; // The 5th field is "E0000" - Avg wind speed in the previous minute, unit: 0.1m/s unsigned int wind_speed_1m; // The 6th field is "F0000" - the highest wind speed in the last 5 minutes, unit: 0.1m/s unsigned int wind_speed_5m; // The 7th field is "G0000" - Real time rain bucket (0-9999), loop-count int rain_bucket_cnt; // New // The 8th field is "H0000" - Number of rain bucket in the last minute, (0-9999) int rt_rain_bucket; // The 9th field is "I0000" - Rain fall in 1 minute, unit: 0.1mm float rain_1m; // The 10th field is "J0000" - the previous hour's rainfall ( 0.1 mm) float rain_1h; // The 11th field is "K0000" - rainfall during the first 24 hours ( 0.1 mm) float rain_24h; // The 12th field is "L0000" - temperature, unit: degree C (unit 0.1 Degree) float temp_f; // The 13th field is "M000" - humidity ( 00 % - 99 %), unit 0.1% float humidity; // The 14th field is "M10020" - air pressure ( 0.1 hpa ) float air_pressure; }; // Return the index according to Wind Angle String str_windir(unsigned int WinVal){ if(WinVal >= 360) return "N"; //N if(WinVal >= 0 && WinVal < 22) return "N"; //N if(WinVal >= 22 && WinVal < 45) return "NNE"; //NNE if(WinVal >= 45 && WinVal < 67) return "NE"; //NE if(WinVal >= 67 && WinVal < 90) return "ENE"; //ENE if(WinVal >= 90 && WinVal < 112) return "E"; //E if(WinVal >= 112 && WinVal < 135) return "ESE"; //ESE if(WinVal >= 135 && WinVal < 157) return "SE"; //SE if(WinVal >= 157 && WinVal < 180) return "S"; //S if(WinVal >= 180 && WinVal < 202) return "S"; //S if(WinVal >= 202 && WinVal < 225) return "SSW"; //SSW if(WinVal >= 225 && WinVal < 247) return "SW"; //SW if(WinVal >= 247 && WinVal < 270) return "WSW"; //WSW if(WinVal >= 270 && WinVal < 292) return "W"; //W if(WinVal >= 292 && WinVal < 315) return "WNW"; //WNW if(WinVal >= 315 && WinVal < 337) return "NW"; //NW if(WinVal >= 337 && WinVal < 359) return "NNW"; //NNW } // Setup OTA stuff void setup_ota() { //Port defaults to 8266 //ArduinoOTA.setPort(8266); // Hostname defaults to projet name ArduinoOTA.setHostname(Mqtt_clientid); // No auth per default // XXX: Change this ArduinoOTA.setPassword((const char *)OTAPASSWORD); ArduinoOTA.onStart([]() { Serial.println("OTA Update is Starting !"); }); ArduinoOTA.onEnd([]() { Serial.println("\nOTA is done. Rebooting..."); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("OTA Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); ArduinoOTA.begin(); } // Setup the stuff. void setup() { Serial.begin(57600); while (!Serial) ; // wait for Arduino Serial Monitor to open Serial.println("\n\n"); setupSpiffs(); Serial.println("Weather Station 3 Adapter by Kiwi"); Serial.println(ESP.getFullVersion()); WiFiManager wm; wm.setSaveConfigCallback(saveConfigCallback); // Setup custom parameters //WiFiManagerParameter custom_mqtt_server("server", "Domoticz MQTT server hostname", cfg_mqtt_server, 40); WiFiManagerParameter custom_idx_windir ("idx1", "Domoticz Index for Wind Virtual Module", idx_windir, 4); WiFiManagerParameter custom_idx_temp ("idx2", "Domoticz Index for Temp/Hum/Baro Virtual Module", idx_temp, 4); WiFiManagerParameter custom_idx_rain ("idx3", "Domoticz Index for Rain Virtual Module", idx_rain, 4); // Add all parameters //wm.addParameter(&custom_mqtt_server); wm.addParameter(&custom_idx_windir); wm.addParameter(&custom_idx_temp); wm.addParameter(&custom_idx_rain); // Automatic connect using saved stuff otherwise start as an AP to configure it if (!wm.autoConnect("ESP-Weather-Station")) { Serial.println("Failer to connect and hit timeout"); delay(3000); // if we still have not connected restard and try again ESP.restart(); delay(5000); } // Always start configportal for a little while (2 m) wm.setConfigPortalTimeout(30); // wm.startConfigPortal("ESP-Weather-Station","ESP"); wm.startConfigPortal("ESP-Weather-Station"); // If we get here we are connected ! Serial.println("Connected to WiFi !"); // Read the updated parameters //strcpy(cfg_mqtt_server, custom_mqtt_server.getValue()); strcpy(idx_windir, custom_idx_windir.getValue()); strcpy(idx_temp, custom_idx_temp.getValue()); strcpy(idx_rain, custom_idx_rain.getValue()); // Save the custom parameters to FS if (shouldSaveConfig) { Serial.println("Saving config to FS..."); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); //json["mqtt_server"] = cfg_mqtt_server; json["idx_windir"] = idx_windir; json["idx_temp"] = idx_temp; json["idx_rain"] = idx_rain; File configFile = SPIFFS.open("/config.json", "w"); if (!configFile) { Serial.println("failed to open config file for writing"); } json.prettyPrintTo(Serial); json.printTo(configFile); configFile.close(); //end save shouldSaveConfig = false; } // Launch OTA stuff setup_ota(); //Serial.println(""); //Serial.println(F("WiFi Connected !")); #if LWIP_IPV6 Serial.printf("IPV6 is enabled\n"); #else Serial.printf("IPV6 is not enabled\n"); #endif Serial.print("My IP address: "); Serial.print(WiFi.localIP()); Serial.print("/"); Serial.print(WiFi.subnetMask()); Serial.print(" GW:"); Serial.println(WiFi.gatewayIP()); // Start the Software Serial for WS3 WS3.begin(WS3_BAUD); debugln("WS3 UART is ready..."); // Allocate memory for packet pkt_str.reserve(WS3_PKT_LEN); debugln(" -> Packet memory allocated!"); // Now connect to MQTT //if(WiFi.hostByName(cfg_mqtt_server, MQipAddr) != 1) // Serial.println("ERROR: Unable to resolv MQTT Hostname"); //else // Serial.printf("INFO: Hostname %s resolved to %s\n",cfg_mqtt_server, MQipAddr.toString().c_str()); client.setServer(mqtt_server, mqtt_port); //client.setServer(MQipAddr, mqtt_port); //client.setCallback(callback); debugln("MQTT started"); } #ifdef DONT_CHKSUM // Validate packet using the checksum. // Work only APRS data on this 51W3 board. // Maybe the code on the board does not make the correct checksum ? bool validate_packet(String pay, unsigned long chk) { // Print the payload and the checksum we want debugln("validate_packet:"); debug(pay); debug(" * "); debugln(chk); // TEST DATA (actual packets) // c000s000g000t075r000p019h43b09940*32 // String pay = "c000s000g000t075r000p019h43b09940"; // byte chk = 0x32; // this will be in HEX) // c000s000g000t075r000p019h42b09940*33 // String pay = "c000s000g000t075r000p019h42b09940"; // byte chk = 0x33; // this will be in HEX) // c000s000g000t075r000p019h42b09939*3D // String pay = "c000s000g000t075r000p019h42b09939"; // byte chk = 0x3D; // this will be in HEX) // SUPER grateful for the helpful https://toolslick.com/math/bitwise/xor-calculator to validate my // code! // Current byte byte i1=0; // the intermediate checksum byte tmp = 0; // starting from the second character, we begin XORing for (int x = 0; x < pay.length() ; x++) { i1=pay[x]; // Do the xOR tmp = tmp^i1; } // do the check if(tmp == chk){ return true; } else { debugln("INVALID!"); debug("calculated:"); debugln(tmp); return false; } } #endif /* DONT_CHKSUM */ // Parse the packet and fill the structure with data void parse_packet(String payload, WS3Packet* p) { // E.G.: A4095 B000 C0000 D0000 E0000 F0000 G0000 H0000 I0000 J0000 K0000 L0237 M502 N09810 O..... // Parse in order, starting with A0000 (wind dir real time, 0-4096) int wind_dir_idx = payload.indexOf('A'); p->wind_dir = payload.substring(wind_dir_idx+1, wind_dir_idx+5).toInt(); // Then move on to B000 - wind direction angle (16 direction) int wind_angle_idx = payload.indexOf('B'); p->wind_angle = payload.substring(wind_angle_idx+1, wind_angle_idx+4).toInt(); // Then move on to C0000 - wind speed frequency (1 Hz) int wind_freq_idx = payload.indexOf('C'); p->wind_freq = payload.substring(wind_freq_idx+1, wind_freq_idx+5).toInt(); // Then move on to D0000 - wind speed real time (unit 0.1 m/s) int wind_speed_idx = payload.indexOf('D'); p->wind_speed = payload.substring(wind_speed_idx+1, wind_speed_idx+5).toInt(); // Then move on to E0000 - wind speed avg in the last minute (unit 0.1 m/s) int wind_speed_1m_idx = payload.indexOf('D'); p->wind_speed_1m = payload.substring(wind_speed_1m_idx+1, wind_speed_1m_idx+5).toInt(); // Then move on to F0000 - wind speed over the last 5 min int wind_speed_5_idx = payload.indexOf('F'); p->wind_speed_5m = payload.substring(wind_speed_5_idx+1, wind_speed_5_idx+5).toInt(); // Then move on to G0000 - Rain in Realtime (0-9999 counter) bucket int rain_bucket_cnt_idx = payload.indexOf('G'); p->rain_bucket_cnt = payload.substring(rain_bucket_cnt_idx+1, rain_bucket_cnt_idx+5).toInt(); // Then move on to H0000 - Rain bucket in the last 1 minute (0-9999 counter) int rt_rain_bucket_idx = payload.indexOf('H'); p->rt_rain_bucket = payload.substring(rt_rain_bucket_idx+1, rt_rain_bucket_idx+5).toInt(); // Then move on to I0000 - rain last minute (0.1mm) int rain1m_idx = payload.indexOf('I'); p->rain_1m = payload.substring(rain1m_idx+1, rain1m_idx+5).toInt() *.1; // Then move on to J0000 - rain last hour (0.1mm) int rain1h_idx = payload.indexOf('J'); p->rain_1h = payload.substring(rain1h_idx+1, rain1h_idx+5).toInt() *.1; // Then move on to K0000 - rain last 24h (0.1mm) int rain24h_idx = payload.indexOf('K'); p->rain_24h = payload.substring(rain24h_idx+1, rain24h_idx+5).toInt()*.1; // Then move on to L0200 - temp (0.1°C) // XXX: Check with minus zero temperatures int temp_idx = payload.indexOf('L'); p->temp_f = payload.substring(temp_idx+1, temp_idx+5).toInt()*.1; // Then move on to M611 - Humidity int humidity_idx = payload.indexOf('M'); // p->humidity = payload.substring(humidity_idx+1, humidity_idx+3).toInt()*.1; p->humidity = payload.substring(humidity_idx+1, humidity_idx+3).toInt(); // Then move on to N10020 - air pressure int pressure_idx = payload.indexOf('N'); p->air_pressure = payload.substring(pressure_idx+1, pressure_idx+6).toInt()*.1; // Handle Gust if(p->wind_speed > WindGust) { WindGust = p->wind_speed; debugln("Update WindGust"); } if(loopcount >= 300) { loopcount = 0; WindGust = p->wind_speed; debugln("10 min expired -> reset counter"); } // Increment loopcount for Gust loopcount++; } // Clear the packet before working on the next void clear_pkt(WS3Packet* p) { p->wind_dir = 0; p->wind_angle = 0; p->wind_freq = 0; p->wind_speed = 0; p->wind_speed_1m = 0; p->wind_speed_5m = 0; p->rain_bucket_cnt = 0; p->rt_rain_bucket = 0; p->rain_1m = 0; p->rain_1h = 0; p->rain_24h = 0; p->temp_f = 0; p->humidity = 0; p->air_pressure = 0; } #ifdef DEBUG // Print the data void print_weather(WS3Packet* p){ Serial.print("Wind Direction (realtime): "); Serial.println(p->wind_dir, DEC); //Serial.println(" degrees"); Serial.print("Wind direction angle : "); Serial.print(p->wind_angle, DEC); Serial.print(" degree "); Serial.println(str_windir(p->wind_angle)); Serial.print("Wind speed Frequency: "); Serial.print(p->wind_freq, DEC); Serial.println(" Hz"); Serial.print("Wind speed: "); Serial.print(p->wind_speed/10, DEC); Serial.println(" m/s"); Serial.print("Wind speed 1m: "); Serial.print(p->wind_speed_1m/10, DEC); Serial.println(" m/s"); Serial.print("Wind speed 5m: "); Serial.print(p->wind_speed_5m/10, DEC); Serial.println(" m/s"); Serial.print("temp_f: "); Serial.print(p->temp_f, DEC); Serial.println(" deg. C."); Serial.print("Rain buckets / buckets 1m: "); Serial.print(p->rain_bucket_cnt, DEC); Serial.print(" / "); Serial.println(p->rt_rain_bucket, DEC); Serial.print("Rain 1m / 1H / 24H: "); Serial.print(p->rain_1m, DEC); Serial.print(" / "); Serial.print(p->rain_1h, DEC); Serial.print(" / "); Serial.print(p->rain_24h, DEC); Serial.println(" mm"); Serial.print("humidity: "); Serial.print(p->humidity, DEC); Serial.println(" %"); Serial.print("air_pressure: "); Serial.print(p->air_pressure, DEC); Serial.println(" hpa"); } #endif /* DEBUG */ // Print the data over MQTT void push_weather(WS3Packet* p) { String MQPayload; byte humidity_status = 0; // Domoticz humdity status byte bar_forecast = 0; // Domoticz baro forcast float pression = 0.0; // Pression float wct = 0.0; // Windchill temperature // Rain is mm x 100 -> Have to compute this. MQPayload = "{ \"idx\" : "+ String(idx_rain) +",\"nvalue\" : 0, \"svalue\" :\"" + String(p->rain_1h) + ";" + String(p->rain_1m) + "\"}"; sendMQTTPayload(MQPayload); // Temperature / Humidity / Baro // Humidity stuff // O = Normal // 1 = Confortable // 2 = Dry (<35%) // 3 = Wet (>70%) if (p->humidity < 35) { humidity_status = 2; // Dry } else if (p->humidity >= 70) { humidity_status = 3; // Wet } else { if ((p->humidity >=35) && (p->humidity <50)) { humidity_status = 1; // Confortable } else { // >=50 -> 70% humidity_status = 0; // Normal } } // Correct the pression according to altitude pression = p->air_pressure + (ALTITUDE / 8.3); // Pression should be adapted as it should be on sea level // Prediction // 0 : No info // 1 : Sunny // 2 : Partly cloudy // 3 : Cloudy // 4 : Rain // Maybe have to store values for last 3 hours? // XXX: fix value with altitude if (pression < 1000) { bar_forecast = 4; // Rain } else if (pression < 1020) { bar_forecast = 3; // Cloudy } else if (pression < 1030) { bar_forecast = 2; // Partly cloudy } else { bar_forecast = 1; // Sunny } // See : https://github.com/G6EJD/ESP32-Weather-Forecaster MQPayload = "{ \"idx\" : "+ String(idx_temp) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->temp_f) + ";" + String(p->humidity) + ";"+String(humidity_status)+ ";" + String(pression) +";"+String(bar_forecast)+"\"}"; sendMQTTPayload(MQPayload); // Wind // Last temperature is WindChill // Have to compute it https://www.alpiniste.fr/windchill-calculateur/ //wct = p->temp_f; wct = 13.12 + 0.6215*p->temp_f - 11,37*pow((p->wind_speed * 3.6),0.16)+0.3965*p->temp_f*pow((p->wind_speed * 3.6),0.16); MQPayload = "{ \"idx\" : "+ String(idx_windir) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->wind_angle) + ";" + String(str_windir(p->wind_angle)) + ";" + String(p->wind_speed) + ";" + String(WindGust) +";" + String(p->temp_f) + ";"+String(wct)+"\"}"; sendMQTTPayload(MQPayload); } // Processing the packet. bool process_packet(String pkt, WS3Packet* p) { debugln("[D] process_packet - ALive!"); debugln(pkt); // Allocate bytes for the payload String payload; payload.reserve(WS3_PKT_LEN-WS3_CHK_LEN); #ifdef DONT_CHKSUM // everything after the * is checksum (2 char long) unsigned long chksum; #endif /* DONT_CHKSUM */ // Check if the 75rd character is * if (pkt.charAt(CHK_SUM_DELINEATOR) != '*') { debugln("Packed invalid; no * character at position 75!"); return false; #ifdef DONT_CHKSUM } else { // The character indicating the checksum is coming is in the correct place. Yay. // Now, we need to pull the two ascii characters that are transmitted to us // and turn them into a single byte. E.G. Char 3, Char D should convert to 0x3D. // // We can do this with the strtoul() function; we indicate that we wante base 16 chksum = strtoul(pkt.substring(CHK_SUM_DELINEATOR+1, CHK_SUM_DELINEATOR+2).c_str(),NULL,16); } #endif /* DONT_CHKSUM */ // We have the checksum, Now we can bother to get the payload payload = pkt.substring(0, CHK_SUM_DELINEATOR); // And try to validate... #ifndef DONT_CHKSUM if(!validate_packet(payload, chksum)){ debugln("invalid packet! :("); return false; } else { debugln("Valid packet!"); } #endif /* DONT_CHKSUM */ parse_packet(payload, p); return true; } void loop() { // MQTT if (!client.connected()) { reconnect(); } // While data comes in and we don't have a pending packet to process... while (WS3.available() && pkt_ok !=true) { // Pull the bytes off the stream char inChar = (char)WS3.read(); // And build up the packet pkt_str += inChar; // Until we hit the end if (inChar == '\n') { pkt_ok = true; } } // Yay, we now have a packet! // Now, we attempt to parse out the string into a packet that we can work with if (pkt_ok) { debugln("pkt_ok!"); // At this point, we have a string of characters that was probably a valid packet // We set get some memory and attempt to parse the string into the struct WS3Packet p = {}; // Validate the payload, then parse it. if (process_packet(pkt_str, &p)) { // print results if parse OK #ifdef DEBUG print_weather(&p); #endif /* DEBUG */ // Push to MQTT push_weather(&p); debugln("processed"); } else { debugln("unable to parse packet :("); } // clear so we can start again pkt_str = ""; pkt_ok = false; clear_pkt(&p); } ArduinoOTA.handle(); } // MQTT Stuff //void callback(char* topic, byte* payload, unsigned int length) { // debug("Message arrived ["); // Serial.print(topic); // debug("] "); // for (int i = 0; i < length; i++) { // Serial.print((char)payload[i]); // } // debug(" "); //} void reconnect() { // Loop until we're reconnected while (!client.connected()) { debug("Attempting MQTT connection..."); // Attempt to connect if (client.connect(Mqtt_clientid)) { debugln("connected"); // ... and resubscribe client.subscribe(dom_in); } else { debug("failed, rc="); debug(client.state()); debugln(" try again in 5 seconds"); // Wait 5 seconds before retrying for(int i = 0; i<5000; i++){ delay(1); } } } } // Sends MQTT payload to the Mosquitto server running on a Raspberry Pi. // Mosquitto server deliveres data to Domoticz server running on a same Raspberry Pi void sendMQTTPayload(String msgpayload) { // Convert payload to char array msgpayload.toCharArray(msgToPublish, msgpayload.length()+1); //Publish payload to MQTT broker if (client.publish(dom_in, msgToPublish)) { debug("Following data published to MQTT broker: "); debug(dom_in); debug(" "); debugln(msgpayload); } else { debug("Publishing to MQTT broker failed... "); debugln(client.state()); } } // WiFiManager & Autoconfig //callback notifying us of the need to save config void saveConfigCallback () { Serial.println("Should save config"); shouldSaveConfig = true; } void setupSpiffs(){ //clean FS, for testing //SPIFFS.format(); //read configuration from FS json debugln("mounting FS..."); if (SPIFFS.begin()) { debugln("mounted file system"); if (SPIFFS.exists("/config.json")) { //file exists, reading and loading debugln("reading config file"); File configFile = SPIFFS.open("/config.json", "r"); if (configFile) { debugln("opened config file"); size_t size = configFile.size(); // Allocate a buffer to store contents of the file. std::unique_ptr buf(new char[size]); configFile.readBytes(buf.get(), size); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.parseObject(buf.get()); json.printTo(Serial); if (json.success()) { debugln("\nparsed json"); //strcpy(cfg_mqtt_server, json["mqtt_server"]); strcpy(idx_windir, json["idx_windir"]); strcpy(idx_temp, json["idx_temp"]); strcpy(idx_rain, json["idex_rain"]); Serial.printf("Server : %s, idx (wind/temp/rain) : %s/%s/%s\n", cfg_mqtt_server, idx_windir, idx_temp, idx_rain); } else { Serial.println("failed to load json config"); } } } } else { Serial.println("failed to mount FS"); } }