diff --git a/README.md b/README.md index da64514..f9ae98f 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,12 @@ Hinweis: Das RS485 Entwicklungsboard verwendet einen MAX485 Pegelwandler der fü Hier muss 'Bat AutoLimit Grid' auf Y stehen - + ## Webif +### Bild 3: Addition of Homewizard + + diff --git a/SoyoSource.jpg b/SoyoSource.jpg new file mode 100644 index 0000000..479c7e0 Binary files /dev/null and b/SoyoSource.jpg differ diff --git a/src/main.cpp b/src/main.cpp index 4d39742..f919f7f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,1345 +1,1373 @@ -/*************************************************************************** - soyosource-powercontroller @matlen67 - - Version: 1.240508 - - 16.03.2024 -> Speichern der Checkboxzustände: aktiv Timer1 / Timer2 - 03.04.2024 -> Statusübersicht bei geschlossenen details/summary boxen - 14.04.2024 -> Falls Batterieschutz aktiviert, deaktiviere Regelung der Nulleinspeisung - 25.04.2024 -> Leistungspunkt bei Nulleinspeisung festlegen - (Bei mir funktioniert gut Intervall Shelly 1000ms & Intervall Nulleinspeisung 4000ms) - 26.04.2024 -> Auswahl der aktiven Leiter (L1, L2, L3) beim Shelly - 27.04.2024 -> Fehlerbehebung Shelly 3EM, Shelly Plus 1PM mit zugefügt - 28.04.2024 -> Teiler unter 'SoyoSource Output' hinzugefügt, um die Leistung auf mehere Geräte aufzuteilen - 29.04.2024 -> Telnet entfernt - 05.05.2024 -> update ArduinoJson to 7.0.4 - 08.05.2024 -> mqtt topic voltage & soc bearbeitbar - - - ************************* - Wiring - NodeMCU D1 - RS485 RO - NodeMCU D3 - RS485 DE/RE - NodeMCU D4 - RS485 DI - -****************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "html.h" - -#define DEBUG_SERIAL Serial - -#define DEBUG - -#ifdef DEBUG - #define DBG_PRINT(x) DEBUG_SERIAL.print(x) - #define DBG_PRINTLN(x) DEBUG_SERIAL.println(x) -#else - #define DBG_PRINT(x) - #define DBG_PRINTLN(x) -#endif - -//***************************************************************************** -// da Serial.printf(x,x) mit define nicht funktioniert als workaround sprintf -// sprintf(dbgbuffer,"ESP_%02X%02X%02X", mac[3], mac[4], mac[5]); -// DBG_PRINTLN(dbgbuffer); -//***************************************************************************** -char dbgbuffer[128]; - -#define RXPin D1 // Serial Receive pin (D1) -#define TXPin D4 // Serial Transmit pin (D4) - -//RS485 control -#define SERIAL_COMMUNICATION_CONTROL_PIN D3 // Transmission set pin (D3) -#define RS485_TX_PIN_VALUE HIGH -#define RS485_RX_PIN_VALUE LOW - -// time server -#define MY_NTP_SERVER "de.pool.ntp.org" -#define MY_TZ "CET-1CEST,M3.5.0/2,M10.5.0/3" - - -SoftwareSerial RS485Serial(RXPin, TXPin); // RX, TX -WiFiClient espClient; -PubSubClient client(espClient); -AsyncWebServer server(80); -AsyncEventSource events("/events"); -AsyncDNSServer dns; - - -// Uptime Global Variables -Uptime uptime; -uint8_t Uptime_Years = 0U, Uptime_Months = 0U, Uptime_Days = 0U, Uptime_Hours = 0U, Uptime_Minutes = 0U, Uptime_Seconds = 0U; -uint16_t Uptime_TotalDays = 0U; // Total Uptime Days -char uptime_str[37]; - -// Wifi to percent -const int RSSI_MAX =-50; // max strength signal in dBm -const int RSSI_MIN =-100; // min strength signal in dBm - -//Timer -unsigned long timerSoyoSource = 555; -unsigned long lastTimerSoyoSource = 0; - -unsigned long timerUptime = 1000; -unsigned long lastTimerUptime = 0; - -unsigned long meterinterval = 2000; -unsigned long lastMeterinterval = 0; - -unsigned long nullinterval = 5000; -unsigned long lastNullinterval = 0; - - - -//mqtt -char mqtt_server[16] = "192.168.178.10"; -char mqtt_port[5] = "1889"; -char msgData[64]; -String msg = ""; -char mqtt_topic_bat_voltage [48] = "VenusOS/SmartShunt/voltage"; -char mqtt_topic_bat_soc [48] = "VenusOS/SmartShunt/soc"; - -String dataReceived; -int data; -bool isDataReceived = false; -uint8_t byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7; -int byteSend; -int data_array[8]; -int soyo_hello_data[8] = {0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // bit7 org 0x00, CRC 0xFF -int soyo_power_data[8] = {0x24, 0x56, 0x00, 0x21, 0x00, 0x00, 0x80, 0x08}; // 0 Watt -int soyo_text_data[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - -char buffer[8]; -int old_soyo_power = 0; -int soyo_power = 0; -int new_soyo_power = 0; -int teiler_output = 1; - -unsigned char mac[6]; -char mqtt_root[32] = "SoyoSource/"; -char clientId[16]; -char topic_power[40]; -char soyo_text[40]; - -float mqtt_bat_soc = 0.0; -float mqtt_bat_voltage = 0.0; - -long rssi; - -time_t now; -tm timeInfo; - - -// timer -char currentTime[20]; -char timer1_time[6] = "06:00"; -char timer2_time[6] = "20:00"; -char meteripaddr[16] = ""; - -int timer1_watt = 0; -int timer2_watt = 0; -int maxwatt = 0; - -//state checkboxes -bool checkbox_timer1 = false; -bool checkbox_timer2 = false; -bool checkbox_mqttenabled = false; -bool checkbox_nulleinspeisung = false; -bool checkbox_batschutz = false; -bool checkbox_meter_l1 = true; -bool checkbox_meter_l2 = true; -bool checkbox_meter_l3 = true; - -char metername[24] = "Meter"; -char mqtt_state[20] = "disabled"; - -// variablen Shelly 3em -const int shelly_3em_pro = 1; // ip/rpc/Shelly.GetStatus -const int shelly_plus_1pm = 2; // ip/rpc/Shelly.GetStatus - -const int shelly_3em = 10; // ip/status -const int shelly_em = 11; // ip/status -const int shelly_1pm = 12; // ip/status - - -String shelly_ip = ""; -int shelly_model = 0 ; - -//nulleinspeisung -int nulloffset = 0; -int meter_power = 0; -int meterpower = 0; -int meterl1 = 0; -int meterl2 = 0; -int meterl3 = 0; - -//batterieüberwachung -int batsocstop = 15; -int batsocstart = 50; -bool output_enabled = true; - - -bool new_connect = true; - -const char* PARAM_MESSAGE = "message"; - -//flag for saving data -bool shouldSaveConfig = false; - - -//callback notifying us of the need to save config -void saveConfigCallback () { - DBG_PRINTLN("Should save config"); - shouldSaveConfig = true; -} - - -void notFound(AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); -} - - -int dBmtoPercent(int dBm){ - int percent; - if(dBm <= RSSI_MIN){ - percent = 0; - } else if(dBm >= RSSI_MAX) { - percent = 100; - } else { - percent = 2 * (dBm + 100); - } - - return percent; -} - - -void myUptime(){ - uptime.calculateUptime(); - - // Get The Uptime Values To Global Variables - Uptime_Years = uptime.getYears(); - Uptime_Months = uptime.getMonths(); - Uptime_Days = uptime.getDays(); - Uptime_Hours = uptime.getHours(); - Uptime_Minutes = uptime.getMinutes(); - Uptime_Seconds = uptime.getSeconds(); - Uptime_TotalDays = uptime.getTotalDays(); - - if (Uptime_Years == 0U) { // Uptime Is Less Than One Year - // First 60 Seconds - if (Uptime_Minutes == 0U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) - sprintf(uptime_str, "00:00:%02i", Uptime_Seconds); - // First Minute - else if (Uptime_Minutes == 1U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) - sprintf(uptime_str, "00:%02i:%02i", Uptime_Minutes, Uptime_Seconds); - // Second Minute And More But Less Than Hours, Days, Months - else if (Uptime_Minutes >= 2U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) - sprintf(uptime_str, "00:%02i:%02i", Uptime_Minutes, Uptime_Seconds); - // First Hour And More But Less Than Days, Months - else if (Uptime_Hours >= 1U && Uptime_Days == 0U && Uptime_Months == 0U) - sprintf(uptime_str, "%02i:%02i:%02i", Uptime_Hours, Uptime_Minutes, Uptime_Seconds); - // First Day And Less Than Month - else if (Uptime_Days == 1U && Uptime_Months == 0U) - sprintf(uptime_str, "%iday %02i:%02i:%02i", Uptime_Days, Uptime_Hours, Uptime_Minutes, Uptime_Seconds); - // Second Day And More But Less Than Month - else if (Uptime_Days >= 2U && Uptime_Months == 0U) - sprintf(uptime_str, "%idays %02i:%02i:%02i", Uptime_Days, Uptime_Hours, Uptime_Minutes, Uptime_Seconds); - // First Month And More But Less Than One Year - else if (Uptime_Months >= 1U) - sprintf(uptime_str, "%im, %id %02i:%02i", Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); - // If There Is Any Error In This If Loop Then Make Full String. - else sprintf(uptime_str, "%iy %im %id %02i:%02i", Uptime_Years, Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); - } else // Uptime Is More Than One Year - sprintf(uptime_str, "%iy %im %id %02i:%02i", Uptime_Years, Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); -} - - -//callback from mqtt -void mqtt_callback(char* topic, byte* payload, unsigned int length) { - unsigned int i = 0; - - for (i=0;i= 0 && arrived_value_i <= 3000) { - soyo_power = arrived_value_i; - } - } - - if(strcmp(topic, mqtt_topic_bat_soc) == 0){ - float arrived_value_f = atof(buffer); - mqtt_bat_soc = arrived_value_f; - } - - if(strcmp(topic, mqtt_topic_bat_voltage) == 0){ - float arrived_value_f = atof(buffer); - mqtt_bat_voltage = arrived_value_f; - } -} - - -String processor(const String& var){ - return String(); -} - - -void reconnect() { - DBG_PRINTLN("reconnect MQTT connection!"); - - //set callback again - client.setCallback(mqtt_callback); - - uint8_t timeout = 15; - - // wait for connection - while (!client.connected()){ - - DBG_PRINTLN(""); - - if (client.connect(clientId)) { - DBG_PRINTLN("connection established"); - - client.publish(topic_power, "0"); - client.subscribe(topic_power); - client.subscribe(mqtt_topic_bat_soc); - client.subscribe(mqtt_topic_bat_voltage); - - strcpy(mqtt_state, "connect"); - - DBG_PRINT("subscrible: "); - DBG_PRINT(topic_power); - DBG_PRINTLN(""); - - DBG_PRINT("subscrible: "); - DBG_PRINT(mqtt_topic_bat_soc); - DBG_PRINTLN(""); - - DBG_PRINT("subscrible: "); - DBG_PRINT(mqtt_topic_bat_voltage); - DBG_PRINTLN(""); - - } else { - DBG_PRINTLN("reconnect failed! "); - strcpy(mqtt_state, "connect error"); - - while (timeout){ - DBG_PRINT("."); - timeout--; - delay(1000); - } - } - } - -} - - -int calc_checksumme(int b1, int b2, int b3, int b4, int b5, int b6 ){ - int calc = (0xFF - b1 - b2 - b3 - b4 - b5 - b6) % 256; - return calc & 0xFF; -} - - -void sendSoyoPowerData(int power){ - soyo_power_data[0] = 0x24; - soyo_power_data[1] = 0x56; - soyo_power_data[2] = 0x00; - soyo_power_data[3] = 0x21; - soyo_power_data[4] = power >> 0x08; - soyo_power_data[5] = power & 0xFF; - soyo_power_data[6] = 0x80; - soyo_power_data[7] = calc_checksumme(soyo_power_data[1], soyo_power_data[2], soyo_power_data[3], soyo_power_data[4], soyo_power_data[5], soyo_power_data[6]); - - for(int i=0; i<8; i++) { - RS485Serial.write(soyo_power_data[i]); // send data to RS485 - //DBG_PRINTLN(soyo_power_data[i], HEX); - } -} - -//read config.json -void readConfig(){ - //read configuration from json - DBG_PRINTLN("mounting FS..."); - - if (LittleFS.begin()) { - DBG_PRINTLN("mounted file system"); - if (LittleFS.exists("/config.json")) { - //file exists, reading and loading - DBG_PRINTLN("reading config file"); - File configFile = LittleFS.open("/config.json", "r"); - if (configFile) { - DBG_PRINTLN("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); - - JsonDocument json; - auto deserializeError = deserializeJson(json, buf.get()); - serializeJson(json, Serial); - if (!deserializeError) { - DBG_PRINTLN("\nparsed json"); - strcpy(mqtt_server, json["mqtt_server"]); - strcpy(mqtt_port, json["mqtt_port"]); - - if(json.containsKey("mqtt_bat_vol")){ - strcpy(mqtt_topic_bat_voltage, json["mqtt_bat_vol"]); - } - - if(json.containsKey("mqtt_bat_soc")){ - strcpy(mqtt_topic_bat_soc, json["mqtt_bat_soc"]); - } - - char key_value[2]; - - if(json.containsKey("mqtt_on")){ - strcpy(key_value, json["mqtt_on"]); - if(strcmp(key_value, "1") == 0){ - checkbox_mqttenabled = true; - }else{ - checkbox_mqttenabled = false; - } - } - - if(json.containsKey("zft_on")){ - strcpy(key_value, json["zft_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_nulleinspeisung = true; - }else{ - checkbox_nulleinspeisung = false; - } - } - - if(json.containsKey("batp_on")){ - strcpy(key_value, json["batp_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_batschutz = true; - }else{ - checkbox_batschutz = false; - } - } - - if(json.containsKey("t1_on")){ - strcpy(key_value, json["t1_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_timer1 = true; - }else{ - checkbox_timer1 = false; - } - } - - if(json.containsKey("t2_on")){ - strcpy(key_value, json["t2_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_timer2 = true; - }else{ - checkbox_timer2 = false; - } - } - - if(json.containsKey("mtr_l1_on")){ - strcpy(key_value, json["mtr_l1_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_meter_l1 = true; - }else{ - checkbox_meter_l1 = false; - } - } - - if(json.containsKey("mtr_l2_on")){ - strcpy(key_value, json["mtr_l2_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_meter_l2 = true; - }else{ - checkbox_meter_l2 = false; - } - } - - if(json.containsKey("mtr_l3_on")){ - strcpy(key_value, json["mtr_l3_on"]); - - if(strcmp(key_value, "1") == 0){ - checkbox_meter_l3 = true; - }else{ - checkbox_meter_l3 = false; - } - } - - - if(json.containsKey("t1_t")){ - strcpy(timer1_time, json["t1_t"]); - } - - if(json.containsKey("t2_t")){ - strcpy(timer2_time, json["t2_t"]); - } - - if(json.containsKey("t1_p")){ - timer1_watt = json["t1_p"]; - } - - if(json.containsKey("t2_p")){ - timer2_watt = json["t2_p"]; - } - - if(json.containsKey("mp")){ - maxwatt = json["mp"]; - } - - if(json.containsKey("mtr_ip")){ - strcpy(meteripaddr, json["mtr_ip"]); - shelly_ip = String(meteripaddr); - } - - if(json.containsKey("mtr_iv")){ - meterinterval = json["mtr_iv"]; - } - - if(json.containsKey("z_iv")){ - nullinterval = json["z_iv"]; - } - - if(json.containsKey("z_ofs")){ - nulloffset = json["z_ofs"]; - } - - if(json.containsKey("soc_stop")){ - batsocstop = json["soc_stop"]; - } - - if(json.containsKey("soc_start")){ - batsocstart = json["soc_start"]; - } - - if(json.containsKey("tout")){ - teiler_output = json["tout"]; - } - - } else { - DBG_PRINTLN("failed to load json config"); - } - } - } - } else { - DBG_PRINTLN("failed to mount FS"); - } - //end read config data -} - - -// write config.json -void saveConfig(){ - DBG_PRINTLN(F("save data to config.json")); - JsonDocument json; - - json["mqtt_server"] = mqtt_server; - json["mqtt_port"] = mqtt_port; - json["mqtt_bat_vol"] = mqtt_topic_bat_voltage; - json["mqtt_bat_soc"] = mqtt_topic_bat_soc; - - - if(checkbox_mqttenabled){ - json["mqtt_on"] = "1"; - }else{ - json["mqtt_on"] = "0"; - } - - if(checkbox_nulleinspeisung){ - json["zft_on"] = "1"; - }else{ - json["zft_on"] = "0"; - } - - if(checkbox_batschutz){ - json["batp_on"] = "1"; - }else{ - json["batp_on"] = "0"; - } - - if(checkbox_timer1){ - json["t1_on"] = "1"; - }else{ - json["t1_on"] = "0"; - } - - if(checkbox_timer2){ - json["t2_on"] = "1"; - }else{ - json["t2_on"] = "0"; - } - - if(checkbox_meter_l1){ - json["mtr_l1_on"] = "1"; - }else{ - json["mtr_l1_on"] = "0"; - } - - if(checkbox_meter_l2){ - json["mtr_l2_on"] = "1"; - }else{ - json["mtr_l2_on"] = "0"; - } - - if(checkbox_meter_l3){ - json["mtr_l3_on"] = "1"; - }else{ - json["mtr_l3_on"] = "0"; - } - - json["t1_t"] = timer1_time; - json["t1_p"] = timer1_watt; - json["t2_t"] = timer2_time; - json["t2_p"] = timer2_watt; - json["mp"] = maxwatt; - json["mtr_ip"] = meteripaddr; - json["mtr_iv"] = meterinterval; - json["z_iv"] = nullinterval; - json["z_ofs"] = nulloffset; - json["soc_stop"] = batsocstop; - json["soc_start"] = batsocstart; - json["tout"] = teiler_output; - - File configFile = LittleFS.open("/config.json", "w"); - if (!configFile) { - DBG_PRINTLN("failed to open config file for writing"); - return; - } - - serializeJson(json, configFile); - configFile.close(); - - serializeJson(json, Serial); - DBG_PRINTLN(); -} - - -// get shelly type(3EM PRO, 3EM, EM, 1PM, Plus 1PM) -int getShellyType(){ - String shelly_url = "http://" + shelly_ip + "/shelly"; - int type = 0; - - memset(metername, 0, sizeof(metername)); - strcat(metername, "no device"); - - JsonDocument doc; - - WiFiClient client_shelly; - HTTPClient http; - - if (http.begin(client_shelly, shelly_url)) { - int httpCode = http.GET(); - if (httpCode > 0) { - if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { - String payload = http.getString(); - DeserializationError error = deserializeJson(doc, payload); - - if (error) { - DBG_PRINT(F("deserializeJson() failed: ")); - DBG_PRINTLN(error.f_str()); - } - - String json_type = doc["type"]; - String json_model = doc["model"]; - - if(json_type != NULL){ - - //test auf Shelly 1PM - if(json_type.equals("SHSW-PM")){ - type = shelly_1pm; - memset(metername, 0, sizeof(metername)); - strcat(metername, "Shelly 1PM"); - } - //test auf Shelly EM - if(json_type.equals("SHEM")){ - type = shelly_em; - memset(metername, 0, sizeof(metername)); - strcat(metername, "Shelly EM"); - } - - //test auf Shelly 3EM - if(json_type.equals("SHEM-3")){ - type = shelly_3em; - memset(metername, 0, sizeof(metername)); - strcat(metername, "Shelly 3EM"); - } - } - - - if(json_model != NULL){ - - //test auf Shelly 3EM Pro - if(json_model.equals("SPEM-003CEBEU")) { - type = shelly_3em_pro; - memset(metername, 0, sizeof(metername)); - strcat(metername, "Shelly 3EM Pro"); - } - - //test auf Shelly Plus 1PM - if(json_model.equals("SNSW-001P16EU")) { - type = shelly_plus_1pm; - memset(metername, 0, sizeof(metername)); - strcat(metername, "Shelly Plus 1PM"); - } - } - - } - } - http.end(); - } - DBG_PRINT("getShellyType() = "); - DBG_PRINTLN(String(metername)); - - return type; -} - - -// read shelly3EM -int getMeterData(int type) { - String shelly_url; - int power = 0; - int power1 = 0; - int power2 = 0; - int power3 = 0; - - JsonDocument doc; - WiFiClient client_shelly; - HTTPClient http; - - if (type > 0 && type < 10) { - shelly_url = "http://" + shelly_ip + "/rpc/Shelly.GetStatus"; // Shelly PRO 3EM - } else if(type >= 10) { - shelly_url = "http://" + shelly_ip + "/status"; // Shelly 3EM und Andere - } else{ - return 0; - } - - if (http.begin(client_shelly, shelly_url)) { - int httpCode = http.GET(); - if (httpCode > 0) { - if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { - String payload = http.getString(); - DeserializationError error = deserializeJson(doc, payload); - - if (error) { - DBG_PRINT(F("deserializeJson() failed: ")); - DBG_PRINTLN(error.f_str()); - } - - - if (type == shelly_3em_pro) { - power1 = doc["em:0"]["a_act_power"]; - power2 = doc["em:0"]["b_act_power"]; - power3 = doc["em:0"]["c_act_power"]; - } else if (type == shelly_3em) { - power1 = doc["emeters"][0]["power"]; - power2 = doc["emeters"][1]["power"]; - power3 = doc["emeters"][2]["power"]; - } else if (type == shelly_em) { - power1 = doc["meters"][0]["power"]; - power2 = doc["meters"][1]["power"]; - power3 = 0; - } else if (type == shelly_1pm) { - power1 = doc["meters"][0]["power"]; - power2 = 0; - power3 = 0; - } else if (type == shelly_plus_1pm) { - power1 = doc["switch:0"]["apower"]; - power2 = 0; - power3 = 0; - } - - - if(!checkbox_meter_l1){ - power1 = 0; - } - - if(!checkbox_meter_l2){ - power2 = 0; - } - - if(!checkbox_meter_l3){ - power3 = 0; - } - - power = power1 + power2 + power3; - - meterpower = power; - meterl1 = power1; - meterl2 = power2; - meterl3 = power3; - } - - } else { - sprintf(dbgbuffer,"[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - DBG_PRINTLN(dbgbuffer); - shelly_model = 0; - } - - http.end(); - } else { - DBG_PRINTLN("[HTTP] Unable to connect\n"); - shelly_model = 0; - } - - return power; -} - - -void checkTimer(){ - - time(&now); - localtime_r(&now, &timeInfo); - - if (checkbox_timer1 == true){ - int t1_hour = String(timer1_time).substring(0,2).toInt(); - int t1_min = String(timer1_time).substring(3).toInt(); - - if((timeInfo.tm_hour == t1_hour && timeInfo.tm_min == t1_min && timeInfo.tm_sec == 0) || (timeInfo.tm_hour == t1_hour && timeInfo.tm_min == t1_min && timeInfo.tm_sec == 1) ){ - soyo_power = timer1_watt; - } - } - - if (checkbox_timer2 == true){ - int t2_hour = String(timer2_time).substring(0,2).toInt(); - int t2_min = String(timer2_time).substring(3).toInt(); - - if((timeInfo.tm_hour == t2_hour && timeInfo.tm_min == t2_min && timeInfo.tm_sec == 0) || (timeInfo.tm_hour == t2_hour && timeInfo.tm_min == t2_min && timeInfo.tm_sec == 1)){ - soyo_power = timer2_watt; - } - } - -} - - -//#################### SETUP ####################### -void setup() { - - DEBUG_SERIAL.begin(115200); - delay(500); - - DBG_PRINTLN(""); - DBG_PRINT(F("CPU Frequency = ")); - DBG_PRINT(F_CPU / 1000000); - DBG_PRINTLN(F(" MHz")); - - WiFi.macAddress(mac); - WiFi.persistent(true); // sonst verliert er nach einem Neustart die IP !!! - - sprintf(dbgbuffer,"ESP_%02X%02X%02X", mac[3], mac[4], mac[5]); - DBG_PRINTLN(dbgbuffer); - - //configTime(MY_TZ, MY_NTP_SERVER); - - sprintf(clientId, "soyo_%02x%02x%02x", mac[3], mac[4], mac[5] ); - - //mqtt_root = "SoyoSource/soyo_xxxxxx"; - strcat(mqtt_root, clientId); - - //topic_power = "SoyoSource/soyo_xxxxxx/power"; - strcat(topic_power, mqtt_root); - strcat(topic_power, "/power"); - - pinMode(SERIAL_COMMUNICATION_CONTROL_PIN, OUTPUT); - digitalWrite(SERIAL_COMMUNICATION_CONTROL_PIN, RS485_RX_PIN_VALUE); - RS485Serial.begin(4800); // set RS485 baud - - readConfig(); - - - ESPAsync_WMParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40); - ESPAsync_WMParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6); - - ESPAsync_WiFiManager wifiManager(&server, &dns); - wifiManager.setSaveConfigCallback(saveConfigCallback); - wifiManager.setConfigPortalTimeout(60); - wifiManager.addParameter(&custom_mqtt_server); - wifiManager.addParameter(&custom_mqtt_port); - configTime(MY_TZ, MY_NTP_SERVER); - - bool res = wifiManager.autoConnect(clientId); - - if(!res) { - DBG_PRINTLN("Failed to connect"); - ESP.restart(); - } else { - //if you get here you have connected to the WiFi - DBG_PRINT("WiFi connected to "); - DBG_PRINTLN(String(WiFi.SSID())); - DBG_PRINT("RSSI = "); - DBG_PRINT(String(WiFi.RSSI())); - DBG_PRINTLN(" dBm"); - DBG_PRINT("IP address "); - DBG_PRINTLN(WiFi.localIP()); - DBG_PRINTLN(); - - //read updated parameters - strcpy(mqtt_server, custom_mqtt_server.getValue()); - strcpy(mqtt_port, custom_mqtt_port.getValue()); - - //save the custom parameters to FS - if (shouldSaveConfig) { - saveConfig(); - } - - DBG_PRINTLN(String("mqttenabled: ") + checkbox_mqttenabled); - if(checkbox_mqttenabled){ - DBG_PRINTLN("set mqtt server!"); - DBG_PRINTLN(String("mqtt_server: ") + mqtt_server); - DBG_PRINTLN(String("mqtt_port: ") + mqtt_port); - - client.setServer(mqtt_server, atoi(mqtt_port)); - client.setCallback(mqtt_callback); - } - - // Handle Web Server Events - events.onConnect([](AsyncEventSourceClient *client){ - if(client->lastId()){ - DBG_PRINTLN(""); - //DEBUG_SERIAL.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); - sprintf(dbgbuffer,"Client reconnected! Last message ID that it got is: %u\n", client->lastId()); - DBG_PRINTLN(dbgbuffer); - //DEBUG_SERIAL.println(""); - } - client->send("hello!", NULL, millis(), 10000); - }); - - // Handle Web Server - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ - new_connect = true; - request->send_P(200, "text/html", index_html, processor); - }); - - // crate json and fetch data - server.on("/json", HTTP_GET, [] (AsyncWebServerRequest *request){ - JsonDocument myJson; - String message = ""; - - rssi = WiFi.RSSI(); - - myJson["WIFIRSSI"] = rssi; - myJson["CLIENTID"] = clientId; - myJson["METERNAME"] = metername; - myJson["MAXWATTINPUT"] = maxwatt; - myJson["TOUT"] = teiler_output; - myJson["NULLINTERVAL"] = nullinterval; - myJson["NULLOFFSET"] = nulloffset; - myJson["METERIP"] = meteripaddr; - myJson["METERINTERVAL"] = meterinterval; - myJson["TIMER1TIME"] = timer1_time; - myJson["TIMER1WATT"] = timer1_watt; - myJson["TIMER2TIME"] = timer2_time; - myJson["TIMER2WATT"] = timer2_watt; - myJson["MQTTROOT"] = mqtt_root; - myJson["MQTTSTATECL"] = mqtt_state; - - myJson["CBNULL"] = checkbox_nulleinspeisung; //checkbox - if(checkbox_nulleinspeisung){ // Stausanzeige - myJson["NULLSTATE"] = "EIN"; - }else{ - myJson["NULLSTATE"] = "AUS"; - } - - myJson["CBMQTTSTATE"] = checkbox_mqttenabled; //checkbox - if(checkbox_mqttenabled){ - myJson["MQTTSTATE"] = "EIN"; - }else{ - myJson["MQTTSTATE"] = "AUS"; - } - - myJson["CBTIMER1"] = checkbox_timer1; //checkbox - myJson["CBTIMER2"] = checkbox_timer2; //checkbox - if(checkbox_timer1 || checkbox_timer2){ - myJson["TIMERSTATE"] = "EIN"; - }else{ - myJson["TIMERSTATE"] = "AUS"; - } - - myJson["CBBATSCHUTZ"] = checkbox_batschutz; //checkbox - if(checkbox_batschutz){ - myJson["BATTSTATE"] = "EIN"; - }else{ - myJson["BATTSTATE"] = "AUS"; - } - - myJson["CBMETERL1"] = checkbox_meter_l1; //checkbox Shelly L1 - myJson["CBMETERL2"] = checkbox_meter_l2; //checkbox Shelly L2 - myJson["CBMETERL3"] = checkbox_meter_l3; //checkbox Shelly L3 - - myJson["MQTTSERVER"] = mqtt_server; - myJson["MQTTPORT"] = mqtt_port; - myJson["MQTTBATVOL"] = mqtt_topic_bat_voltage; - myJson["MQTTBATSOC"] = mqtt_topic_bat_soc; - - myJson["UPTIME"] = uptime_str; - myJson["SOYOPOWER"] = soyo_power; - myJson["METERNAME"] = metername; - myJson["METERPOWER"] = meterpower; - myJson["METERL1"] = meterl1; - myJson["METERL2"] = meterl2; - myJson["METERL3"] = meterl3; - myJson["MQTT_SUB_1"] = String(soyo_power) + " W"; - myJson["MQTT_BAT_SOC"] = String(mqtt_bat_soc, 1) + " %"; - myJson["MQTT_BAT_V"] = String(mqtt_bat_voltage, 1) + " V"; - myJson["BATSOCSTOP"] = batsocstop; - myJson["BATSOCSTART"] = batsocstart; - myJson["WIFIQUALITI"] = dBmtoPercent(rssi); - - - serializeJson(myJson, message); - - request->send(200, "application/json", message); - }); - - // start AP Mode - server.on("/apmode", HTTP_GET, [](AsyncWebServerRequest *request) { - ESPAsync_WiFiManager wifiManager(&server,&dns); - wifiManager.resetSettings(); - - ESP.restart(); - request->send_P(200, "text/html", index_html, processor); - }); - - // restart system - server.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { - DBG_PRINTLN("/restart"); - ESP.restart(); - request->send_P(200, "text/html", index_html, processor); - }); - - server.on("/acoutput", HTTP_GET, [] (AsyncWebServerRequest *request) { - String parm1; - - if (request->hasParam("value") ) { - parm1 = request->getParam("value")->value(); - DBG_PRINT("/acoutput?value = "); - DBG_PRINTLN(parm1); - - if(parm1.equals("/s0") ){ - soyo_power = 0; - sprintf(msgData, "%d", soyo_power); - if(checkbox_mqttenabled){ - client.publish(topic_power, msgData); - } - } - else if(parm1.equals("/p1")){ - soyo_power +=1; - sprintf(msgData, "%d", soyo_power); - if(checkbox_mqttenabled){ - client.publish(topic_power, msgData); - } - } - else if(parm1.equals("/p10")){ - soyo_power +=10; - sprintf(msgData, "%d", soyo_power); - if(checkbox_mqttenabled){ - client.publish(topic_power, msgData); - } - } - else if(parm1.equals("/m1")){ - soyo_power -=1; - if(soyo_power < 0){ - soyo_power = 0; - } - sprintf(msgData, "%d", soyo_power); - if(checkbox_mqttenabled){ - client.publish(topic_power, msgData); - } - } - else if(parm1.equals("/m10")){ - soyo_power -=10; - if(soyo_power < 0){ - soyo_power = 0; - } - sprintf(msgData, "%d", soyo_power); - if(checkbox_mqttenabled){ - client.publish(topic_power, msgData); - } - } - } - request->send_P(200, "text/html", index_html, processor); - }); - - - server.on("/checkbox", HTTP_GET, [] (AsyncWebServerRequest *request) { - String checkbox_id; - String checkbox_value; - - if (request->hasParam("cbid") && request->hasParam("state")) { - checkbox_id = request->getParam("cbid")->value(); - checkbox_value = request->getParam("state")->value(); - - if(checkbox_id.equals("CBTIMER1")){ - if(checkbox_value.equals("1")){ - checkbox_timer1 = true; - } else { - checkbox_timer1 = false; - } - } - else if(checkbox_id.equals("CBTIMER2")){ - if(checkbox_value.equals("1")){ - checkbox_timer2 = true; - } else { - checkbox_timer2 = false; - } - } - else if(checkbox_id.equals("CBMQTTSTATE")){ - if(checkbox_value.equals("1")){ - checkbox_mqttenabled = true; - } else { - checkbox_mqttenabled = false; - } - } - else if(checkbox_id.equals("CBNULL")){ - if(checkbox_value.equals("1")){ - checkbox_nulleinspeisung = true; - } else { - checkbox_nulleinspeisung = false; - soyo_power = 0; - } - } - else if(checkbox_id.equals("CBBATSCHUTZ")){ - if(checkbox_value.equals("1")){ - checkbox_batschutz = true; - } else { - checkbox_batschutz = false; - output_enabled = true; //wenn batschutz aus, dann freigabe fuer soyo output - } - } - else if(checkbox_id.equals("CBMETERL1")){ - if(checkbox_value.equals("1")){ - checkbox_meter_l1 = true; - } else { - checkbox_meter_l1 = false; - } - } - else if(checkbox_id.equals("CBMETERL2")){ - if(checkbox_value.equals("1")){ - checkbox_meter_l2 = true; - } else { - checkbox_meter_l2 = false; - } - } - else if(checkbox_id.equals("CBMETERL3")){ - if(checkbox_value.equals("1")){ - checkbox_meter_l3 = true; - } else { - checkbox_meter_l3 = false; - } - } - } - request->send_P(200, "text/html", index_html, processor); - }); - - - server.on("/savesettings", HTTP_GET, [] (AsyncWebServerRequest *request) { - String value; - - value = request->getParam("t1")->value(); - memset(timer1_time, 0, sizeof(timer1_time)); - strcat(timer1_time, value.c_str()); - - value = request->getParam("w1")->value(); - timer1_watt = atoi(value.c_str()); - - value = request->getParam("t2")->value(); - memset(timer2_time, 0, sizeof(timer2_time)); - strcat(timer2_time, value.c_str()); - - value = request->getParam("w2")->value(); - timer2_watt = atoi(value.c_str()); - - value = request->getParam("maxwatt")->value(); - maxwatt = atoi(value.c_str()); - - value = request->getParam("meteripaddr")->value(); - memset(meteripaddr, 0, sizeof(meteripaddr)); - strcat(meteripaddr, value.c_str()); - - value = request->getParam("tout")->value(); - teiler_output = atoi(value.c_str()); - - value = request->getParam("meterinterval")->value(); - meterinterval = atol(value.c_str()); - - value = request->getParam("nullinterval")->value(); - nullinterval = atol(value.c_str()); - - value = request->getParam("nulloffset")->value(); - nulloffset = atoi(value.c_str()); - - value = request->getParam("mqttserver")->value(); - memset(mqtt_server, 0, sizeof(mqtt_server)); - strcat(mqtt_server, value.c_str()); - - value = request->getParam("mqttport")->value(); - memset(mqtt_port, 0, sizeof(mqtt_port)); - strcat(mqtt_port, value.c_str()); - - value = request->getParam("mqttbatvol")->value(); - memset(mqtt_topic_bat_voltage, 0, sizeof(mqtt_topic_bat_voltage)); - strcat(mqtt_topic_bat_voltage, value.c_str()); - - value = request->getParam("mqttbatsoc")->value(); - memset(mqtt_topic_bat_soc, 0, sizeof(mqtt_topic_bat_soc)); - strcat(mqtt_topic_bat_soc, value.c_str()); - - value = request->getParam("batsocstop")->value(); - batsocstop = atoi(value.c_str()); - - value = request->getParam("batsocstart")->value(); - batsocstart = atoi(value.c_str()); - - saveConfig(); - - shelly_ip = String(meteripaddr); - - request->send_P(200, "text/html", index_html, processor); - }); - - AsyncElegantOTA.begin(&server); - server.onNotFound(notFound); - server.addHandler(&events); - server.begin(); - - rssi = WiFi.RSSI(); - - shelly_model = getShellyType(); // get shelly typ, 3em / 3empro - - digitalWrite(SERIAL_COMMUNICATION_CONTROL_PIN, RS485_TX_PIN_VALUE); // RS485 Modul -> set board to transmit - } - - // end setup() -} - - -void loop() { - - if(checkbox_mqttenabled){ - if (!client.connected()) { - DBG_PRINTLN("lost mqtt connection -> start reconncect"); - reconnect(); - } - client.loop(); - } - - - // send current power to SoyoSource - if ((millis() - lastTimerSoyoSource) > timerSoyoSource) { - - if(checkbox_batschutz == true && output_enabled == false){ // wenn batterie soc < limit dann soyo_power = 0 - soyo_power = 0; - } - - new_soyo_power = soyo_power / teiler_output; // Last auf mehrere Soyo's aufteilen - if(new_soyo_power < 0){ - new_soyo_power = 0; - } - - //sendSoyoPowerData(soyo_power); - sendSoyoPowerData(new_soyo_power); - - if(new_soyo_power != old_soyo_power) { // nur für Debug, damit nur Laständerungen ausgegeben werden - old_soyo_power = new_soyo_power; - sprintf(dbgbuffer,"new soyo_power = %i ( %02X %02X %02X %02X %02X %02X %02X %02X )",new_soyo_power, soyo_power_data[0],soyo_power_data[1],soyo_power_data[2],soyo_power_data[3],soyo_power_data[4],soyo_power_data[5],soyo_power_data[6],soyo_power_data[7]); - DBG_PRINTLN(dbgbuffer); - } - - if(checkbox_mqttenabled){ - sprintf(msgData, "%d", soyo_power); - client.publish(topic_power, msgData); - } - - lastTimerSoyoSource = millis(); - } - - - // timer to get Shelly3EM data - if ((millis() - lastMeterinterval) > meterinterval) { - if (shelly_model > 0){ - meter_power = getMeterData(shelly_model); - } else{ - shelly_model = getShellyType(); - DBG_PRINTLN("Kein Shelly erkannt! Bitte IP eintragen, speichern und ESP neu starten."); - } - - lastMeterinterval = millis(); - } - - - // timer to manage Nulleinspeisung - if ((millis() - lastNullinterval) > nullinterval) { - if(checkbox_nulleinspeisung && output_enabled){ - if(meter_power > nulloffset + 10){ - soyo_power += meter_power - nulloffset; - - if(soyo_power > maxwatt){ - soyo_power = maxwatt; - } - } - - if(meter_power < 0 + nulloffset ){ - soyo_power += meter_power - nulloffset; - - if(soyo_power < 0){ - soyo_power = 0; - } - } - - } - lastNullinterval = millis(); - } - - - // timer für uptime, SoyoSource Timer und BatSOCLimit - if ((millis() - lastTimerUptime) > timerUptime) { - myUptime(); - - if(checkbox_timer1 || checkbox_timer2){ - checkTimer(); - } - - // check ob Batterie SOC < oder > eingestelltem Limit - float mqttbatsoc_float = mqtt_bat_soc + 0.5; - int mqttbatsoc_int = (int)mqttbatsoc_float; - - if(checkbox_batschutz == true && mqttbatsoc_int > 1){ // falls mqtt noch nicht verbunden oder nicht aktiv - if(mqttbatsoc_int <= batsocstop){ - output_enabled = false; - }else if(mqttbatsoc_int >= batsocstart){ - output_enabled = true; - } - } - - lastTimerUptime = millis(); - } - - -} - - - +/*************************************************************************** + soyosource-powercontroller @matlen67 + + Version: 1.240508BK + + 16.03.2024 -> Speichern der Checkboxzustände: aktiv Timer1 / Timer2 + 03.04.2024 -> Statusübersicht bei geschlossenen details/summary boxen + 14.04.2024 -> Falls Batterieschutz aktiviert, deaktiviere Regelung der Nulleinspeisung + 25.04.2024 -> Leistungspunkt bei Nulleinspeisung festlegen + (Bei mir funktioniert gut Intervall Shelly 1000ms & Intervall Nulleinspeisung 4000ms) + 26.04.2024 -> Auswahl der aktiven Leiter (L1, L2, L3) beim Shelly + 27.04.2024 -> Fehlerbehebung Shelly 3EM, Shelly Plus 1PM mit zugefügt + 28.04.2024 -> Teiler unter 'SoyoSource Output' hinzugefügt, um die Leistung auf mehere Geräte aufzuteilen + 29.04.2024 -> Telnet entfernt + 05.05.2024 -> update ArduinoJson to 7.0.4 + 08.05.2024 -> mqtt topic voltage & soc bearbeitbar + + + ************************* + Wiring + NodeMCU D1 - RS485 RO + NodeMCU D3 - RS485 DE/RE + NodeMCU D4 - RS485 DI + +****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "html.h" + +#define DEBUG_SERIAL Serial + +#define DEBUG + +#ifdef DEBUG + #define DBG_PRINT(x) DEBUG_SERIAL.print(x) + #define DBG_PRINTLN(x) DEBUG_SERIAL.println(x) +#else + #define DBG_PRINT(x) + #define DBG_PRINTLN(x) +#endif + +//***************************************************************************** +// da Serial.printf(x,x) mit define nicht funktioniert als workaround sprintf +// sprintf(dbgbuffer,"ESP_%02X%02X%02X", mac[3], mac[4], mac[5]); +// DBG_PRINTLN(dbgbuffer); +//***************************************************************************** +char dbgbuffer[128]; +#define D1 5 +#define D3 0 +#define D4 2 + +#define RXPin D1 // Serial Receive pin (D1) +#define TXPin D4 // Serial Transmit pin (D4) + +//RS485 control +#define SERIAL_COMMUNICATION_CONTROL_PIN D3 // Transmission set pin (D3) +#define RS485_TX_PIN_VALUE HIGH +#define RS485_RX_PIN_VALUE LOW + +// time server +#define MY_NTP_SERVER "de.pool.ntp.org" +#define MY_TZ "CET-1CEST,M3.5.0/2,M10.5.0/3" + +//IMPORTANT: Uncomment this line if you want to enable SHELLY: +#define ENABLE_HOMEWIZARD +#ifndef ENABLE_HOMEWIZARD +#define SHELLY +#endif + +SoftwareSerial RS485Serial(RXPin, TXPin); // RX, TX +WiFiClient espClient; +PubSubClient client(espClient); +AsyncWebServer server(80); +AsyncEventSource events("/events"); +AsyncDNSServer dns; + + +// Uptime Global Variables +Uptime uptime; +uint8_t Uptime_Years = 0U, Uptime_Months = 0U, Uptime_Days = 0U, Uptime_Hours = 0U, Uptime_Minutes = 0U, Uptime_Seconds = 0U; +uint16_t Uptime_TotalDays = 0U; // Total Uptime Days +char uptime_str[37]; + +// Wifi to percent +const int RSSI_MAX =-50; // max strength signal in dBm +const int RSSI_MIN =-100; // min strength signal in dBm + +//Timer +unsigned long timerSoyoSource = 555; +unsigned long lastTimerSoyoSource = 0; + +unsigned long timerUptime = 1000; +unsigned long lastTimerUptime = 0; + +unsigned long meterinterval = 2000; +unsigned long lastMeterinterval = 0; + +unsigned long nullinterval = 5000; +unsigned long lastNullinterval = 0; + + + +//mqtt +char mqtt_server[16] = "192.168.178.30"; +char mqtt_port[5] = "1889"; +char msgData[64]; +String msg = ""; +char mqtt_topic_bat_voltage [48] = "VenusOS/SmartShunt/voltage"; +char mqtt_topic_bat_soc [48] = "VenusOS/SmartShunt/soc"; + +String dataReceived; +int data; +bool isDataReceived = false; +uint8_t byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7; +int byteSend; +int data_array[8]; +int soyo_hello_data[8] = {0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // bit7 org 0x00, CRC 0xFF +int soyo_power_data[8] = {0x24, 0x56, 0x00, 0x21, 0x00, 0x00, 0x80, 0x08}; // 0 Watt +int soyo_text_data[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +char buffer[8]; +int old_soyo_power = 0; +int soyo_power = 0; +int new_soyo_power = 0; +int teiler_output = 1; + +unsigned char mac[6]; +char mqtt_root[32] = "SoyoSource/"; +char clientId[16]; +char topic_power[40]; +char soyo_text[40]; + +float mqtt_bat_soc = 0.0; +float mqtt_bat_voltage = 0.0; + +long rssi; + +time_t now; +tm timeInfo; + + +// timer +char currentTime[20]; +char timer1_time[6] = "06:00"; +char timer2_time[6] = "20:00"; +char meteripaddr[16] = ""; + +int timer1_watt = 0; +int timer2_watt = 0; +int maxwatt = 0; + +//state checkboxes +bool checkbox_timer1 = false; +bool checkbox_timer2 = false; +bool checkbox_mqttenabled = false; +bool checkbox_nulleinspeisung = false; +bool checkbox_batschutz = false; +bool checkbox_meter_l1 = true; +bool checkbox_meter_l2 = true; +bool checkbox_meter_l3 = true; + +char metername[24] = "Meter"; +char mqtt_state[20] = "disabled"; + +// variablen Shelly 3em +const int shelly_3em_pro = 1; // ip/rpc/Shelly.GetStatus +const int shelly_plus_1pm = 2; // ip/rpc/Shelly.GetStatus + +const int shelly_3em = 10; // ip/status +const int shelly_em = 11; // ip/status +const int shelly_1pm = 12; // ip/status + +const int homewizard = 20; // ip/api/v1/data + +String meter_ip = ""; +int meter_model = 0 ; + +//nulleinspeisung +int nulloffset = 0; +int meter_power = 0; +int meterpower = 0; +int meterl1 = 0; +int meterl2 = 0; +int meterl3 = 0; + +//batterieüberwachung +int batsocstop = 15; +int batsocstart = 50; +bool output_enabled = true; + + +bool new_connect = true; + +const char* PARAM_MESSAGE = "message"; + +//flag for saving data +bool shouldSaveConfig = false; + + +//callback notifying us of the need to save config +void saveConfigCallback () { + DBG_PRINTLN("Should save config"); + shouldSaveConfig = true; +} + + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + + +int dBmtoPercent(int dBm){ + int percent; + if(dBm <= RSSI_MIN){ + percent = 0; + } else if(dBm >= RSSI_MAX) { + percent = 100; + } else { + percent = 2 * (dBm + 100); + } + + return percent; +} + + +void myUptime(){ + uptime.calculateUptime(); + + // Get The Uptime Values To Global Variables + Uptime_Years = uptime.getYears(); + Uptime_Months = uptime.getMonths(); + Uptime_Days = uptime.getDays(); + Uptime_Hours = uptime.getHours(); + Uptime_Minutes = uptime.getMinutes(); + Uptime_Seconds = uptime.getSeconds(); + Uptime_TotalDays = uptime.getTotalDays(); + + if (Uptime_Years == 0U) { // Uptime Is Less Than One Year + // First 60 Seconds + if (Uptime_Minutes == 0U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) + sprintf(uptime_str, "00:00:%02i", Uptime_Seconds); + // First Minute + else if (Uptime_Minutes == 1U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) + sprintf(uptime_str, "00:%02i:%02i", Uptime_Minutes, Uptime_Seconds); + // Second Minute And More But Less Than Hours, Days, Months + else if (Uptime_Minutes >= 2U && Uptime_Hours == 0U && Uptime_Days == 0U && Uptime_Months == 0U) + sprintf(uptime_str, "00:%02i:%02i", Uptime_Minutes, Uptime_Seconds); + // First Hour And More But Less Than Days, Months + else if (Uptime_Hours >= 1U && Uptime_Days == 0U && Uptime_Months == 0U) + sprintf(uptime_str, "%02i:%02i:%02i", Uptime_Hours, Uptime_Minutes, Uptime_Seconds); + // First Day And Less Than Month + else if (Uptime_Days == 1U && Uptime_Months == 0U) + sprintf(uptime_str, "%iday %02i:%02i:%02i", Uptime_Days, Uptime_Hours, Uptime_Minutes, Uptime_Seconds); + // Second Day And More But Less Than Month + else if (Uptime_Days >= 2U && Uptime_Months == 0U) + sprintf(uptime_str, "%idays %02i:%02i:%02i", Uptime_Days, Uptime_Hours, Uptime_Minutes, Uptime_Seconds); + // First Month And More But Less Than One Year + else if (Uptime_Months >= 1U) + sprintf(uptime_str, "%im, %id %02i:%02i", Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); + // If There Is Any Error In This If Loop Then Make Full String. + else sprintf(uptime_str, "%iy %im %id %02i:%02i", Uptime_Years, Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); + } else // Uptime Is More Than One Year + sprintf(uptime_str, "%iy %im %id %02i:%02i", Uptime_Years, Uptime_Months, Uptime_Days, Uptime_Hours, Uptime_Minutes); +} + + +//callback from mqtt +void mqtt_callback(char* topic, byte* payload, unsigned int length) { + unsigned int i = 0; + + for (i=0;i= 0 && arrived_value_i <= 3000) { + soyo_power = arrived_value_i; + } + } + + if(strcmp(topic, mqtt_topic_bat_soc) == 0){ + float arrived_value_f = atof(buffer); + mqtt_bat_soc = arrived_value_f; + } + + if(strcmp(topic, mqtt_topic_bat_voltage) == 0){ + float arrived_value_f = atof(buffer); + mqtt_bat_voltage = arrived_value_f; + } +} + + +String processor(const String& var){ + return String(); +} + + +void reconnect() { + DBG_PRINTLN("reconnect MQTT connection!"); + + //set callback again + client.setCallback(mqtt_callback); + + uint8_t timeout = 15; + + // wait for connection + while (!client.connected()){ + + DBG_PRINTLN(""); + + if (client.connect(clientId)) { + DBG_PRINTLN("connection established"); + + client.publish(topic_power, "0"); + client.subscribe(topic_power); + client.subscribe(mqtt_topic_bat_soc); + client.subscribe(mqtt_topic_bat_voltage); + + strcpy(mqtt_state, "connect"); + + DBG_PRINT("subscrible: "); + DBG_PRINT(topic_power); + DBG_PRINTLN(""); + + DBG_PRINT("subscrible: "); + DBG_PRINT(mqtt_topic_bat_soc); + DBG_PRINTLN(""); + + DBG_PRINT("subscrible: "); + DBG_PRINT(mqtt_topic_bat_voltage); + DBG_PRINTLN(""); + + } else { + DBG_PRINTLN("reconnect failed! "); + strcpy(mqtt_state, "connect error"); + + while (timeout){ + DBG_PRINT("."); + timeout--; + delay(1000); + } + } + } + +} + + +int calc_checksumme(int b1, int b2, int b3, int b4, int b5, int b6 ){ + int calc = (0xFF - b1 - b2 - b3 - b4 - b5 - b6) % 256; + return calc & 0xFF; +} + + +void sendSoyoPowerData(int power){ + soyo_power_data[0] = 0x24; + soyo_power_data[1] = 0x56; + soyo_power_data[2] = 0x00; + soyo_power_data[3] = 0x21; + soyo_power_data[4] = power >> 0x08; + soyo_power_data[5] = power & 0xFF; + soyo_power_data[6] = 0x80; + soyo_power_data[7] = calc_checksumme(soyo_power_data[1], soyo_power_data[2], soyo_power_data[3], soyo_power_data[4], soyo_power_data[5], soyo_power_data[6]); + + for(int i=0; i<8; i++) { + RS485Serial.write(soyo_power_data[i]); // send data to RS485 + //DBG_PRINTLN(soyo_power_data[i], HEX); + } +} + +//read config.json +void readConfig(){ + //read configuration from json + DBG_PRINTLN("mounting FS..."); + + if (LittleFS.begin()) { + DBG_PRINTLN("mounted file system"); + if (LittleFS.exists("/config.json")) { + //file exists, reading and loading + DBG_PRINTLN("reading config file"); + File configFile = LittleFS.open("/config.json", "r"); + if (configFile) { + DBG_PRINTLN("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); + + JsonDocument json; + auto deserializeError = deserializeJson(json, buf.get()); + serializeJson(json, Serial); + if (!deserializeError) { + DBG_PRINTLN("\nparsed json"); + strcpy(mqtt_server, json["mqtt_server"]); + strcpy(mqtt_port, json["mqtt_port"]); + + if(json.containsKey("mqtt_bat_vol")){ + strcpy(mqtt_topic_bat_voltage, json["mqtt_bat_vol"]); + } + + if(json.containsKey("mqtt_bat_soc")){ + strcpy(mqtt_topic_bat_soc, json["mqtt_bat_soc"]); + } + + char key_value[2]; + + if(json.containsKey("mqtt_on")){ + strcpy(key_value, json["mqtt_on"]); + if(strcmp(key_value, "1") == 0){ + checkbox_mqttenabled = true; + }else{ + checkbox_mqttenabled = false; + } + } + + if(json.containsKey("zft_on")){ + strcpy(key_value, json["zft_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_nulleinspeisung = true; + }else{ + checkbox_nulleinspeisung = false; + } + } + + if(json.containsKey("batp_on")){ + strcpy(key_value, json["batp_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_batschutz = true; + }else{ + checkbox_batschutz = false; + } + } + + if(json.containsKey("t1_on")){ + strcpy(key_value, json["t1_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_timer1 = true; + }else{ + checkbox_timer1 = false; + } + } + + if(json.containsKey("t2_on")){ + strcpy(key_value, json["t2_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_timer2 = true; + }else{ + checkbox_timer2 = false; + } + } + + if(json.containsKey("mtr_l1_on")){ + strcpy(key_value, json["mtr_l1_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_meter_l1 = true; + }else{ + checkbox_meter_l1 = false; + } + } + + if(json.containsKey("mtr_l2_on")){ + strcpy(key_value, json["mtr_l2_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_meter_l2 = true; + }else{ + checkbox_meter_l2 = false; + } + } + + if(json.containsKey("mtr_l3_on")){ + strcpy(key_value, json["mtr_l3_on"]); + + if(strcmp(key_value, "1") == 0){ + checkbox_meter_l3 = true; + }else{ + checkbox_meter_l3 = false; + } + } + + + if(json.containsKey("t1_t")){ + strcpy(timer1_time, json["t1_t"]); + } + + if(json.containsKey("t2_t")){ + strcpy(timer2_time, json["t2_t"]); + } + + if(json.containsKey("t1_p")){ + timer1_watt = json["t1_p"]; + } + + if(json.containsKey("t2_p")){ + timer2_watt = json["t2_p"]; + } + + if(json.containsKey("mp")){ + maxwatt = json["mp"]; + } + + if(json.containsKey("mtr_ip")){ + strcpy(meteripaddr, json["mtr_ip"]); + meter_ip = String(meteripaddr); + } + + if(json.containsKey("mtr_iv")){ + meterinterval = json["mtr_iv"]; + } + + if(json.containsKey("z_iv")){ + nullinterval = json["z_iv"]; + } + + if(json.containsKey("z_ofs")){ + nulloffset = json["z_ofs"]; + } + + if(json.containsKey("soc_stop")){ + batsocstop = json["soc_stop"]; + } + + if(json.containsKey("soc_start")){ + batsocstart = json["soc_start"]; + } + + if(json.containsKey("tout")){ + teiler_output = json["tout"]; + } + + } else { + DBG_PRINTLN("failed to load json config"); + } + } + } + } else { + DBG_PRINTLN("failed to mount FS"); + } + //end read config data +} + + +// write config.json +void saveConfig(){ + DBG_PRINTLN(F("save data to config.json")); + JsonDocument json; + + json["mqtt_server"] = mqtt_server; + json["mqtt_port"] = mqtt_port; + json["mqtt_bat_vol"] = mqtt_topic_bat_voltage; + json["mqtt_bat_soc"] = mqtt_topic_bat_soc; + + + if(checkbox_mqttenabled){ + json["mqtt_on"] = "1"; + }else{ + json["mqtt_on"] = "0"; + } + + if(checkbox_nulleinspeisung){ + json["zft_on"] = "1"; + }else{ + json["zft_on"] = "0"; + } + + if(checkbox_batschutz){ + json["batp_on"] = "1"; + }else{ + json["batp_on"] = "0"; + } + + if(checkbox_timer1){ + json["t1_on"] = "1"; + }else{ + json["t1_on"] = "0"; + } + + if(checkbox_timer2){ + json["t2_on"] = "1"; + }else{ + json["t2_on"] = "0"; + } + + if(checkbox_meter_l1){ + json["mtr_l1_on"] = "1"; + }else{ + json["mtr_l1_on"] = "0"; + } + + if(checkbox_meter_l2){ + json["mtr_l2_on"] = "1"; + }else{ + json["mtr_l2_on"] = "0"; + } + + if(checkbox_meter_l3){ + json["mtr_l3_on"] = "1"; + }else{ + json["mtr_l3_on"] = "0"; + } + + json["t1_t"] = timer1_time; + json["t1_p"] = timer1_watt; + json["t2_t"] = timer2_time; + json["t2_p"] = timer2_watt; + json["mp"] = maxwatt; + json["mtr_ip"] = meteripaddr; + json["mtr_iv"] = meterinterval; + json["z_iv"] = nullinterval; + json["z_ofs"] = nulloffset; + json["soc_stop"] = batsocstop; + json["soc_start"] = batsocstart; + json["tout"] = teiler_output; + + File configFile = LittleFS.open("/config.json", "w"); + if (!configFile) { + DBG_PRINTLN("failed to open config file for writing"); + return; + } + + serializeJson(json, configFile); + configFile.close(); + + serializeJson(json, Serial); + DBG_PRINTLN(); +} + + +// get meter type(3EM PRO, 3EM, EM, 1PM, Plus 1PM, Homewizard) +int getMeterType(){ +#ifdef ENABLE_SHELLY + String meter_url = "http://" + meter_ip + "/shelly"; +#endif +#ifdef ENABLE_HOMEWIZARD + String meter_url = "http://" + meter_ip + "/api"; +#endif + int type = 0; + + memset(metername, 0, sizeof(metername)); + strcat(metername, "no device"); + + JsonDocument doc; + + WiFiClient client_meter; + HTTPClient http; + + if (http.begin(client_meter, meter_url)) { + int httpCode = http.GET(); + + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = http.getString(); + + DeserializationError error = deserializeJson(doc, payload); + + if (error) { + DBG_PRINT(F("deserializeJson() failed: ")); + DBG_PRINTLN(error.f_str()); + } + + String json_type = doc["type"]; + String json_model = doc["model"]; + String json_product_name = doc["product_name"]; + if(json_type != NULL){ + + //test auf Shelly 1PM + if(json_type.equals("SHSW-PM")){ + type = shelly_1pm; + memset(metername, 0, sizeof(metername)); + strcat(metername, "Shelly 1PM"); + } + //test auf Shelly EM + if(json_type.equals("SHEM")){ + type = shelly_em; + memset(metername, 0, sizeof(metername)); + strcat(metername, "Shelly EM"); + } + + //test auf Shelly 3EM + if(json_type.equals("SHEM-3")){ + type = shelly_3em; + memset(metername, 0, sizeof(metername)); + strcat(metername, "Shelly 3EM"); + } + } + + + if(json_model != NULL){ + + //test auf Shelly 3EM Pro + if(json_model.equals("SPEM-003CEBEU")) { + type = shelly_3em_pro; + memset(metername, 0, sizeof(metername)); + strcat(metername, "Shelly 3EM Pro"); + } + + //test auf Shelly Plus 1PM + if(json_model.equals("SNSW-001P16EU")) { + type = shelly_plus_1pm; + memset(metername, 0, sizeof(metername)); + strcat(metername, "Shelly Plus 1PM"); + } + } + + if(json_product_name != NULL){ + //test auf Homewizard + if(json_product_name.equals("P1 meter")) { + type = homewizard; + DBG_PRINTLN(type); + memset(metername, 0, sizeof(metername)); + strcat(metername, "Homewizard"); + } + } + } + } + http.end(); + } + DBG_PRINT("getMeterType() = "); + DBG_PRINTLN(String(metername)); + + return type; +} + + +// read meter +int getMeterData(int type) { + String meter_url; + int power = 0; + int power1 = 0; + int power2 = 0; + int power3 = 0; + + JsonDocument doc; + WiFiClient client_meter; + HTTPClient http; + + if (type > 0 && type < 10) { + meter_url = "http://" + meter_ip + "/rpc/Shelly.GetStatus"; // Shelly PRO 3EM + } else if(type >= 10 && type < 15) { + meter_url = "http://" + meter_ip + "/status"; // Shelly 3EM und Andere + } else if (type >=16) { + meter_url = "http://" + meter_ip + "/api/v1/data"; // Homewizard + } else { + return 0; + } + + if (http.begin(client_meter, meter_url)) { + int httpCode = http.GET(); + if (httpCode > 0) { + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + String payload = http.getString(); + DeserializationError error = deserializeJson(doc, payload); + + if (error) { + DBG_PRINT(F("deserializeJson() failed: ")); + DBG_PRINTLN(error.f_str()); + } + + + if (type == shelly_3em_pro) { + power1 = doc["em:0"]["a_act_power"]; + power2 = doc["em:0"]["b_act_power"]; + power3 = doc["em:0"]["c_act_power"]; + } else if (type == shelly_3em) { + power1 = doc["emeters"][0]["power"]; + power2 = doc["emeters"][1]["power"]; + power3 = doc["emeters"][2]["power"]; + } else if (type == shelly_em) { + power1 = doc["meters"][0]["power"]; + power2 = doc["meters"][1]["power"]; + power3 = 0; + } else if (type == shelly_1pm) { + power1 = doc["meters"][0]["power"]; + power2 = 0; + power3 = 0; + } else if (type == shelly_plus_1pm) { + power1 = doc["switch:0"]["apower"]; + power2 = 0; + power3 = 0; + } else if (type == homewizard) { + power1 = doc["active_power_l1_w"]; + power2 = doc["active_power_l2_w"]; + power3 = doc["active_power_l3_w"]; + } + + + if(!checkbox_meter_l1){ + power1 = 0; + } + + if(!checkbox_meter_l2){ + power2 = 0; + } + + if(!checkbox_meter_l3){ + power3 = 0; + } + + power = power1 + power2 + power3; + + meterpower = power; + meterl1 = power1; + meterl2 = power2; + meterl3 = power3; + } + + } else { + sprintf(dbgbuffer,"[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); + DBG_PRINTLN(dbgbuffer); + meter_model = 0; + } + + http.end(); + } else { + DBG_PRINTLN("[HTTP] Unable to connect\n"); + meter_model = 0; + } + + return power; +} + + +void checkTimer(){ + + time(&now); + localtime_r(&now, &timeInfo); + + if (checkbox_timer1 == true){ + int t1_hour = String(timer1_time).substring(0,2).toInt(); + int t1_min = String(timer1_time).substring(3).toInt(); + + if((timeInfo.tm_hour == t1_hour && timeInfo.tm_min == t1_min && timeInfo.tm_sec == 0) || (timeInfo.tm_hour == t1_hour && timeInfo.tm_min == t1_min && timeInfo.tm_sec == 1) ){ + soyo_power = timer1_watt; + } + } + + if (checkbox_timer2 == true){ + int t2_hour = String(timer2_time).substring(0,2).toInt(); + int t2_min = String(timer2_time).substring(3).toInt(); + + if((timeInfo.tm_hour == t2_hour && timeInfo.tm_min == t2_min && timeInfo.tm_sec == 0) || (timeInfo.tm_hour == t2_hour && timeInfo.tm_min == t2_min && timeInfo.tm_sec == 1)){ + soyo_power = timer2_watt; + } + } + +} + + +//#################### SETUP ####################### +void setup() { + + DEBUG_SERIAL.begin(115200); + delay(500); + + DBG_PRINTLN(""); + DBG_PRINT(F("CPU Frequency = ")); + DBG_PRINT(F_CPU / 1000000); + DBG_PRINTLN(F(" MHz")); + + WiFi.macAddress(mac); + WiFi.persistent(true); // sonst verliert er nach einem Neustart die IP !!! + + sprintf(dbgbuffer,"ESP_%02X%02X%02X", mac[3], mac[4], mac[5]); + DBG_PRINTLN(dbgbuffer); + + //configTime(MY_TZ, MY_NTP_SERVER); + + sprintf(clientId, "soyo_%02x%02x%02x", mac[3], mac[4], mac[5] ); + + //mqtt_root = "SoyoSource/soyo_xxxxxx"; + strcat(mqtt_root, clientId); + + //topic_power = "SoyoSource/soyo_xxxxxx/power"; + strcat(topic_power, mqtt_root); + strcat(topic_power, "/power"); + + pinMode(SERIAL_COMMUNICATION_CONTROL_PIN, OUTPUT); + digitalWrite(SERIAL_COMMUNICATION_CONTROL_PIN, RS485_RX_PIN_VALUE); + RS485Serial.begin(4800); // set RS485 baud + + readConfig(); + + + ESPAsync_WMParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40); + ESPAsync_WMParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6); + + ESPAsync_WiFiManager wifiManager(&server, &dns); + wifiManager.setSaveConfigCallback(saveConfigCallback); + wifiManager.setConfigPortalTimeout(60); + wifiManager.addParameter(&custom_mqtt_server); + wifiManager.addParameter(&custom_mqtt_port); + configTime(MY_TZ, MY_NTP_SERVER); + + bool res = wifiManager.autoConnect(clientId); + + if(!res) { + DBG_PRINTLN("Failed to connect"); + ESP.restart(); + } else { + //if you get here you have connected to the WiFi + DBG_PRINT("WiFi connected to "); + DBG_PRINTLN(String(WiFi.SSID())); + DBG_PRINT("RSSI = "); + DBG_PRINT(String(WiFi.RSSI())); + DBG_PRINTLN(" dBm"); + DBG_PRINT("IP address "); + DBG_PRINTLN(WiFi.localIP()); + DBG_PRINTLN(); + + //read updated parameters + strcpy(mqtt_server, custom_mqtt_server.getValue()); + strcpy(mqtt_port, custom_mqtt_port.getValue()); + + //save the custom parameters to FS + if (shouldSaveConfig) { + saveConfig(); + } + + DBG_PRINTLN(String("mqttenabled: ") + checkbox_mqttenabled); + if(checkbox_mqttenabled){ + DBG_PRINTLN("set mqtt server!"); + DBG_PRINTLN(String("mqtt_server: ") + mqtt_server); + DBG_PRINTLN(String("mqtt_port: ") + mqtt_port); + + client.setServer(mqtt_server, atoi(mqtt_port)); + client.setCallback(mqtt_callback); + } + + // Handle Web Server Events + events.onConnect([](AsyncEventSourceClient *client){ + if(client->lastId()){ + DBG_PRINTLN(""); + //DEBUG_SERIAL.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId()); + sprintf(dbgbuffer,"Client reconnected! Last message ID that it got is: %u\n", client->lastId()); + DBG_PRINTLN(dbgbuffer); + //DEBUG_SERIAL.println(""); + } + client->send("hello!", NULL, millis(), 10000); + }); + + // Handle Web Server + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + new_connect = true; + request->send_P(200, "text/html", index_html, processor); + }); + + // crate json and fetch data + server.on("/json", HTTP_GET, [] (AsyncWebServerRequest *request){ + JsonDocument myJson; + String message = ""; + + rssi = WiFi.RSSI(); + + myJson["WIFIRSSI"] = rssi; + myJson["CLIENTID"] = clientId; + myJson["METERNAME"] = metername; + myJson["MAXWATTINPUT"] = maxwatt; + myJson["TOUT"] = teiler_output; + myJson["NULLINTERVAL"] = nullinterval; + myJson["NULLOFFSET"] = nulloffset; + myJson["METERIP"] = meteripaddr; + myJson["METERINTERVAL"] = meterinterval; + myJson["TIMER1TIME"] = timer1_time; + myJson["TIMER1WATT"] = timer1_watt; + myJson["TIMER2TIME"] = timer2_time; + myJson["TIMER2WATT"] = timer2_watt; + myJson["MQTTROOT"] = mqtt_root; + myJson["MQTTSTATECL"] = mqtt_state; + + myJson["CBNULL"] = checkbox_nulleinspeisung; //checkbox + if(checkbox_nulleinspeisung){ // Stausanzeige + myJson["NULLSTATE"] = "EIN"; + }else{ + myJson["NULLSTATE"] = "OFF"; + } + + myJson["CBMQTTSTATE"] = checkbox_mqttenabled; //checkbox + if(checkbox_mqttenabled){ + myJson["MQTTSTATE"] = "EIN"; + }else{ + myJson["MQTTSTATE"] = "AUS"; + } + + myJson["CBTIMER1"] = checkbox_timer1; //checkbox + myJson["CBTIMER2"] = checkbox_timer2; //checkbox + if(checkbox_timer1 || checkbox_timer2){ + myJson["TIMERSTATE"] = "EIN"; + }else{ + myJson["TIMERSTATE"] = "AUS"; + } + + myJson["CBBATSCHUTZ"] = checkbox_batschutz; //checkbox + if(checkbox_batschutz){ + myJson["BATTSTATE"] = "EIN"; + }else{ + myJson["BATTSTATE"] = "AUS"; + } + + myJson["CBMETERL1"] = checkbox_meter_l1; //checkbox Shelly L1 + myJson["CBMETERL2"] = checkbox_meter_l2; //checkbox Shelly L2 + myJson["CBMETERL3"] = checkbox_meter_l3; //checkbox Shelly L3 + + myJson["MQTTSERVER"] = mqtt_server; + myJson["MQTTPORT"] = mqtt_port; + myJson["MQTTBATVOL"] = mqtt_topic_bat_voltage; + myJson["MQTTBATSOC"] = mqtt_topic_bat_soc; + + myJson["UPTIME"] = uptime_str; + myJson["SOYOPOWER"] = soyo_power; + myJson["METERNAME"] = metername; + myJson["METERPOWER"] = meterpower; + myJson["METERL1"] = meterl1; + myJson["METERL2"] = meterl2; + myJson["METERL3"] = meterl3; + myJson["MQTT_SUB_1"] = String(soyo_power) + " W"; + myJson["MQTT_BAT_SOC"] = String(mqtt_bat_soc, 1) + " %"; + myJson["MQTT_BAT_V"] = String(mqtt_bat_voltage, 1) + " V"; + myJson["BATSOCSTOP"] = batsocstop; + myJson["BATSOCSTART"] = batsocstart; + myJson["WIFIQUALITI"] = dBmtoPercent(rssi); + + + serializeJson(myJson, message); + + request->send(200, "application/json", message); + }); + + // start AP Mode + server.on("/apmode", HTTP_GET, [](AsyncWebServerRequest *request) { + ESPAsync_WiFiManager wifiManager(&server,&dns); + wifiManager.resetSettings(); + + ESP.restart(); + request->send_P(200, "text/html", index_html, processor); + }); + + // restart system + server.on("/restart", HTTP_GET, [](AsyncWebServerRequest *request) { + DBG_PRINTLN("/restart"); + ESP.restart(); + request->send_P(200, "text/html", index_html, processor); + }); + + server.on("/acoutput", HTTP_GET, [] (AsyncWebServerRequest *request) { + String parm1; + + if (request->hasParam("value") ) { + parm1 = request->getParam("value")->value(); + DBG_PRINT("/acoutput?value = "); + DBG_PRINTLN(parm1); + + if(parm1.equals("/s0") ){ + soyo_power = 0; + sprintf(msgData, "%d", soyo_power); + if(checkbox_mqttenabled){ + client.publish(topic_power, msgData); + } + } + else if(parm1.equals("/p1")){ + soyo_power +=1; + sprintf(msgData, "%d", soyo_power); + if(checkbox_mqttenabled){ + client.publish(topic_power, msgData); + } + } + else if(parm1.equals("/p10")){ + soyo_power +=10; + sprintf(msgData, "%d", soyo_power); + if(checkbox_mqttenabled){ + client.publish(topic_power, msgData); + } + } + else if(parm1.equals("/m1")){ + soyo_power -=1; + if(soyo_power < 0){ + soyo_power = 0; + } + sprintf(msgData, "%d", soyo_power); + if(checkbox_mqttenabled){ + client.publish(topic_power, msgData); + } + } + else if(parm1.equals("/m10")){ + soyo_power -=10; + if(soyo_power < 0){ + soyo_power = 0; + } + sprintf(msgData, "%d", soyo_power); + if(checkbox_mqttenabled){ + client.publish(topic_power, msgData); + } + } + } + request->send_P(200, "text/html", index_html, processor); + }); + + + server.on("/checkbox", HTTP_GET, [] (AsyncWebServerRequest *request) { + String checkbox_id; + String checkbox_value; + + if (request->hasParam("cbid") && request->hasParam("state")) { + checkbox_id = request->getParam("cbid")->value(); + checkbox_value = request->getParam("state")->value(); + + if(checkbox_id.equals("CBTIMER1")){ + if(checkbox_value.equals("1")){ + checkbox_timer1 = true; + } else { + checkbox_timer1 = false; + } + } + else if(checkbox_id.equals("CBTIMER2")){ + if(checkbox_value.equals("1")){ + checkbox_timer2 = true; + } else { + checkbox_timer2 = false; + } + } + else if(checkbox_id.equals("CBMQTTSTATE")){ + if(checkbox_value.equals("1")){ + checkbox_mqttenabled = true; + } else { + checkbox_mqttenabled = false; + } + } + else if(checkbox_id.equals("CBNULL")){ + if(checkbox_value.equals("1")){ + checkbox_nulleinspeisung = true; + } else { + checkbox_nulleinspeisung = false; + soyo_power = 0; + } + } + else if(checkbox_id.equals("CBBATSCHUTZ")){ + if(checkbox_value.equals("1")){ + checkbox_batschutz = true; + } else { + checkbox_batschutz = false; + output_enabled = true; //wenn batschutz aus, dann freigabe fuer soyo output + } + } + else if(checkbox_id.equals("CBMETERL1")){ + if(checkbox_value.equals("1")){ + checkbox_meter_l1 = true; + } else { + checkbox_meter_l1 = false; + } + } + else if(checkbox_id.equals("CBMETERL2")){ + if(checkbox_value.equals("1")){ + checkbox_meter_l2 = true; + } else { + checkbox_meter_l2 = false; + } + } + else if(checkbox_id.equals("CBMETERL3")){ + if(checkbox_value.equals("1")){ + checkbox_meter_l3 = true; + } else { + checkbox_meter_l3 = false; + } + } + } + request->send_P(200, "text/html", index_html, processor); + }); + + + server.on("/savesettings", HTTP_GET, [] (AsyncWebServerRequest *request) { + String value; + + value = request->getParam("t1")->value(); + memset(timer1_time, 0, sizeof(timer1_time)); + strcat(timer1_time, value.c_str()); + + value = request->getParam("w1")->value(); + timer1_watt = atoi(value.c_str()); + + value = request->getParam("t2")->value(); + memset(timer2_time, 0, sizeof(timer2_time)); + strcat(timer2_time, value.c_str()); + + value = request->getParam("w2")->value(); + timer2_watt = atoi(value.c_str()); + + value = request->getParam("maxwatt")->value(); + maxwatt = atoi(value.c_str()); + + value = request->getParam("meteripaddr")->value(); + memset(meteripaddr, 0, sizeof(meteripaddr)); + strcat(meteripaddr, value.c_str()); + + value = request->getParam("tout")->value(); + teiler_output = atoi(value.c_str()); + + value = request->getParam("meterinterval")->value(); + meterinterval = atol(value.c_str()); + + value = request->getParam("nullinterval")->value(); + nullinterval = atol(value.c_str()); + + value = request->getParam("nulloffset")->value(); + nulloffset = atoi(value.c_str()); + + value = request->getParam("mqttserver")->value(); + memset(mqtt_server, 0, sizeof(mqtt_server)); + strcat(mqtt_server, value.c_str()); + + value = request->getParam("mqttport")->value(); + memset(mqtt_port, 0, sizeof(mqtt_port)); + strcat(mqtt_port, value.c_str()); + + value = request->getParam("mqttbatvol")->value(); + memset(mqtt_topic_bat_voltage, 0, sizeof(mqtt_topic_bat_voltage)); + strcat(mqtt_topic_bat_voltage, value.c_str()); + + value = request->getParam("mqttbatsoc")->value(); + memset(mqtt_topic_bat_soc, 0, sizeof(mqtt_topic_bat_soc)); + strcat(mqtt_topic_bat_soc, value.c_str()); + + value = request->getParam("batsocstop")->value(); + batsocstop = atoi(value.c_str()); + + value = request->getParam("batsocstart")->value(); + batsocstart = atoi(value.c_str()); + + saveConfig(); + + meter_ip = String(meteripaddr); + + request->send_P(200, "text/html", index_html, processor); + }); + + AsyncElegantOTA.begin(&server); + server.onNotFound(notFound); + server.addHandler(&events); + server.begin(); + + rssi = WiFi.RSSI(); + + meter_model = getMeterType(); // get shelly typ, 3em / 3empro + + digitalWrite(SERIAL_COMMUNICATION_CONTROL_PIN, RS485_TX_PIN_VALUE); // RS485 Modul -> set board to transmit + } + + // end setup() +} + + +void loop() { + + if(checkbox_mqttenabled){ + if (!client.connected()) { + DBG_PRINTLN("lost mqtt connection -> start reconncect"); + reconnect(); + } + client.loop(); + } + + + // send current power to SoyoSource + if ((millis() - lastTimerSoyoSource) > timerSoyoSource) { + + if(checkbox_batschutz == true && output_enabled == false){ // wenn batterie soc < limit dann soyo_power = 0 + soyo_power = 0; + } + + new_soyo_power = soyo_power / teiler_output; // Last auf mehrere Soyo's aufteilen + if(new_soyo_power < 0){ + new_soyo_power = 0; + } + + //sendSoyoPowerData(soyo_power); + sendSoyoPowerData(new_soyo_power); + + if(new_soyo_power != old_soyo_power) { // nur für Debug, damit nur Laständerungen ausgegeben werden + old_soyo_power = new_soyo_power; + sprintf(dbgbuffer,"new soyo_power = %i ( %02X %02X %02X %02X %02X %02X %02X %02X )",new_soyo_power, soyo_power_data[0],soyo_power_data[1],soyo_power_data[2],soyo_power_data[3],soyo_power_data[4],soyo_power_data[5],soyo_power_data[6],soyo_power_data[7]); + DBG_PRINTLN(dbgbuffer); + } + + if(checkbox_mqttenabled){ + sprintf(msgData, "%d", soyo_power); + client.publish(topic_power, msgData); + } + + lastTimerSoyoSource = millis(); + } + + + // timer to get Shelly3EM data + if ((millis() - lastMeterinterval) > meterinterval) { + if (meter_model > 0){ + meter_power = getMeterData(meter_model); + } else{ + meter_model = getMeterType(); + DBG_PRINTLN("Kein Shelly erkannt! Bitte IP eintragen, speichern und ESP neu starten."); + } + + lastMeterinterval = millis(); + } + + + // timer to manage Nulleinspeisung + if ((millis() - lastNullinterval) > nullinterval) { + if(checkbox_nulleinspeisung && output_enabled){ + if(meter_power > nulloffset + 10){ + soyo_power += meter_power - nulloffset; + + if(soyo_power > maxwatt){ + soyo_power = maxwatt; + } + } + + if(meter_power < 0 + nulloffset ){ + soyo_power += meter_power - nulloffset; + + if(soyo_power < 0){ + soyo_power = 0; + } + } + + } + lastNullinterval = millis(); + } + + + // timer für uptime, SoyoSource Timer und BatSOCLimit + if ((millis() - lastTimerUptime) > timerUptime) { + myUptime(); + + if(checkbox_timer1 || checkbox_timer2){ + checkTimer(); + } + + // check ob Batterie SOC < oder > eingestelltem Limit + float mqttbatsoc_float = mqtt_bat_soc + 0.5; + int mqttbatsoc_int = (int)mqttbatsoc_float; + + if(checkbox_batschutz == true && mqttbatsoc_int > 1){ // falls mqtt noch nicht verbunden oder nicht aktiv + if(mqttbatsoc_int <= batsocstop){ + output_enabled = false; + }else if(mqttbatsoc_int >= batsocstart){ + output_enabled = true; + } + } + + lastTimerUptime = millis(); + } + + +}