Files
Ardiuno/WS3/WS3-ESP32/WS3-ESP32.ino
2021-01-30 10:50:07 +01:00

295 lines
7.9 KiB
C++

// Weather Station 3 from AliExpress / TaoBao
//
// Code for ESP32-PoE-ISO
//
// Notice : this will use ONLY Ethernet card. With DHCP.
//
// 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 <FS.h>
#include <DNSServer.h>
#ifndef ESP32
#include <ESP8266mDNS.h>
#endif
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
//#include <AddrList.h>
// EspSoftwareSerial (or plain SoftwareSerial)
#ifndef ESP32
#include <SoftwareSerial.h>
#endif
// MQTT
#include <PubSubClient.h>
// WiFiManager
#include <WiFiManager.h>
#include <ArduinoJson.h> // Version 5 ONLY (TODO upgrade this)
#ifdef ESP32
#include <SPIFFS.h>
#endif /* ESP32 */
#include "ws3_defs.h"
#include "ws3_vars.h"
#ifndef ESP32
SoftwareSerial WS3(RXD2,TXD2);
#else
#define WS3 Serial2
#endif
WiFiClient espClient;
PubSubClient client(espClient);
// ws3_wifi.ino
void setupSpiffs();
void saveConfigCallback();
// ws3_ota.ino
void setup_ota();
// Setup the stuff.
void setup() {
#ifndef ESP32
Serial.begin(57600);
#else
Serial.begin(115200);
#endif
while (!Serial) ; // wait for Arduino Serial Monitor to open
Serial.println("\n\n");
setupSpiffs();
Serial.println("Weather Station 3 Adapter by Kiwi");
#ifndef ESP32
Serial.println(ESP.getFullVersion());
#endif
WiFiManager wm;
wm.setSaveConfigCallback(saveConfigCallback);
// Setup custom parameters
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_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(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["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();
#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
#ifndef ESP32
WS3.begin(WS3_BAUD);
#else
// We use Serial2 on ESP32 (hardware)
WS3.begin(WS3_BAUD, SERIAL_8N1, RXD2, TXD2);
while (!WS3) ; // wait for Arduino Serial Monitor to open
#endif
debugln("WS3 UART is ready...");
// Allocate memory for packet
pkt_str.reserve(WS3_PKT_LEN);
debugln(" -> Packet memory allocated!");
client.setServer(mqtt_server, mqtt_port);
//client.setCallback(callback);
debugln("MQTT started");
}
// 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
MQPayload = "{ \"idx\" : "+ String(idx_rain) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->rain_1h*10) + ";" + String(p->rain_1m*0.1) + "\"}";
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);
}
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();
}