SmartPool - pomoc przy kodzie ;)

User avatar
Lector
Posts: 2471
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

A tu zmienione piny, jako ze mam klawisz z LED to na nim sygnalizuje blokadę, przekaźniki przeszły o jeden niżej, tak ze teraz 6 sygnalizuję włączony harmonogram pompy filtracyjnej, 7 sygnalizuję włączony termostat = logika grzania, 8 sygnalizuje iż termostat grzeje czyli żądana temperatura nie jest osiągnięta.

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>

#include <supla/storage/storage.h>
#include <supla/storage/config.h>
#include <supla/storage/littlefs_config.h>

#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>

#include <HTTPUpdateServer.h>
#include <EEPROM.h>
#include <supla/storage/eeprom.h> 

#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_0_GPIO             18
#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{2048};
Supla::Eeprom eeprom;
Supla::Device::StatusLed statusLed(STATUS_LED_GPIO, false);
Supla::EspWebServer suplaServer;
HTTPUpdateServer httpUpdater;

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::Relay* relayHarmonogram = nullptr;
Supla::Control::HvacBase* hvacPool = nullptr;
bool depChlorine = false;
bool depUV = false;

bool lockPump = false;
bool lockChlorine = false;
bool lockHeating = false;
bool lockUV = false;

bool schControlDependencies = true;
bool schEnabled = true;
unsigned long heatPumpStartTime = 0;
bool heatPumpRunningInAuto = false;

// ======================================================
// OBSŁUGA EEPROM (ESP32)
// ======================================================
void zapiszWartoscEEPROM(int adres, uint8_t wartosc) {
  if (EEPROM.read(adres) != wartosc) { 
    EEPROM.write(adres, wartosc);
    EEPROM.commit();
  }
}

uint8_t odczytajWartoscEEPROM(int adres, uint8_t domyslnaWartosc) {
  uint8_t wartosc = EEPROM.read(adres);
  if (wartosc == 0xFF) {
    return domyslnaWartosc;
  }
  return wartosc;
}

// ======================================================
// 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++;
    }
  }
}

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];
    snprintf(key, sizeof(key), "ds_blob_%d", sensorNum);
    
    size_t blobSize = sizeof(deviceAddress);
    cfg->getBlob(key, (char*)deviceAddress, blobSize);
  }

 protected:
  int sensorNum;
  DeviceAddress deviceAddress;
};

ConfigurableDS18B20* dsSensors[SENSOR_COUNT];

// ======================================================
// FUNKCJE POMOCNICZE
// ======================================================
void setRelayBlocked(Supla::Control::Relay* relay, bool blocked) {
  if (!relay || !relay->getChannel()) return;
  if (blocked) {
    relay->turnOff();
    relay->getChannel()->setStateOffline();
  } else {
    relay->getChannel()->setStateOnline();
  }
}

// ======================================================
// ZAPIS FORMULARZA
// ======================================================
void handleSmartPoolSaveAction() {
  auto server = suplaServer.getServerPtr();
  if (!server) return;

  auto cfg = Supla::Storage::ConfigInstance();
  if (cfg) {
    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);
    cfg->setUInt8("dep_chlorine", server->hasArg("dep_chlorine") ? 1 : 0);
    cfg->setUInt8("dep_uv", server->hasArg("dep_uv") ? 1 : 0);
    cfg->setUInt8("p_sch_dep", server->hasArg("p_sch_dep") ? 1 : 0);

    // Zoptymalizowany zapis adresów czujników do jednego klucza (Blob)
    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 blobKey[20];
          snprintf(blobKey, sizeof(blobKey), "ds_blob_%d", t);

          if (selectedIdx >= 0 && selectedIdx < discoveredCount) {
             cfg->setBlob(blobKey, (const char*)discoveredAddresses[selectedIdx], 8);
          } else {
             uint8_t zeroAddress[8] = {0, 0, 0, 0, 0, 0, 0, 0};
             cfg->setBlob(blobKey, (const char*)zeroAddress, 8);
          }
       }
    }

    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", "");
}

