295 lines
7.9 KiB
C++
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();
|
|
}
|