Coś tam poprawiłem, szkoda ze nie ma ochotnika do testów.
Code: Select all
#include <SuplaDevice.h>
#include <supla/network/esp_wifi.h>
#include <supla/control/relay.h>
#include <supla/control/button.h>
#include <supla/control/hvac_base.h>
#include <supla/clock/clock.h>
#include <supla/network/html/time_parameters.h>
#include <supla/network/html/hvac_parameters.h>
#include <supla/control/internal_pin_output.h>
#include <supla/device/status_led.h>
#include <supla/control/action_trigger.h>
// ======================================================
// STORAGE
// ======================================================
#include <supla/storage/storage.h>
#include <supla/storage/config.h>
#include <supla/storage/littlefs_config.h>
// ======================================================
// WWW
// ======================================================
#include <supla/network/esp_web_server.h>
#include <supla/network/html/device_info.h>
#include <supla/network/html/protocol_parameters.h>
#include <supla/network/html/status_led_parameters.h>
#include <supla/network/html/wifi_parameters.h>
#include <supla/device/supla_ca_cert.h>
// ======================================================
// OTA
// ======================================================
#ifdef ARDUINO_ARCH_ESP32
#include <HTTPUpdateServer.h>
#else
#include <ESP8266HTTPUpdateServer.h>
#endif
// ======================================================
// DS18B20
// ======================================================
#include <OneWire.h>
#include <DallasTemperature.h>
#include <supla/sensor/thermometer.h>
// ======================================================
// GPIO - ESP32 Relay X8
// ======================================================
#define STATUS_LED_GPIO 23
#define BUTTON_CFG_GPIO 0
#define RELAY_1_GPIO 32
#define RELAY_2_GPIO 33
#define RELAY_3_GPIO 25
#define RELAY_4_GPIO 26
#define RELAY_5_GPIO 27
#define RELAY_6_GPIO 14
#define RELAY_7_GPIO 12
#define RELAY_8_GPIO 13
#define ONE_WIRE_BUS 16
#define LIGHT_BUTTON_GPIO 17
// ======================================================
// CONFIG & VARIABLES
// ======================================================
const char* const SENSOR_NAMES[] = {
"Woda w basenie",
"Woda w solarach",
"Woda powrotna",
"Powietrze"
};
const int SENSOR_COUNT = sizeof(SENSOR_NAMES) / sizeof(SENSOR_NAMES[0]);
Supla::ESPWifi wifi;
Supla::LittleFsConfig configSupla;
Supla::Device::StatusLed statusLed(STATUS_LED_GPIO, false);
Supla::EspWebServer suplaServer;
#ifdef ARDUINO_ARCH_ESP32
HTTPUpdateServer httpUpdater;
#else
ESP8266HTTPUpdateServer httpUpdater;
#endif
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress discoveredAddresses[10];
int discoveredCount = 0;
Supla::Control::Relay* relayLock = nullptr;
Supla::Control::Relay* relayPump = nullptr;
Supla::Control::Relay* relayChlorine = nullptr;
Supla::Control::Relay* relayHeatPump = nullptr;
Supla::Control::Relay* relayUV = nullptr;
Supla::Control::Relay* relayLight = nullptr;
Supla::Control::HvacBase* hvacPool = nullptr;
bool depChlorine = false;
bool depUV = false;
bool lockPump = false;
bool lockChlorine = false;
bool lockHeating = false;
bool lockUV = false;
unsigned long heatPumpStartTime = 0;
bool heatPumpRunningInAuto = false;
// Scan function for DS18B20
void scanDS18B20() {
pinMode(ONE_WIRE_BUS, INPUT_PULLUP); delay(250); sensors.begin(); delay(50);
discoveredCount = 0; DeviceAddress tempAddr; oneWire.reset_search();
while (oneWire.search(tempAddr) && discoveredCount < 10) {
if (sensors.validAddress(tempAddr) && tempAddr[0] == 0x28) {
memcpy(discoveredAddresses[discoveredCount], tempAddr, sizeof(DeviceAddress));
discoveredCount++;
}
}
}
// ======================================================
// DS18B20 CUSTOM CLASS
// ======================================================
class ConfigurableDS18B20 : public Supla::Sensor::Thermometer {
public:
ConfigurableDS18B20(int sensorNumber) : sensorNum(sensorNumber) {
memset(deviceAddress, 0, sizeof(deviceAddress));
}
void onInit() override {
loadAddress();
channel.setNewValue(getValue());
}
double getValue() override {
if (deviceAddress[0] == 0 && deviceAddress[7] == 0) return -275.0;
sensors.requestTemperaturesByAddress(deviceAddress);
double temp = sensors.getTempC(deviceAddress);
if (temp == DEVICE_DISCONNECTED_C) return -275.0;
return temp;
}
void loadAddress() {
auto cfg = Supla::Storage::ConfigInstance();
if (!cfg) return;
char key[20];
for (int i = 0; i < 8; i++) {
snprintf(key, sizeof(key), "ds_%d_byte_%d", sensorNum, i);
uint8_t b = 0;
cfg->getUInt8(key, &b);
deviceAddress[i] = b;
}
}
protected:
int sensorNum;
DeviceAddress deviceAddress;
};
ConfigurableDS18B20* dsSensors[SENSOR_COUNT];
// ======================================================
// FUNKCJA OBSŁUGI ZAPISU FORMULARZA SMARTPOOL (POST /smartpool)
// ======================================================
void handleSmartPoolSaveAction() {
auto server = suplaServer.getServerPtr();
if (!server) return;
auto cfg = Supla::Storage::ConfigInstance();
if (cfg) {
// 1. Blokady
cfg->setUInt8("lock_pump", server->hasArg("lock_pump") ? 1 : 0);
cfg->setUInt8("lock_chlor", server->hasArg("lock_chlor") ? 1 : 0);
cfg->setUInt8("lock_heat", server->hasArg("lock_heat") ? 1 : 0);
cfg->setUInt8("lock_uv", server->hasArg("lock_uv") ? 1 : 0);
// 2. Zależności pompy
cfg->setUInt8("dep_chlorine", server->hasArg("dep_chlorine") ? 1 : 0);
cfg->setUInt8("dep_uv", server->hasArg("dep_uv") ? 1 : 0);
// 3. Przypisanie czujników
for (int t = 0; t < SENSOR_COUNT; t++) {
char argName[20];
snprintf(argName, sizeof(argName), "ds_assign_%d", t);
if (server->hasArg(argName)) {
int selectedIdx = server->arg(argName).toInt();
char idxKey[20]; snprintf(idxKey, sizeof(idxKey), "ds_idx_%d", t);
cfg->setUInt8(idxKey, selectedIdx);
char byteKey[20];
for (int i = 0; i < 8; i++) {
snprintf(byteKey, sizeof(byteKey), "ds_%d_byte_%d", t, i);
cfg->setUInt8(byteKey, (selectedIdx >= 0 && selectedIdx < discoveredCount) ? discoveredAddresses[selectedIdx][i] : 0);
}
}
}
// 4. Pompa ogrzewania
if (server->hasArg("heat_on_t1")) cfg->setUInt8("heat_on_t1", server->arg("heat_on_t1").toInt());
if (server->hasArg("heat_on_t2")) cfg->setUInt8("heat_on_t2", server->arg("heat_on_t2").toInt());
if (server->hasArg("heat_on_diff")) cfg->setInt32("heat_on_diff", server->arg("heat_on_diff").toInt());
if (server->hasArg("heat_off_t1")) cfg->setUInt8("heat_off_t1", server->arg("heat_off_t1").toInt());
if (server->hasArg("heat_off_t2")) cfg->setUInt8("heat_off_t2", server->arg("heat_off_t2").toInt());
if (server->hasArg("heat_off_diff")) cfg->setInt32("heat_off_diff", server->arg("heat_off_diff").toInt());
if (server->hasArg("heat_min_time")) cfg->setInt32("heat_min_time", server->arg("heat_min_time").toInt());
cfg->commit();
for (int i = 0; i < SENSOR_COUNT; i++) {
if (dsSensors[i]) dsSensors[i]->loadAddress();
}
}
server->sendHeader("Location", "/smartpool?saved=1");
server->send(303, "text/plain", "");
}
// ======================================================
// GENEROWANIE NOWOCZESNEJ PODSTRONY SMARTPOOL (GET /smartpool)
// ======================================================
void handleSmartPoolPage() {
auto server = suplaServer.getServerPtr();
if (!server) return;
auto cfg = Supla::Storage::ConfigInstance();
String html = "";
html += "<!DOCTYPE html><html lang=\"pl\"><head><meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">"
"<title>SmartPool Settings</title>"
"<style>"
"*, ::before, ::after { box-sizing: border-box; }"
"html:focus-within { scroll-behavior: smooth; }"
"body, html, h1, h3, p { margin: 0; padding: 0; font-family: HelveticaNeue, \"Helvetica Neue\", Helvetica, Arial, sans-serif; }"
"body { background: #00d151; min-height: 100vh; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; font-size: 14px; font-weight: 400; color: #fff; padding-top: 40px; }"
".wrapper { width: 100%; max-width: 500px; padding: 20px 10px; }"
".box { background: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 5px 6px rgba(0, 0, 0, 0.3); margin-bottom: 20px; color: #000; }"
"h1 { font-weight: 300; font-size: 23px; margin-bottom: 20px; text-align: left; letter-spacing: 1px; }"
".box h3 { margin-top: 0; margin-bottom: 20px; font-weight: 300; font-size: 23px; text-align: left; color: #000; border-bottom: 1px solid #f0f0f0; padding-bottom: 10px; }"
"select, input[type=number] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 14px; background: #fff; color: #333; outline: none; box-sizing: border-box; }"
"select:focus, input[type=number]:focus { border-color: #00d151; }"
"input[type=number] { text-align: center; }"
".sp-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }"
".sp-switch input { opacity: 0; width: 0; height: 0; }"
".sp-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 24px; }"
".sp-slider:before { position: absolute; content: \"\"; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; }"
"input:checked + .sp-slider { background-color: #00d151; }"
"input:checked + .sp-slider:before { transform: translateX(20px); }"
".sp-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #f4f4f4; }"
".sp-row:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }"
".sp-row label { color: #333; font-weight: 500; margin: 0; cursor: pointer; }"
".ds-table { width: 100%; border-collapse: collapse; }"
".ds-table td { padding: 8px 0; vertical-align: middle; }"
".ds-label { font-weight: 500; color: #333; width: 45%; }"
".btn-group { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }"
"button, .btn-link { background: #000; width: 100%; border: none; border-radius: 10px; padding: 14px; text-transform: uppercase; color: #fff; font-size: 16px; font-weight: bold; cursor: pointer; box-shadow: 0 5px 6px rgba(0, 0, 0, 0.3); text-align: center; text-decoration: none; display: block; font-family: inherit; box-sizing: border-box; }"
"#logo { display: block; margin: 0 auto 10px; height: 130px; width: auto; fill: #000; }"
".alert { background: #fff; color: #00d151; padding: 14px; border-radius: 10px; margin-bottom: 20px; text-align: center; font-weight: bold; box-shadow: 0 5px 6px rgba(0,0,0,0.2); font-size: 15px; transition: opacity 0.5s ease; opacity: 1; }"
".section-subtitle { margin: 15px 0 8px 0; font-weight: bold; color: #00d151; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; }"
"@media screen and (max-width: 530px) { .wrapper { padding: 20px 6vw; } }"
"</style></head><body><div class=\"wrapper\">";
// Wstrzyknięcie oryginalnego logotypu Supli
html += "<svg id=\"logo\" version=\"1.1\" viewBox=\"0 0 200 200\" xml:space=\"preserve\">"
"<path d=\"M59.3,2.5c18.1,0.6,31.8,8,40.2,23.5c3.1,5.7,4.3,11.9,4.1,18.3c-0.1,3.6-0.7,7.1-1.9,10.6c-0.2,0.7-0.1,1.1,0.6,1.5c12.8,7.7,25.5,15.4,38.3,23c2.9,1.7,5.8,3.4,8.7,5.3c1,0.6,1.6,0.6,2.5-0.1c4.5-3.6,9.8-5.3,15.7-5.4c12.5-0.1,22.9,7.9,25.2,19c1.9,9.2-2.9,19.2-11.8,23.9c-8.4,4.5-16.9,4.5-25.5,0.2c-0.7-0.3-1-0.2-1.5,0.3c-4.8,4.9-9.7,9.8-14.5,14.6c-5.3,5.3-10.6,10.7-15.9,16c-1.8,1.8-3.6,3.7-5.4,5.4c-0.7,0.6-0.6,1,0,1.6c3.6,3.4,5.8,7.5,6.2,12.2c0.7,7.7-2.2,14-8.8,18.5c-12.3,8.6-30.3,3.5-35-10.4c-2.8-8.4,0.6-17.7,8.6-22.8c0.9-0.6,1.1-1,0.8-2c-2-6.2-4.4-12.4-6.6-18.6c-6.3-17.6-12.7-35.1-19-52.7c-0.2-0.7-0.5-1-1.4-0.9c-12.5,0.7-23.6-2.6-33-10.4c-8-6.6-12.9-15-14.2-25c-1.5-11.5,1.7-21.9,9.6-30.7C32.5,8.9,42.2,4.2,53.7,2.7c0.7-0.1,1.5-0.2,2.2-0.2C57,2.4,58.2,2.5,59.3,2.5z M76.5,81c0,0.1,0.1,0.3,0.1,0.6c1.6,6.3,3.2,12.6,4.7,18.9c4.5,17.7,8.9,35.5,13.3,53.2c0.2,0.9,0.6,1.1,1.6,0.9c5.4-1.2,10.7-0.8,15.7,1.6c0.8,0.4,1.2,0.3,1.7-0.4c11.2-12.9,22.5-25.7,33.4-38.7c0.5-0.6,0.4-1,0-1.6c-5.6-7.9-6.1-16.1-1.3-24.5c0.5-0.8,0.3-1.1-0.5-1.6c-9.1-4.7-18.1-9.3-27.2-14c-6.8-3.5-13.5-7-20.3-10.5c-0.7-0.4-1.1-0.3-1.6,0.4c-1.3,1.8-2.7,3.5-4.3,5.1c-4.2,4.2-9.1,7.4-14.7,9.7C76.9,80.3,76.4,80.3,76.5,81z M89,42.6c0.1-2.5-0.4-5.4-1.5-8.1C83,23.1,74.2,16.9,61.7,15.8c-10-0.9-18.6,2.4-25.3,9.7c-8.4,9-9.3,22.4-2.2,32.4c6.8,9.6,19.1,14.2,31.4,11.9C79.2,67.1,89,55.9,89,42.6z M102.1,188.6c0.6,0.1,1.5-0.1,2.4-0.2c9.5-1.4,15.3-10.9,11.6-19.2c-2.6-5.9-9.4-9.6-16.8-8.6c-8.3,1.2-14.1,8.9-12.4,16.6C88.2,183.9,94.4,188.6,102.1,188.6z M167.7,88.5c-1,0-2.1,0.1-3.1,0.3c-9,1.7-14.2,10.6-10.8,18.6c2.9,6.8,11.4,10.3,19,7.8c7.1-2.3,11.1-9.1,9.6-15.9C180.9,93,174.8,88.5,167.7,88.5z\"></path>"
"</svg>";
html += "<h1>SMARTPOOL</h1>";
if (server->hasArg("saved")) {
html += "<div id=\"savedAlert\" class=\"alert\">Ustawienia zostały pomyślnie zapisane!</div>";
}
html += "<form action=\"/smartpool\" method=\"POST\">";
// Boks 1: Przypisanie czujników
html += "<div class=\"box\"><h3>Czujniki DS18B20</h3><table class=\"ds-table\">";
for (int t = 0; t < SENSOR_COUNT; t++) {
uint8_t selectedIdx = 255;
if (cfg) { char key[20]; snprintf(key, sizeof(key), "ds_idx_%d", t); cfg->getUInt8(key, &selectedIdx); }
html += "<tr><td class=\"ds-label\">" + String(SENSOR_NAMES[t]) + "</td><td>";
html += "<select name=\"ds_assign_" + String(t) + "\">";
html += "<option value=\"255\"" + String(selectedIdx == 255 ? " selected" : "") + ">Nie przypisany</option>";
for (int i = 0; i < discoveredCount; i++) {
String idStr = "ID: ";
for(int b=0; b<4; b++) { char hex[4]; snprintf(hex, sizeof(hex), "%02X", discoveredAddresses[i][b]); idStr += hex; }
html += "<option value=\"" + String(i) + "\"" + String(selectedIdx == i ? " selected" : "") + ">" + idStr + "...</option>";
}
html += "</select></td></tr>";
}
html += "</table></div>";
// Helper lambda dla przełączników
auto addToggleSwitch = [](String name, String label, uint8_t val) {
return "<div class=\"sp-row\">"
"<label for=\"" + name + "\">" + label + "</label>"
"<label class=\"sp-switch\">"
"<input type=\"checkbox\" id=\"" + name + "\" name=\"" + name + "\" value=\"1\"" + String(val ? " checked" : "") + ">"
"<span class=\"sp-slider\"></span>"
"</label></div>";
};
// Boks 2: Blokady główne
uint8_t l_pump = 0, l_chlor = 0, l_heat = 0, l_uv = 0;
if (cfg) { cfg->getUInt8("lock_pump", &l_pump); cfg->getUInt8("lock_chlor", &l_chlor); cfg->getUInt8("lock_heat", &l_heat); cfg->getUInt8("lock_uv", &l_uv); }
html += "<div class=\"box\"><h3>Zabezpieczenie - blokada</h3>";
html += addToggleSwitch("lock_pump", "Blokuj pompę filtracyjną przy STOP", l_pump);
html += addToggleSwitch("lock_chlor", "Blokuj generator chloru/ozonu przy STOP", l_chlor);
html += addToggleSwitch("lock_heat", "Blokuj pompę ogrzewania przy STOP", l_heat);
html += addToggleSwitch("lock_uv", "Blokuj lampę UV-C przy STOP", l_uv);
html += "</div>";
// Boks 3: Zależności pompy
uint8_t chlorine = 0, uv = 0;
if (cfg) { cfg->getUInt8("dep_chlorine", &chlorine); cfg->getUInt8("dep_uv", &uv); }
html += "<div class=\"box\"><h3>Zależności pracy urządzeń</h3>";
html += addToggleSwitch("dep_chlorine", "Generator chloru wymaga przepływu", chlorine);
html += addToggleSwitch("dep_uv", "Lampa UV-C wymaga przepływu", uv);
html += "</div>";
// Boks 4: Automatyka ogrzewania
uint8_t h_on_t1 = 0, h_on_t2 = 0, h_off_t1 = 0, h_off_t2 = 0; int32_t h_on_diff = 0, h_off_diff = 0, h_min_time = 0;
if (cfg) { cfg->getUInt8("heat_on_t1", &h_on_t1); cfg->getUInt8("heat_on_t2", &h_on_t2); cfg->getInt32("heat_on_diff", &h_on_diff); cfg->getUInt8("heat_off_t1", &h_off_t1); cfg->getUInt8("heat_off_t2", &h_off_t2); cfg->getInt32("heat_off_diff", &h_off_diff); cfg->getInt32("heat_min_time", &h_min_time); }
html += "<div class=\"box\"><h3>Automatyka ogrzewania</h3>";
html += "<div class=\"section-subtitle\">Warunek startu pompy</div>";
html += "<div style=\"display:flex; align-items:center; gap:8px; margin-bottom:15px;\"><select name=\"heat_on_t1\" style=\"flex:2;\">";
for (int i = 0; i < SENSOR_COUNT; i++) { html += "<option value=\"" + String(i) + "\"" + String(h_on_t1 == i ? " selected" : "") + ">" + String(SENSOR_NAMES[i]) + "</option>"; }
html += "</select><span style=\"font-weight:bold; color:#666;\"><</span><select name=\"heat_on_t2\" style=\"flex:2;\">";
for (int i = 0; i < SENSOR_COUNT; i++) { html += "<option value=\"" + String(i) + "\"" + String(h_on_t2 == i ? " selected" : "") + ">" + String(SENSOR_NAMES[i]) + "</option>"; }
html += "</select><span style=\"font-weight:bold; color:#666;\">o</span>";
html += "<input type=\"number\" name=\"heat_on_diff\" style=\"width:65px;\" value=\"" + String(h_on_diff) + "\"></div>";
html += "<div class=\"section-subtitle\">Warunek stopu pompy</div>";
html += "<div style=\"display:flex; align-items:center; gap:8px; margin-bottom:20px;\"><select name=\"heat_off_t1\" style=\"flex:2;\">";
for (int i = 0; i < SENSOR_COUNT; i++) { html += "<option value=\"" + String(i) + "\"" + String(h_off_t1 == i ? " selected" : "") + ">" + String(SENSOR_NAMES[i]) + "</option>"; }
html += "</select><span style=\"font-weight:bold; color:#666;\"><</span><select name=\"heat_off_t2\" style=\"flex:2;\">";
for (int i = 0; i < SENSOR_COUNT; i++) { html += "<option value=\"" + String(i) + "\"" + String(h_off_t2 == i ? " selected" : "") + ">" + String(SENSOR_NAMES[i]) + "</option>"; }
html += "</select><span style=\"font-weight:bold; color:#666;\">o</span>";
html += "<input type=\"number\" name=\"heat_off_diff\" style=\"width:65px;\" value=\"" + String(h_off_diff) + "\"></div>";
html += "<div style=\"display:flex; align-items:center; justify-content:space-between; border-top:1px solid #f0f0f0; padding-top:15px;\">"
"<label style=\"color:#333; font-weight:500;\">Minimalny czas pracy (min.)</label>"
"<input type=\"number\" name=\"heat_min_time\" style=\"width:75px;\" value=\"" + String(h_min_time) + "\"></div></div>";
// Przyciski akcji
html += "<div class=\"btn-group\">";
html += "<button type=\"button\" onclick=\"window.location.href='/'\">SUPLA SETTINGS</button>";
html += "<button type=\"button\" onclick=\"window.location.href='/update'\">UPDATE FIRMWARE</button>";
html += "<button type=\"submit\">SAVE</button>";
html += "</div>";
html += "</form></div>";
// Skrypt JavaScript ukrywający komunikat
html += "<script>"
"setTimeout(function() {"
" var alertEl = document.getElementById('savedAlert');"
" if (alertEl) {"
" alertEl.style.opacity = '0';"
" setTimeout(function() { alertEl.style.display = 'none'; }, 500);"
" }"
"}, 3000);"
"</script>";
html += "</body></html>";
server->send(200, "text/html", html);
}
// ======================================================
// KLASA GENERUJĄCA LINK W GŁÓWNYM FORMULARZU
// ======================================================
class SmartPoolMenuLink : public Supla::HtmlElement {
public:
SmartPoolMenuLink() : HtmlElement(Supla::HTML_SECTION_FORM) {}
void send(Supla::WebSender* sender) override {
sender->send("</div>"
"<button type=\"button\" onclick=\"window.location.href='/smartpool'\">SMARTPOOL SETTINGS</button>"
"<button type=\"button\" onclick=\"window.location.href='/update'\">UPDATE FIRMWARE</button>"
"<div>");
}
};
// ======================================================
// MAIN RELAY LOGIC
// ======================================================
void handleRelayDependencies() {
auto cfg = Supla::Storage::ConfigInstance();
if (cfg) {
uint8_t temp = 0;
cfg->getUInt8("lock_pump", &temp); lockPump = (temp == 1);
cfg->getUInt8("lock_chlor", &temp); lockChlorine = (temp == 1);
cfg->getUInt8("lock_heat", &temp); lockHeating = (temp == 1);
cfg->getUInt8("lock_uv", &temp); lockUV = (temp == 1);
cfg->getUInt8("dep_chlorine", &temp); depChlorine = (temp == 1);
cfg->getUInt8("dep_uv", &temp); depUV = (temp == 1);
}
bool mainLockActive = (relayLock && relayLock->isOn());
if (mainLockActive) {
if (lockPump && relayPump && relayPump->isOn()) { relayPump->turnOff(); relayPump->getChannel()->setNewValue(false); }
if (lockChlorine && relayChlorine && relayChlorine->isOn()) { relayChlorine->turnOff(); relayChlorine->getChannel()->setNewValue(false); }
if (lockHeating && relayHeatPump && relayHeatPump->isOn()) { relayHeatPump->turnOff(); relayHeatPump->getChannel()->setNewValue(false); heatPumpRunningInAuto = false; }
if (lockUV && relayUV && relayUV->isOn()) { relayUV->turnOff(); relayUV->getChannel()->setNewValue(false); }
}
if (relayPump && !(mainLockActive && lockPump)) {
static bool lastPumpState = false;
bool currentPumpState = relayPump->isOn();
if (lastPumpState == true && currentPumpState == false) {
if (depChlorine && relayChlorine && relayChlorine->isOn()) { relayChlorine->turnOff(); }
if (depUV && relayUV && relayUV->isOn()) { relayUV->turnOff(); }
lastPumpState = currentPumpState;
return;
}
if (!currentPumpState) {
if (depChlorine && relayChlorine && relayChlorine->isOn()) { relayPump->turnOn(); currentPumpState = true; }
else if (depUV && relayUV && relayUV->isOn()) { relayPump->turnOn(); currentPumpState = true; }
}
if (!currentPumpState) {
if (depChlorine && relayChlorine && relayChlorine->isOn()) { relayChlorine->turnOff(); }
if (depUV && relayUV && relayUV->isOn()) { relayUV->turnOff(); }
}
lastPumpState = currentPumpState;
}
if (hvacPool && relayHeatPump) {
if (mainLockActive && lockHeating) {
if (relayHeatPump->isOn()) { relayHeatPump->turnOff(); relayHeatPump->getChannel()->setNewValue(false); }
heatPumpRunningInAuto = false;
return;
}
bool hvacEnabled = hvacPool->getMode() != SUPLA_HVAC_MODE_OFF;
bool hvacHeating = false;
double currentTemp = dsSensors[0]->getValue();
double targetTemp = hvacPool->getTemperatureSetpointHeat() / 100.0;
double hysteresis = hvacPool->getCurrentHysteresis(false) / 100.0;
if (hvacEnabled && currentTemp > -100 && currentTemp < (targetTemp - hysteresis)) { hvacHeating = true; }
if (hvacEnabled && hvacHeating) {
uint8_t on_t1_idx = 0, on_t2_idx = 0, off_t1_idx = 0, off_t2_idx = 0;
int32_t on_diff = 0, off_diff = 0, min_time_min = 0;
if (cfg) {
cfg->getUInt8("heat_on_t1", &on_t1_idx); cfg->getUInt8("heat_on_t2", &on_t2_idx); cfg->getInt32("heat_on_diff", &on_diff);
cfg->getUInt8("heat_off_t1", &off_t1_idx); cfg->getUInt8("heat_off_t2", &off_t2_idx); cfg->getInt32("heat_off_diff", &off_diff);
cfg->getInt32("heat_min_time", &min_time_min);
}
double t_on1 = dsSensors[on_t1_idx]->getValue(); double t_on2 = dsSensors[on_t2_idx]->getValue();
double t_off1 = dsSensors[off_t1_idx]->getValue(); double t_off2 = dsSensors[off_t2_idx]->getValue();
unsigned long minTimeMs = (unsigned long)min_time_min * 60000;
bool currentPumpState = relayHeatPump->isOn();
if (!currentPumpState) {
if (t_on1 > -100 && t_on2 > -100 && (t_on1 < (t_on2 - on_diff))) { relayHeatPump->turnOn(); heatPumpStartTime = millis(); heatPumpRunningInAuto = true; }
}
else if (currentPumpState && heatPumpRunningInAuto) {
if (millis() - heatPumpStartTime >= minTimeMs) {
if (t_off1 > -100 && t_off2 > -100 && (t_off1 < (t_off2 + off_diff))) { relayHeatPump->turnOff(); heatPumpRunningInAuto = false; }
}
}
}
else {
if (heatPumpRunningInAuto && relayHeatPump->isOn()) {
int32_t min_time_min = 0; if (cfg) cfg->getInt32("heat_min_time", &min_time_min);
unsigned long minTimeMs = (unsigned long)min_time_min * 60000;
if (millis() - heatPumpStartTime >= minTimeMs) { relayHeatPump->turnOff(); heatPumpRunningInAuto = false; }
} else if (!hvacEnabled) {
heatPumpRunningInAuto = false;
}
}
}
}
// ======================================================
// SETUP
// ======================================================
void setup() {
Serial.begin(115200); delay(1000); Serial.println("\nSTART"); delay(500);
new Supla::Clock;
SuplaDevice.allowWorkInOfflineMode(0);
SuplaDevice.setName("SUPLA-SmartPool");
SuplaDevice.setSwVersion("SmartPool v1.5");
new Supla::Html::DeviceInfo(&SuplaDevice);
new Supla::Html::WifiParameters;
new Supla::Html::ProtocolParameters(false, false);
new Supla::Html::StatusLedParameters;
new Supla::Html::TimeParameters(&SuplaDevice);
// Rejestracja linku w menu głównym
new SmartPoolMenuLink();
relayLock = new Supla::Control::Relay(RELAY_1_GPIO, true);
relayPump = new Supla::Control::Relay(RELAY_2_GPIO, true);
relayChlorine = new Supla::Control::Relay(RELAY_3_GPIO, true);
relayHeatPump = new Supla::Control::Relay(RELAY_4_GPIO, true);
relayUV = new Supla::Control::Relay(RELAY_5_GPIO, true);
relayLight = new Supla::Control::Relay(RELAY_6_GPIO, true);
relayLock->getChannel()->setChannelNumber(0); relayLock->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH); relayLock->setInitialCaption("Zabezpieczenie");
relayPump->getChannel()->setChannelNumber(1); relayPump->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH); relayPump->setInitialCaption("Pompa filtracyjna");
relayChlorine->getChannel()->setChannelNumber(2); relayChlorine->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH); relayChlorine->setInitialCaption("Generator chloru i ozonu");
relayHeatPump->getChannel()->setChannelNumber(3); relayHeatPump->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH); relayHeatPump->setInitialCaption("Pompa ogrzewania");
relayUV->getChannel()->setChannelNumber(4); relayUV->setDefaultFunction(SUPLA_CHANNELFNC_LIGHTSWITCH); relayUV->setInitialCaption("Lampa UV-C");
relayLight->getChannel()->setChannelNumber(5); relayLight->setDefaultFunction(SUPLA_CHANNELFNC_LIGHTSWITCH); relayLight->setInitialCaption("Oświetlenie basenu");
pinMode(RELAY_7_GPIO, OUTPUT);
pinMode(RELAY_8_GPIO, OUTPUT);
digitalWrite(RELAY_7_GPIO, LOW);
digitalWrite(RELAY_8_GPIO, LOW);
// Poprawna konfiguracja przycisku i ActionTriggera
auto buttonLight = new Supla::Control::Button(LIGHT_BUTTON_GPIO, true, true);
buttonLight->setHoldTime(1000); buttonLight->setMulticlickTime(300);
auto atLight = new Supla::Control::ActionTrigger();
atLight->setRelatedChannel(relayLight);
atLight->attach(buttonLight);
buttonLight->addAction(Supla::TOGGLE, relayLight, Supla::ON_PRESS);
buttonLight->addAction(Supla::TOGGLE, relayLock, Supla::ON_HOLD);
scanDS18B20();
for (int i = 0; i < SENSOR_COUNT; i++) {
dsSensors[i] = new ConfigurableDS18B20(i);
dsSensors[i]->getChannel()->setChannelNumber(7 + i);
dsSensors[i]->setInitialCaption(SENSOR_NAMES[i]);
}
auto hvacOutput = new Supla::Control::InternalPinOutput(-1);
hvacPool = new Supla::Control::HvacBase(hvacOutput);
hvacPool->getChannel()->setChannelNumber(6);
hvacPool->setInitialCaption("Termostat Basenu");
hvacPool->setMainThermometerChannelNo(7);
hvacPool->setDefaultTemperatureRoomMin(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, 2000);
hvacPool->setDefaultTemperatureRoomMax(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, 4000);
hvacPool->setTemperatureSetpointHeat(3300);
hvacPool->setTemperatureHisteresis(10);
hvacPool->setTemperatureHisteresisMin(10);
hvacPool->setTemperatureHisteresisMax(1000);
auto buttonCfg = new Supla::Control::Button(BUTTON_CFG_GPIO, true, true);
buttonCfg->configureAsConfigButton(&SuplaDevice);
SuplaDevice.setSuplaCACert(suplaCACert);
SuplaDevice.setSupla3rdPartyCACert(supla3rdCACert);
SuplaDevice.begin(21);
// Bezpieczne przypisanie endpointów i HTTPUpdateServer PO wystartowaniu Supli
if(suplaServer.getServerPtr()) {
httpUpdater.setup(suplaServer.getServerPtr(), "/update");
suplaServer.getServerPtr()->on("/smartpool", HTTP_GET, handleSmartPoolPage);
suplaServer.getServerPtr()->on("/smartpool", HTTP_POST, handleSmartPoolSaveAction);
}
}
// ======================================================
// LOOP
// ======================================================
void loop() {
SuplaDevice.iterate();
// Nie obciążamy procesora ciągłym sprawdzaniem i odczytem Dallas (raz na 10 sekund)
static unsigned long lastDependencyCheck = 0;
if (millis() - lastDependencyCheck >= 10000) {
lastDependencyCheck = millis();
handleRelayDependencies();
}
if (hvacPool) {
bool hvacEnabled = hvacPool->getMode() != SUPLA_HVAC_MODE_OFF;
digitalWrite(RELAY_7_GPIO, hvacEnabled ? HIGH : LOW);
bool hvacHeating = false;
double currentTemp = dsSensors[0]->getValue();
double targetTemp = hvacPool->getTemperatureSetpointHeat() / 100.0;
double hysteresis = hvacPool->getCurrentHysteresis(false) / 100.0;
if (hvacEnabled && currentTemp > -100 && currentTemp < (targetTemp - hysteresis)) { hvacHeating = true; }
digitalWrite(RELAY_8_GPIO, hvacHeating ? HIGH : LOW);
}
static bool LOCAL_WEB_SERVER = false;
if (!LOCAL_WEB_SERVER && Supla::Network::IsReady()) {
LOCAL_WEB_SERVER = true;
SuplaDevice.handleAction(0, Supla::START_LOCAL_WEB_SERVER);
}
}
Nie wiem dlaczego termostat nie ustawia się na 33 tak jak podałem w kodzie.