// ======================================================
// STRONA WWW
// ======================================================
void handleSmartPoolPage() {
  auto server = suplaServer.getServerPtr();
  if (!server) return;
  auto cfg = Supla::Storage::ConfigInstance();

  String html = "";
  html += "<!doctype html><html lang=en><head><meta charset=UTF-8><meta name=viewport content=width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no><title>SUPLA-SmartPool</title><style>*,*::before,*::after{box-sizing:border-box}html:focus-within{scroll-behavior:smooth}html,body,h1,h2,h3,h4,p{font-family:HelveticaNeue,Helvetica,Arial,sans-serif;margin:0;padding:0}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5;font-size:14px;background:#00d151;color:#fff}h1,h3{margin:10px 0;font-weight:300;font-size:23px}.wrapper{display:flex;flex-direction:column;justify-content:center;min-height:100vh}.content{text-align:center;padding:20px 10px}.box{background:#fff;color:#000;border-radius:10px;padding:5px 10px;margin:10px 0;box-shadow:0 5px 6px rgb(0 0 0 / .3)}#msg{position:fixed;top:0;left:0;width:100%;height:119px;box-sizing:border-box;padding:15px 20px;background:#ffe836;color:#000;text-align:center;font-size:26px;box-shadow:0 2px 5px rgb(0 0 0 / .3);z-index:9999;display:flex;align-items:center;justify-content:center}.form{text-align:left;max-width:500px;margin:0 auto;margin-top:-40px;padding:10px;padding-top:70px}.form-field{display:flex;align-items:center;padding:8px 10px;border-top:1px solid #00d150;margin:0 -10px}.box>.form-field:first-of-type{border-top:0}.form-field label{width:250px;margin-right:5px;color:#00d151}@media screen and (max-width:530px){.form-field{flex-direction:column}.form-field label{width:100%;margin:3px 0}}.form-field input,.form-field select{width:100%;border:1px solid #ccc;border-radius:6px;padding:3px 8px;background:#fff}.form-field.right-checkbox label:last-child{width:100%;text-align:center}.switch{position:relative;display:inline-block;width:51px;height:25px}.switch input{opacity:0;width:0;height:0}.slider{position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:.4s;border-radius:34px}.slider:before{position:absolute;content:\"\";height:17px;width:17px;left:4px;bottom:4px;background-color:#fff;transition:.4s;border-radius:50%}input:checked+.slider{background-color:#00d151}input:checked+.slider:before{transform:translateX(26px)}button{display:block;color:#fff;background:#000;width:100%;border:0;border-radius:10px;padding:10px;text-transform:uppercase;cursor:pointer;font-size:1.3em;margin-top:15px}</style></head><body><div class=wrapper><div class=content><div class=form><h1>SmartPool</h1>";
  if (server->hasArg("saved")) html += "<div id=\"msg\">Data saved</div>";
  html += "<form action=/smartpool method=POST><input type=\"hidden\" name=\"supla_csrf\" value=\"" + server->arg("supla_csrf") + "\">";

  html += "<div class=box><h3>Czujniki DS18B20</h3>";
  for (int t = 0; t < SENSOR_COUNT; t++) {
    DeviceAddress savedAddr;
    memset(savedAddr, 0, sizeof(DeviceAddress));
    if (cfg) {
      char blobKey[20];
      snprintf(blobKey, sizeof(blobKey), "ds_blob_%d", t);
      cfg->getBlob(blobKey, (char*)savedAddr, 8);
    }

    bool isAssigned = false;
    for (int b = 0; b < 8; b++) {
      if (savedAddr[b] != 0) {
        isAssigned = true;
        break;
      }
    }

    html += "<div class=form-field><label for=ds_assign_" + String(t) + ">" + String(SENSOR_NAMES[t]) + "</label><div><select name=ds_assign_" + String(t) + " id=ds_assign_" + String(t) + ">";
    html += "<option value=255" + String(!isAssigned ? " selected" : "") + ">Nie przypisany</option>";
    
    for (int i = 0; i < discoveredCount; i++) {
      String idStr = "";
      for (int b = 0; b < 8; b++) {
        char hex[3];
        snprintf(hex, sizeof(hex), "%02X", discoveredAddresses[i][b]);
        idStr += hex;
      }

      bool match = true;
      for (int b = 0; b < 8; b++) {
        if (discoveredAddresses[i][b] != savedAddr[b]) {
          match = false;
          break;
        }
      }

      html += "<option value=\"" + String(i) + "\"" + String(match && isAssigned ? " selected" : "") + ">ID: " + idStr.substring(0, 8) + "..." + idStr.substring(12) + "</option>";
    }
    html += "</select></div></div>";
  }
  html += "</div>";

  uint8_t h_on_t1 = 0, h_on_t2 = 1, h_off_t1 = 0, h_off_t2 = 2;
  int32_t h_on_diff = 5, h_off_diff = 3, h_min_time = 5;
  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>Pompa ogrzewania</h3>";
  html += "<div class=form-field style=gap:6px;><label>Warunek startu pompy</label><select name=heat_on_t1>";
  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>&lt;</span><select name=heat_on_t2>";
  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>o</span><input type=number min=0 name=heat_on_diff style=width:60px value=\"" + String(h_on_diff) + "\"></div>";
  html += "<div class=form-field style=gap:6px;><label>Warunek stopu pompy</label><select name=heat_off_t1>";
  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>&lt;</span><select name=heat_off_t2>";
  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>o</span><input type=number min=0 name=heat_off_diff style=width:60px value=\"" + String(h_off_diff) + "\"></div>";
  html += "<div class=form-field style=justify-content:space-between;><label>Minimalny czas pracy (min.)</label><input type=number min=0 name=heat_min_time style=width:60px;text-align:center value=\"" + String(h_min_time) + "\"></div></div>";

  auto addToggleSwitch = [](String name, String label, uint8_t val) {
    return "<div class=\"form-field right-checkbox\"><label for=\"" + name + "\">" + label + "</label><label><span class=switch><input type=checkbox value=1 name=\"" + name + "\" id=\"" + name + "\"" + String(val ? " checked" : "") + "><span class=slider></span></span></label></div>";
  };

  uint8_t l_pump = 0, l_chlor = 0, l_heat = 0, l_uv = 0, depSch = 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);
    cfg->getUInt8("p_sch_dep", &depSch);
  }

  html += "<div class=box><h3>Zabezpieczenie - blokada</h3>";
  html += addToggleSwitch("lock_pump", "Pompa filtracyjna", l_pump);
  html += addToggleSwitch("lock_chlor", "Generator chloru i ozonu", l_chlor);
  html += addToggleSwitch("lock_heat", "Pompa ogrzewania", l_heat);
  html += addToggleSwitch("lock_uv", "Lampa UV-C", l_uv);
  html += "</div>";

  uint8_t chlorine = 0, uv = 0;
  if (cfg) {
    cfg->getUInt8("dep_chlorine", &chlorine);
    cfg->getUInt8("dep_uv", &uv);
  }
  html += "<div class=box><h3>Kanały zależne od pompy filtracyjnej</h3>";
  html += addToggleSwitch("dep_chlorine", "Generator chloru", chlorine);
  html += addToggleSwitch("dep_uv", "Lampa UV-C", uv);
  html += "</div>";

  html += "<div class=box><h3>Harmonogramy pompy filtracyjnej</h3>";
  html += addToggleSwitch("p_sch_dep", "Steruj kanałami zależnymi", depSch);
  html += "</div>";

  html += "<script>setTimeout(function(){var msgEl=document.getElementById('msg');if(msgEl){msgEl.remove();window.history.replaceState({},document.title,'/smartpool');}},3200);</script>";

  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 += "</form></div></div></div></body></html>";

  server->send(200, "text/html", html);
}

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>");
  }
};

