#define FIRMWARE_VSERION "1.0.0"
#define HW_ID "Luisenhof HaMo v1"
#define OTA_UPDATE_PW "&AEbe@xwp&SjiaFhdEWP4Fw3e#"
#define WOKWI true
#define PIN_NEO_PIXEL 33
#define NUM_PIXELS 5
#define LED_LW_KESSEL_ID 0
#define LED_NV_KESSEL_ID 1
#define LED_NV_SPEISEWASSER_ID 2
#define LED_SAMMELSTOERUNG_ID 3
#define LED_STATUS_ID 4
#define MODBUS_INTERVAL 2000
#define BLINK_INTERVAL 200
#define WEBSERVER_PORT 80
#define HAGELSCHUER_PORT 502
#include <Arduino.h>
#if WOKWI==false
#include <ETH.h>
#endif
#include <Adafruit_NeoPixel.h>
#include <ReactESP.h>
#include <AsyncTCP.h>
#include <ModbusClientTCPasync.h>
#include <ESPAsyncWebSrv.h>
#include <ElegantOTAPro.h>
#include <HTTPClient.h>
using namespace reactesp;
ReactESP app;
static bool eth_connected = false;
static bool status_led_state = false;
unsigned long time_call_modbus = 0;
unsigned long time_blink_led = 0;
unsigned long ota_progress_millis = 0;
uint32_t status_led_color = 0;
AsyncWebServer server(WEBSERVER_PORT);
IPAddress hagelschuer_ip(192,168,14,2);
ModbusClientTCPasync hagelschuer_client(hagelschuer_ip, HAGELSCHUER_PORT, 100);
Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800);
uint16_t getInt(uint8_t highbyte, uint8_t lowbyte)
{
return (highbyte << 8) + lowbyte;
}
void handleData(ModbusMessage response, uint32_t token)
{
// Bit 0 -> Server ID
// Bit 1 -> Funktion Code
// Bit 2 -> Datenbits-Länge
float v;
//Register 0 (Adresse 1) - Leitwert Kessel - Bit 3 & 4
v = getInt(response[3], response[4]) / 10;
Serial.printf("Leitwert Kessel : %4.1f μS/cm²",v);
if (v > 5000) {
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(255, 0, 0));
} else {
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(0, 0, 0));
}
Serial.println("");
//Register 1 (Adresse 2) - Wasserstand Kessel - Bit 5 & 6
v = getInt(response[5], response[6]) / 10;
Serial.printf("Niveau Kessel : %3.1f %%",v);
if (v < 40) {
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(255, 0, 0));
} else {
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(0, 0, 0));
}
Serial.println("");
//Register 2 (Adresse 3) - Wasserstand Speisekessel - Bit 7 & 8
v = getInt(response[7], response[8]) / 10;
Serial.printf("Niveau Speisewasser: %3.1f %%",v);
if (v < 35) {
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(255, 255, 0));
} else {
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(0, 0, 0));
}
Serial.println("");
NeoPixel.show();
Serial.println("");
}
void handleError(Error error, uint32_t token)
{
ModbusError me(error);
Serial.printf("Error response: %02X - %s token: %d\n", (int)me, (const char *)me, token);
}
void WiFiEvent(WiFiEvent_t event)
{
switch (event) {
#if WOKWI==false
case ARDUINO_EVENT_ETH_START:
Serial.println("ETH Started");
ETH.setHostname("esp32-ethernet");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
Serial.print("ETH MAC: ");
Serial.print(ETH.macAddress());
Serial.print(", IPv4: ");
Serial.print(ETH.localIP());
if (ETH.fullDuplex()) {
Serial.print(", FULL_DUPLEX");
}
Serial.print(", ");
Serial.print(ETH.linkSpeed());
Serial.println("Mbps");
eth_connected = true;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
#else
case ARDUINO_EVENT_WIFI_STA_START:
Serial.println("WIFI Started");
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
Serial.println("WIFI Connected");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
Serial.print("WIFI MAC: ");
Serial.print(WiFi.macAddress());
Serial.print(", IPv4: ");
Serial.print(WiFi.localIP());
eth_connected = true;
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
Serial.println("WIFI Disconnected");
eth_connected = false;
break;
case ARDUINO_EVENT_WIFI_STA_STOP:
Serial.println("WIFI Stopped");
eth_connected = false;
break;
#endif
default:
break;
}
}
void onOTAStart()
{
Serial.println("OTA update started!");
}
void onOTAProgress(size_t current, size_t final)
{
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
}
}
void onOTAEnd(bool success)
{
if (success) {
Serial.println("OTA update finished successfully!");
} else {
Serial.println("There was an error during OTA update!");
}
}
void init_leds()
{
NeoPixel.begin();
NeoPixel.setPixelColor(LED_STATUS_ID, NeoPixel.Color(255, 0, 0));
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(255, 0, 0));
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(255, 0, 0));
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(255, 0, 0));
NeoPixel.setPixelColor(LED_SAMMELSTOERUNG_ID, NeoPixel.Color(255, 0, 0));
NeoPixel.show();
delay(250);
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(0, 255, 0));
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(0, 255, 0));
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(0, 255, 0));
NeoPixel.setPixelColor(LED_SAMMELSTOERUNG_ID, NeoPixel.Color(0, 255, 0));
NeoPixel.show();
delay(250);
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(0, 0, 255));
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(0, 0, 255));
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(0, 0, 255));
NeoPixel.setPixelColor(LED_SAMMELSTOERUNG_ID, NeoPixel.Color(0, 0, 255));
NeoPixel.show();
delay(250);
NeoPixel.setPixelColor(LED_LW_KESSEL_ID, NeoPixel.Color(0, 0, 0));
NeoPixel.setPixelColor(LED_NV_KESSEL_ID, NeoPixel.Color(0, 0, 0));
NeoPixel.setPixelColor(LED_NV_SPEISEWASSER_ID, NeoPixel.Color(0, 0, 0));
NeoPixel.setPixelColor(LED_SAMMELSTOERUNG_ID, NeoPixel.Color(0, 0, 0));
NeoPixel.show();
}
void call_modbus()
{
Error err;
if (eth_connected) {
status_led_color = NeoPixel.Color(0, 255, 0);
err = hagelschuer_client.addRequest(millis(), 1, READ_INPUT_REGISTER, 0, 3);
if (err != SUCCESS) {
status_led_color = NeoPixel.Color(0, 0, 255);
ModbusError e(err);
Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e);
}
} else {
status_led_color = NeoPixel.Color(255, 255, 0);
}
}
void blink_led()
{
uint32_t led_color;
if (status_led_state) {
led_color = status_led_color;
} else {
led_color = 0;
}
status_led_state = !status_led_state;
NeoPixel.setPixelColor(LED_STATUS_ID, led_color);
NeoPixel.show();
}
void setup()
{
WiFi.onEvent(WiFiEvent);
hagelschuer_client.onDataHandler(&handleData);
hagelschuer_client.onErrorHandler(&handleError);
ElegantOTA.onStart(onOTAStart);
ElegantOTA.onProgress(onOTAProgress);
ElegantOTA.onEnd(onOTAEnd);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hi! I am Luisenhof HaMo.");
});
Serial.begin(115200);
Serial.println("Luisenof HaMo initializing....");
Serial.print("LED initializing... ");
init_leds();
#if WOKWI==false
Serial.print("Ethernet initializing... ");
ETH.begin();
#else
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
#endif
hagelschuer_client.setTimeout(10000);
hagelschuer_client.setIdleTimeout(60000);
ElegantOTA.setAuth("update", OTA_UPDATE_PW);
ElegantOTA.setFilesystemMode(false);
ElegantOTA.setTitle("OTA Update - Luisenhof HaMo");
ElegantOTA.setID(HW_ID);
ElegantOTA.setFWVersion(FIRMWARE_VSERION);
Serial.println("Webservices start");
ElegantOTA.begin(&server);
server.begin();
Serial.println("Program start");
app.onRepeat(BLINK_INTERVAL, blink_led);
app.onRepeat(MODBUS_INTERVAL, call_modbus);
}
void loop()
{
app.tick();
ElegantOTA.loop();
}