// ======================================================
// HARMONOGRAM HVAC
// ======================================================
void checkHvacSchedulePump() {
  auto cfg = Supla::Storage::ConfigInstance();
  if (!cfg || !relayPump || !hvacPool || !relayHarmonogram) return;

  bool mainLockActive = (relayLock && relayLock->isOn());

  if (mainLockActive && lockPump) {
    return;
  }

  bool harmonogramActive = relayHarmonogram->isOn();
  if (!harmonogramActive) return;

  auto currentProgram = hvacPool->getCurrentProgram();
  double my_temp = currentProgram.SetpointTemperatureHeat / 100.0;

  bool shouldBeOn = false;
  if (my_temp >= 20.0 && my_temp <= 40.0) {
    shouldBeOn = true;
  } else {
    shouldBeOn = false;
  }

  bool pumpState = relayPump->isOn();
  if (shouldBeOn && !pumpState) {
    relayPump->turnOn();

    uint8_t dep = 0;
    cfg->getUInt8("p_sch_dep", &dep);
    if (dep == 1) {
      bool schDepChlorine = false;
      bool schDepUV = false;
      uint8_t temp = 0;

      cfg->getUInt8("dep_chlorine", &temp);
      schDepChlorine = (temp == 1);

      cfg->getUInt8("dep_uv", &temp);
      schDepUV = (temp == 1);

      bool mainLockActive = (relayLock && relayLock->isOn());

      cfg->getUInt8("lock_chlor", &temp);
      if (mainLockActive && temp == 1) schDepChlorine = false;

      cfg->getUInt8("lock_uv", &temp);
      if (mainLockActive && temp == 1) schDepUV = false;

      if (schDepChlorine && relayChlorine && !relayChlorine->isOn()) {
        relayChlorine->turnOn();
      }
      if (schDepUV && relayUV && !relayUV->isOn()) {
        relayUV->turnOn();
      }
    }
  } else if (!shouldBeOn && pumpState) {
    relayPump->turnOff();
  }

  if (shouldBeOn && pumpState) {
    uint8_t dep = 0;
    cfg->getUInt8("p_sch_dep", &dep);

    if (dep == 1) {
      bool schDepChlorine = false;
      bool schDepUV = false;
      uint8_t temp = 0;

      cfg->getUInt8("dep_chlorine", &temp);
      schDepChlorine = (temp == 1);

      cfg->getUInt8("dep_uv", &temp);
      schDepUV = (temp == 1);

      bool mainLockActive = (relayLock && relayLock->isOn());

      cfg->getUInt8("lock_chlor", &temp);
      if (mainLockActive && temp == 1) schDepChlorine = false;

      cfg->getUInt8("lock_uv", &temp);
      if (mainLockActive && temp == 1) schDepUV = false;

      if (schDepChlorine && relayChlorine && !relayChlorine->isOn()) {
        relayChlorine->turnOn();
      }

      if (schDepUV && relayUV && !relayUV->isOn()) {
        relayUV->turnOn();
      }
    }
  }
}

// ======================================================
// LOGIKA ZALEŻNOŚCI
// ======================================================
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());

  setRelayBlocked(relayPump, mainLockActive && lockPump);
  setRelayBlocked(relayChlorine, mainLockActive && lockChlorine);
  setRelayBlocked(relayHeatPump, mainLockActive && lockHeating);
  setRelayBlocked(relayUV, mainLockActive && lockUV);

  if (mainLockActive && lockHeating) {
    heatPumpRunningInAuto = 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;
  }
}

void handleRelayLocks() {
  bool mainLockActive = (relayLock && relayLock->isOn());
  setRelayBlocked(relayPump, mainLockActive && lockPump);
  setRelayBlocked(relayChlorine, mainLockActive && lockChlorine);
  setRelayBlocked(relayHeatPump, mainLockActive && lockHeating);
  setRelayBlocked(relayUV, mainLockActive && lockUV);
  if (mainLockActive && lockHeating) {
    heatPumpRunningInAuto = false;
  }
}

// ======================================================
// SETUP
// ======================================================
void setup() {
  Serial.begin(115200);
  delay(1000);
  
  EEPROM.begin(512);
  delay(10);

  new Supla::Clock;

  SuplaDevice.allowWorkInOfflineMode(0);
  SuplaDevice.setName("SUPLA-SmartPool");
  SuplaDevice.setSwVersion("SmartPool v2.0");

  new Supla::Html::DeviceInfo(&SuplaDevice);
  new Supla::Html::WifiParameters;
  new Supla::Html::ProtocolParameters(false, false);
  new Supla::Html::StatusLedParameters;
  new Supla::Html::TimeParameters(&SuplaDevice);
  new SmartPoolMenuLink();

  relayLock = new Supla::Control::Relay(RELAY_0_GPIO, true);
  relayPump = new Supla::Control::Relay(RELAY_1_GPIO, true);
  relayChlorine = new Supla::Control::Relay(RELAY_2_GPIO, true);
  relayHeatPump = new Supla::Control::Relay(RELAY_3_GPIO, true);
  relayUV = new Supla::Control::Relay(RELAY_4_GPIO, true);
  relayLight = new Supla::Control::Relay(RELAY_5_GPIO, true);
  relayHarmonogram = 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");

  relayHarmonogram->getChannel()->setChannelNumber(6);
  relayHarmonogram->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH);
  relayHarmonogram->setInitialCaption("Harmonogram pompy filtracyjnej");
  relayHarmonogram->setDefaultStateRestore();

  pinMode(RELAY_7_GPIO, OUTPUT);
  pinMode(RELAY_8_GPIO, OUTPUT);
  digitalWrite(RELAY_7_GPIO, LOW);
  digitalWrite(RELAY_8_GPIO, LOW);

  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);

  auto hvacOutput = new Supla::Control::InternalPinOutput(-1);
  hvacPool = new Supla::Control::HvacBase(hvacOutput);
  hvacPool->getChannel()->setChannelNumber(7);
  hvacPool->setInitialCaption("Termostat Basenu");
  hvacPool->setMainThermometerChannelNo(8);
  hvacPool->setDefaultTemperatureRoomMin(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, 2000);
  hvacPool->setDefaultTemperatureRoomMax(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, 4000);
  hvacPool->getChannel()->setHvacSetpointTemperatureHeat(3300);
  hvacPool->setTemperatureHisteresisMin(10);
  hvacPool->setTemperatureHisteresisMax(1000);
  hvacPool->setTemperatureHisteresis(50);
  scanDS18B20();
  for (int i = 0; i < SENSOR_COUNT; i++) {
    dsSensors[i] = new ConfigurableDS18B20(i);
    dsSensors[i]->getChannel()->setChannelNumber(8 + i);
    dsSensors[i]->setInitialCaption(SENSOR_NAMES[i]);
  }

  auto buttonCfg = new Supla::Control::Button(BUTTON_CFG_GPIO, true, true);
  buttonCfg->configureAsConfigButton(&SuplaDevice);

  SuplaDevice.setSuplaCACert(suplaCACert);
  SuplaDevice.setSupla3rdPartyCACert(supla3rdCACert);

  SuplaDevice.begin(21);
  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();
  handleRelayLocks();

  static unsigned long lastTimeAndTempCheck = 0;
  if (millis() - lastTimeAndTempCheck >= 10000) {
    lastTimeAndTempCheck = millis();
    checkHvacSchedulePump();
    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;
      
      // Zabezpieczenie: Warunek uruchomienia sprawdzany tylko, gdy odczyt jest większy od 0.0
      if (hvacEnabled && currentTemp > 0.0 && currentTemp < (targetTemp - hysteresis)) {
        hvacHeating = true;
      }
      bool mainLockActive = (relayLock && relayLock->isOn());
      if (mainLockActive && lockHeating) hvacHeating = false;
      digitalWrite(RELAY_8_GPIO, hvacHeating ? HIGH : LOW);
    }
  }

  handleRelayDependencies();

  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);
  }
}
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
klew
Posts: 13908
Joined: Thu Jun 27, 2019 12:16 pm
Location: Wrocław
Has thanked: 134 times
Been thanked: 137 times

Post

Lector wrote: Sun Jun 07, 2026 1:12 pm Jestem zamrożony przez brak odczytu adresów DS18B20 i temperatur.
Na 5 czujników jakie mam żaden mi nie działa, nawet na prostych przykładach pod ESP lub z GG.

Nie wiem czy wina leży w jakiś bibliotekach.
Nic nie wykrywa? Próbowałeś wgrywać jakiś skaner urządzeń DS?
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
Lector
Posts: 2471
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

klew wrote: Sun Jun 07, 2026 5:43 pm
Lector wrote: Sun Jun 07, 2026 1:12 pm Jestem zamrożony przez brak odczytu adresów DS18B20 i temperatur.
Na 5 czujników jakie mam żaden mi nie działa, nawet na prostych przykładach pod ESP lub z GG.

Nie wiem czy wina leży w jakiś bibliotekach.
Nic nie wykrywa? Próbowałeś wgrywać jakiś skaner urządzeń DS?
Mam 5 sztuk, dwa wodoodporne i 3 sztuki golasy, tych golasów miałem więcej i jedne działa mi w innym układzie do dziś.
Teraz żaden firmware który generuje w GG, albo zwykły przykład z Arduino nic nie działa ani na ESP8266 ani na ESP32.
Rozumiem jakbym miał problem z jednym, ale 5 sztuk?
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
klew
Posts: 13908
Joined: Thu Jun 27, 2019 12:16 pm
Location: Wrocław
Has thanked: 134 times
Been thanked: 137 times

Post

Mogę tylko współczuć :p

Skaner nic nie znajduje? Rezystor jest? Podłączanie poprawne?
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
Lector
Posts: 2471
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

klew wrote: Sun Jun 07, 2026 6:21 pm Mogę tylko współczuć :p

Skaner nic nie znajduje? Rezystor jest? Podłączanie poprawne?
Ja pierd.... To rzesz ktoś mnie urządził.
Opis prawie dobry ale braknie k czyli rezystory inne.
Na 3k2 ohma działa.
You do not have the required permissions to view the files attached to this post.
Last edited by Lector on Sun Jun 07, 2026 8:23 pm, edited 1 time in total.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
DamD
Posts: 144
Joined: Mon Oct 27, 2025 6:23 pm
Has thanked: 2 times

Post

Jeżeli mogę wtrącić się do tematu to ja ostatnio zakupiłem 5 szt. z boot....u, a za pomocą kodu ( Arduino IDE):

#include <OneWire.h>
#include <DallasTemperature.h>

// Definicja pinu GPIO dla ESP32-C3 Super Mini
#define ONE_WIRE_BUS 5

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup() {
// Uruchomienie monitora szeregowego (ważna prędkość 115200)
Serial.begin(115200);
while(!Serial) { ; } // Czekaj na połączenie USB

sensors.begin();

Serial.println("--- Szukanie adresów DS18B20 ---");

int deviceCount = sensors.getDeviceCount();
Serial.print("Znaleziono urządzeń: ");
Serial.println(deviceCount);

for (int i = 0; i < deviceCount; i++) {
DeviceAddress deviceAddress;
if (sensors.getAddress(deviceAddress, i)) {
Serial.print("Czujnik ");
Serial.print(i);
Serial.print(" -> Adres HEX: ");
printAddress(deviceAddress);
Serial.println();
}
}
}

void loop() {
// Pętla pusta - adres odczytujemy tylko raz przy starcie
}

// Funkcja pomocnicza do ładnego wyświetlania adresu HEX
void printAddress(DeviceAddress deviceAddress) {
for (uint8_t i = 0; i < 8; i++) {
if (deviceAddress < 16) Serial.print("0");
Serial.print(deviceAddress, HEX);
if (i < 7) Serial.print("-");
}
}

odczytałem adresy czujników DS.

Moduł ESP 32 C3 Super mini , a data na GPIO 5.

Zamiast typowego rezystora użyłem "Adaptera DS18B20" z znanego portalu https://pl.aliexpress.com/item/10050066 ... pt=glo2pol . Wszystkie mam podpięte pod jedną sztukę i działają, na razie testowo. Jeżeli zależy Ci na czasie mogę jedną sztukę adaptera oddać za free, oczywiście bez kosztów przesyłki, jak co to pisz na prv.

Pozdrawiam
Obecnie na stanie SGW-01 x 2, mSBW-02, SBW-02, mROW-02, ROW-01, Auraton Box Supla, DWS, Outdoor Sensor, Bramka Zigbee <=> SUPLA, GG.
User avatar
Lector
Posts: 2471
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Dzięki, temat już rozwiązany.
Po prostu nie mogłem sprawdzić czy firmware SmartPool poprawnie będzie wykrywał czujniki.
Tylko to mnie wstrzymywało przed podmianą w układzie który już pracuje.

Teraz mam nadzieję że we wtorek nie będzie padać to w końcu podmienię sterownik.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
Lector
Posts: 2471
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Nie wytrzymałem i założył dziś sterownik no i znów problem czujnikami temperatur.
Musiałem dać dwa równolegle 3,3k Ohma bo gubiło pomiary, nawet jak było 2,2K Ohma jak przy poprzednim układzie.

Zmodernizowałem troszkę logikę grzania, tak więc jutro cały dzień testów.

Teraz idę zamknąć skrzynkę i mam nadzieje ze nic się nie rozłączy :P
You do not have the required permissions to view the files attached to this post.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
[email protected]
Posts: 1585
Joined: Mon Feb 06, 2023 8:56 am
Has thanked: 18 times
Been thanked: 26 times

Post

@Lector ale wiesz, że DSy to powinny mieć 4.7k? ewentualnie trochę mniej jak masz dużo DSów albo długie przewody
User avatar
Zibi_007
Posts: 3985
Joined: Tue Oct 31, 2023 10:06 pm
Has thanked: 10 times
Been thanked: 31 times

Post

[email protected] wrote: Mon Jun 08, 2026 7:02 pm @Lector ale wiesz, że DSy to powinny mieć 4.7k? ewentualnie trochę mniej jak masz dużo DSów albo długie przewody
Pewnie wie, ale pisał wcześniej, że ma tylko 4,7 Ohm, bez "k" ;-)
Widać, że stosuje co ma.
@Lector - poeksperymentuj jeszcze z tym oporem (połączeniem).
Image Wiesz, że Supla obsługuje Zigbee? Wstąp do Klubu Promila: https://forum.supla.org/viewtopic.php?t=18018
Image Szukasz nowego GG? Jest tutaj: https://forum.supla.org/viewtopic.php?t=18036

Return to “Pomoc”