SmartPool - pomoc przy kodzie ;)

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

Post

Hey, potrzebuje pomocy przy kodzie - działem z Chat GPT i aktualnie mam problem z "wirtualnym termostatem". Termostat nie ma załączać bezpośrednio przekaźnika, chcę w logice (później) odczytywać stan i inne warunki i złączać przekaźnik.

Aktualny kod:

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>

// ======================================================
// 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
// ======================================================

#define STATUS_LED_GPIO          2
#define BUTTON_CFG_GPIO          0

#define RELAY_1_GPIO             2

#define RELAY_2_GPIO             12
#define BUTTON_2_GPIO            9

#define RELAY_3_GPIO             5

#define RELAY_4_GPIO             4
#define BUTTON_4_GPIO            10

#define RELAY_5_GPIO             15

#define RELAY_6_GPIO             3
#define BUTTON_6_GPIO            0

#define BUTTON_THERMOSTAT_GPIO   14

#define ONE_WIRE_BUS             1

// ======================================================
// SUPLA
// ======================================================

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

// ======================================================
// DS18B20
// ======================================================

OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

DeviceAddress discoveredAddresses[10];

int discoveredCount = 0;

// ======================================================
// DS18B20 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[4];

// ======================================================
// WEB PANEL
// ======================================================

class DS18B20AssignmentParameter
  : public Supla::HtmlElement {

public:

  DS18B20AssignmentParameter()
    : HtmlElement(Supla::HTML_SECTION_FORM) {}

  void send(Supla::WebSender* sender) override {

    sender->send("</div><div class=\"box\">");

    sender->send(
        "<h3>DS18B20 Thermometer Assignment</h3>");

    auto cfg =
        Supla::Storage::ConfigInstance();

    for (int t = 0; t < 4; t++) {

      char label[32];

      snprintf(label,
               sizeof(label),
               "Thermometer %d",
               t + 1);

      sender->send(
          "<div class=\"form-field\"><label>");

      sender->send(label);

      sender->send("</label>");

      sender->send("<select name=\"");

      char selectName[32];

      snprintf(selectName,
               sizeof(selectName),
               "ds_assign_%d",
               t);

      sender->send(selectName);

      sender->send("\">");

      uint8_t selectedIdx = 255;

      char key[20];

      snprintf(key,
               sizeof(key),
               "ds_idx_%d",
               t);

      if (cfg) {

        cfg->getUInt8(key,
                      &selectedIdx);
      }

      sender->send(
          "<option value=\"255\"");

      if (selectedIdx == 255) {

        sender->send(" selected");
      }

      sender->send(
          ">Not Assigned</option>");

      for (int i = 0;
           i < discoveredCount;
           i++) {

        char optValue[4];

        char optLabel[64];

        snprintf(optValue,
                 sizeof(optValue),
                 "%d",
                 i);

        snprintf(optLabel,
                 sizeof(optLabel),
                 "ID: %02X%02X%02X%02X...",
                 discoveredAddresses[i][0],
                 discoveredAddresses[i][1],
                 discoveredAddresses[i][2],
                 discoveredAddresses[i][3]);

        sender->send("<option value=\"");

        sender->send(optValue);

        sender->send("\"");

        if (selectedIdx == i) {

          sender->send(" selected");
        }

        sender->send(">");

        sender->send(optLabel);

        sender->send("</option>");
      }

      sender->send("</select></div>");
    }

    sender->send("</div><div>");

    sender->send(
      "<button type=\"button\" "
      "onclick=\"window.location.href='/update'\">");

    sender->send("FIRMWARE UPDATE");

    sender->send("</button>");
  }

  bool handleResponse(
      const char* key,
      const char* value) override {

    if (strncmp(key,
                "ds_assign_",
                10) != 0) {

      return false;
    }

    int t = atoi(key + 10);

    int selectedIdx = atoi(value);

    auto cfg =
        Supla::Storage::ConfigInstance();

    if (!cfg) return false;

    char idxKey[20];

    snprintf(idxKey,
             sizeof(idxKey),
             "ds_idx_%d",
             t);

    cfg->setUInt8(idxKey,
                  selectedIdx);

    char byteKey[20];

    if (selectedIdx >= 0 &&
        selectedIdx < discoveredCount) {

      for (int i = 0; i < 8; i++) {

        snprintf(byteKey,
                 sizeof(byteKey),
                 "ds_%d_byte_%d",
                 t,
                 i);

        cfg->setUInt8(
            byteKey,
            discoveredAddresses[selectedIdx][i]);
      }

    } else {

      for (int i = 0; i < 8; i++) {

        snprintf(byteKey,
                 sizeof(byteKey),
                 "ds_%d_byte_%d",
                 t,
                 i);

        cfg->setUInt8(byteKey, 0);
      }
    }

    return true;
  }
};

// ======================================================
// SCAN DS18B20
// ======================================================

void scanDS18B20() {

  sensors.begin();

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

// ======================================================
// SETUP
// ======================================================

void setup() {

  Serial.begin(115200);

  new Supla::Clock;

  scanDS18B20();

  SuplaDevice.allowWorkInOfflineMode(0);

  SuplaDevice.setName(
      "SUPLA-SmartPool");

  SuplaDevice.setSwVersion(
      "SmartPool v1.0");

  httpUpdater.setup(
      suplaServer.getServerPtr(),
      "/update");

  // ====================================================
  // WWW
  // ====================================================

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

  // ====================================================
  // RELAY 1
  // ====================================================

  auto r1 =
      new Supla::Control::Relay(
          RELAY_1_GPIO);

  r1->getChannel()
      ->setChannelNumber(0);

  r1->setDefaultFunction(
      SUPLA_CHANNELFNC_POWERSWITCH);

  r1->setInitialCaption(
      "Zabezpieczenie");

  // ====================================================
  // RELAY 2
  // ====================================================

  auto r2 =
      new Supla::Control::Relay(
          RELAY_2_GPIO);

  r2->getChannel()
      ->setChannelNumber(1);

  r2->setDefaultFunction(
      SUPLA_CHANNELFNC_POWERSWITCH);

  r2->setInitialCaption(
      "Pompa filtracyjna");

  auto b2 =
      new Supla::Control::Button(
          BUTTON_2_GPIO,
          true,
          true);

  b2->addAction(
      Supla::TOGGLE,
      r2,
      Supla::ON_PRESS);

  // ====================================================
  // RELAY 3
  // ====================================================

  auto r3 =
      new Supla::Control::Relay(
          RELAY_3_GPIO);

  r3->getChannel()
      ->setChannelNumber(2);

  r3->setDefaultFunction(
      SUPLA_CHANNELFNC_POWERSWITCH);

  r3->setInitialCaption(
      "Generator chloru i ozonu");

  // ====================================================
  // RELAY 4
  // ====================================================

  auto r4 =
      new Supla::Control::Relay(
          RELAY_4_GPIO);

  r4->getChannel()
      ->setChannelNumber(3);

  r4->setDefaultFunction(
      SUPLA_CHANNELFNC_POWERSWITCH);

  r4->setInitialCaption(
      "Pompa ogrzewania");

  auto b4 =
      new Supla::Control::Button(
          BUTTON_4_GPIO,
          true,
          true);

  b4->addAction(
      Supla::TOGGLE,
      r4,
      Supla::ON_PRESS);

  // ====================================================
  // RELAY 5
  // ====================================================

  auto r5 =
      new Supla::Control::Relay(
          RELAY_5_GPIO);

  r5->getChannel()
      ->setChannelNumber(4);

  r5->setDefaultFunction(
      SUPLA_CHANNELFNC_LIGHTSWITCH);

  r5->setInitialCaption(
      "Lampa UV-C");

  // ====================================================
  // RELAY 6
  // ====================================================

  auto r6 =
      new Supla::Control::Relay(
          RELAY_6_GPIO);

  r6->getChannel()
      ->setChannelNumber(5);

  r6->setDefaultFunction(
      SUPLA_CHANNELFNC_LIGHTSWITCH);

  r6->setInitialCaption(
      "Oświetlenie basenu");

  auto b6 =
      new Supla::Control::Button(
          BUTTON_6_GPIO,
          true,
          true);

  b6->addAction(
      Supla::TOGGLE,
      r6,
      Supla::ON_PRESS);

  // ====================================================
  // DS18B20
  // ====================================================

  dsSensors[0] = new ConfigurableDS18B20(0);
  dsSensors[0]->getChannel()->setChannelNumber(7);
  dsSensors[0]->setInitialCaption("Woda w basenie");

  dsSensors[1] = new ConfigurableDS18B20(1);
  dsSensors[1]->getChannel()->setChannelNumber(8);
  dsSensors[1]->setInitialCaption("Woda w solarach");

  dsSensors[2] = new ConfigurableDS18B20(2);
  dsSensors[2]->getChannel()->setChannelNumber(9);
  dsSensors[2]->setInitialCaption("Woda powrotna z solarów");

  dsSensors[3] = new ConfigurableDS18B20(3);
  dsSensors[3]->getChannel()->setChannelNumber(10);
  dsSensors[3]->setInitialCaption("Powietrze");

  // ====================================================
  // HVAC
  // ====================================================

  auto hvac = new Supla::Control::HvacBase();

  new Supla::Html::HvacParameters(hvac);

  hvac->getChannel()
      ->setChannelNumber(6);

  hvac->setInitialCaption("Termostat Basenu");

  // główny termometr
  hvac->setMainThermometerChannelNo(7);

  // histereza
  hvac->setTemperatureHisteresis(40);

  // zakres histerezy
  hvac->setTemperatureHisteresisMin(20);

  hvac->setTemperatureHisteresisMax(1000);

  // zakres temperatur
  hvac->setTemperatureRoomMin(2000);

  hvac->setTemperatureRoomMax(4000);

  // ====================================================
  // HVAC BUTTON
  // ====================================================

  auto bThermostat =
      new Supla::Control::Button(
          BUTTON_THERMOSTAT_GPIO,
          true,
          true);

  bThermostat->addAction(
      Supla::TOGGLE,
      hvac,
      Supla::ON_PRESS);

  // ====================================================
  // CONFIG BUTTON
  // ====================================================

  auto buttonCfg =
      new Supla::Control::Button(
          BUTTON_CFG_GPIO,
          true,
          true);

  buttonCfg->configureAsConfigButton(
      &SuplaDevice);

  // ====================================================
  // CERT
  // ====================================================

  SuplaDevice.setSuplaCACert(
      suplaCACert);

  SuplaDevice.setSupla3rdPartyCACert(
      supla3rdCACert);

  // ====================================================
  // START
  // ====================================================

  SuplaDevice.begin(21);
}

// ======================================================
// LOOP
// ======================================================

void loop() {

  SuplaDevice.iterate();

  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);
  }
}
Termostat niby jest ale nie idzie go ustawić w cloud.

Prosze o porady łopatologiczne, mile widziane poprawki w kodzie.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
veeroos
Posts: 1080
Joined: Sun Mar 20, 2022 9:30 am
Location: Głogów
Has thanked: 1 time
Been thanked: 9 times

Post

Cześć, ja robiłem coś na zasadzie "wirtualnego termostatu ostatnio" do sterowania oknami w szklarni , jako wyjścia użyłem wirtualny expander i na podstawie odczytu "wirtualnego sygnału" zamykałem lub otwierałem okno,.które było sterowane kanałem rolety (okno dachowe), roleta też miała wirtualne wyjścia a to dlatego, że okna były sterowane siłownikami które zmieniały swój kierunek poprzez zamianę biegunowości, tu użyłem mostek H. Ale do czego zmierzam, musisz sobie stworzyć wirtualny expander. Możesz skorzystać z kodu Radka:
viewtopic.php?p=177027#p177027

Ja zrobiłem to tak z kodu Radka:

Code: Select all

class VirtualExpander : public Supla::Io::Base {
  public:
    VirtualExpander() : Supla::Io::Base(false) {}
    void customDigitalWrite(int channelNumber, uint8_t pin, uint8_t val) override {
      VIRTUAL_VARIABLE[pin] = val;
    }
    int customDigitalRead(int channelNumber, uint8_t pin) override {
      return VIRTUAL_VARIABLE[pin];
    }
  private:
    bool VIRTUAL_VARIABLE[8];
};
wyjście normalnie odczytujesz jako na przyklad:

Deklarujesz sobie wirtualne wyjście:

Code: Select all

  
    auto EX_OUTPUT = new VirtualExpander();
  Wyjscie_1 = new Supla::Control::InternalPinOutput(EX_OUTPUT, Wirtualny_Termostat, true);

Deklarujesz termostat:

Code: Select all

  TermostatTemperatura_1 = new Supla::Control::HvacBase(Wyjscie_1);
A później gdzieś w kodzie odpytujesz o te wyjscie

Code: Select all

if (Wyjście->isOn()){

}
A i daj sobie spokój z Chatem GPT bo on lubi głupoty napisać, pytaj na forum na pewno pomożemy, jak sobie nie poradzisz z kodem to napisz, napiszę go dla Ciebie i opublikuje w tym temacie
Zamel Mew-01, Zamel PEW-01, Zamel SBW-01, Zamel THW-01, Zamel SRW-01, Zamel mSLW-02, Auraton BOX + Indor Sensor, Airly Gate By Veeroos, Zigbee to Supla Gateway + sensors, Zamel SGW-01 and more

https://github.com/v33r005
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Kod się troszkę rozwinął:

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>

// ======================================================
// 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 (Dopasowane pod stabilne działanie ESP8266)
// ======================================================
#define STATUS_LED_GPIO          13
#define BUTTON_CFG_GPIO           0
#define RELAY_1_GPIO              2  // Zabezpieczenie (Blokada)
#define RELAY_2_GPIO             12  // Pompa filtracyjna
#define BUTTON_2_GPIO             9
#define RELAY_3_GPIO              5  // Generator chloru
#define RELAY_4_GPIO              4  // Pompa ogrzewania
#define BUTTON_4_GPIO            10
#define RELAY_5_GPIO             15  // Lampa UV-C
#define RELAY_6_GPIO              3  // Oświetlenie basenu
#define BUTTON_6_GPIO             0
#define BUTTON_THERMOSTAT_GPIO   14

// Ustaw pin magistrali DS18B20 (np. 2 lub 3 w zależności od podłączenia)
#define ONE_WIRE_BUS              1

// ======================================================
// SUPLA INSTANCJE
// ======================================================
Supla::ESPWifi wifi;
Supla::LittleFsConfig configSupla;
Supla::Device::StatusLed statusLed(STATUS_LED_GPIO, true);
Supla::EspWebServer suplaServer;

#ifdef ARDUINO_ARCH_ESP32
HTTPUpdateServer httpUpdater;
#else
ESP8266HTTPUpdateServer httpUpdater;
#endif

// ======================================================
// DS18B20 KONFIGURACJA SPRZĘTOWA
// ======================================================
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
DeviceAddress discoveredAddresses[10];
int discoveredCount = 0;

// ======================================================
// POINTERY DLA LOGIKI ZALEŻNOŚCI I BLOKAD
// ======================================================
Supla::Control::Relay* relayLock = nullptr;      // Relay 1
Supla::Control::Relay* relayPump = nullptr;      // Relay 2
Supla::Control::Relay* relayChlorine = nullptr;  // Relay 3
Supla::Control::Relay* relayHeatPump = nullptr;  // Relay 4
Supla::Control::Relay* relayUV = nullptr;        // Relay 5

// Statusy checkboxów pobierane z pamięci
bool depChlorine = false;
bool depUV = false;

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

// ======================================================
// DS18B20 KLASA KANAŁU SUPLA
// ======================================================
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[4];

// ======================================================
// WEB PANEL - WYBÓR CZUJNIKÓW
// ======================================================
class DS18B20AssignmentParameter : public Supla::HtmlElement {
public:
  DS18B20AssignmentParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}

  void send(Supla::WebSender* sender) override {
    sender->send("</div><div class=\"box\">");
    sender->send("<h3>DS18B20 Thermometer Assignment</h3>");

    auto cfg = Supla::Storage::ConfigInstance();

    for (int t = 0; t < 4; t++) {
      char label[32];
      snprintf(label, sizeof(label), "Thermometer %d", t + 1);
      sender->send("<div class=\"form-field\"><label>");
      sender->send(label);
      sender->send("</label>");
      sender->send("<select name=\"");

      char selectName[32];
      snprintf(selectName, sizeof(selectName), "ds_assign_%d", t);
      sender->send(selectName);
      sender->send("\">");

      uint8_t selectedIdx = 255;
      char key[20];
      snprintf(key, sizeof(key), "ds_idx_%d", t);

      if (cfg) {
        cfg->getUInt8(key, &selectedIdx);
      }

      sender->send("<option value=\"255\"");
      if (selectedIdx == 255) {
        sender->send(" selected");
      }
      sender->send(">Not Assigned</option>");

      for (int i = 0; i < discoveredCount; i++) {
        char optValue[4];
        char optLabel[64];

        snprintf(optValue, sizeof(optValue), "%d", i);
        snprintf(optLabel, sizeof(optLabel), "ID: %02X%02X%02X%02X...",
                 discoveredAddresses[i][0],
                 discoveredAddresses[i][1],
                 discoveredAddresses[i][2],
                 discoveredAddresses[i][3]);

        sender->send("<option value=\"");
        sender->send(optValue);
        sender->send("\"");
        if (selectedIdx == i) {
          sender->send(" selected");
        }
        sender->send(">");
        sender->send(optLabel);
        sender->send("</option>");
      }
      sender->send("</select></div>");
    }
  }

  bool handleResponse(const char* key, const char* value) override {
    if (strncmp(key, "ds_assign_", 10) != 0) {
      return false;
    }
    int t = atoi(key + 10);
    int selectedIdx = atoi(value);
    auto cfg = Supla::Storage::ConfigInstance();
    if (!cfg) return false;

    char idxKey[20];
    snprintf(idxKey, sizeof(idxKey), "ds_idx_%d", t);
    cfg->setUInt8(idxKey, selectedIdx);

    char byteKey[20];
    if (selectedIdx >= 0 && selectedIdx < discoveredCount) {
      for (int i = 0; i < 8; i++) {
        snprintf(byteKey, sizeof(byteKey), "ds_%d_byte_%d", t, i);
        cfg->setUInt8(byteKey, discoveredAddresses[selectedIdx][i]);
      }
    } else {
      for (int i = 0; i < 8; i++) {
        snprintf(byteKey, sizeof(byteKey), "ds_%d_byte_%d", t, i);
        cfg->setUInt8(byteKey, 0);
      }
    }
    return true;
  }
};

// ======================================================
// PRZYCISK AKTUALIZACJI FIRMWARE
// ======================================================
class FirmwareUpdateButton : public Supla::HtmlElement {
public:
  FirmwareUpdateButton() : HtmlElement(Supla::HTML_SECTION_FORM) {}

  void send(Supla::WebSender* sender) override {
    sender->send("<div>");
    sender->send("<button type=\"button\" "
                 "style=\"width:100%;"
                 "height:50px;"
                 "background:black;"
                 "color:white;"
                 "border:none;"
                 "border-radius:8px;"
                 "font-size:16px;"
                 "font-weight:bold;\" "
                 "onclick=\"window.location.href='/update'\">");
    sender->send("FIRMWARE UPDATE");
    sender->send("</button>");
  }
};

// ======================================================
// WEB PANEL - NOWY BLOK KONFIGURACJI BLOKADY (PRZEKAŹNIK 1)
// ======================================================
class RelayLockParameter : public Supla::HtmlElement {
public:
  RelayLockParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}

  void send(Supla::WebSender* sender) override {
    sender->send("</div><div class=\"box\">");
    sender->send("<h3>Zabezpieczenie - blokada przekaźników</h3>");
    sender->send("<input type=\"hidden\" name=\"lock_reset\" value=\"1\">");

    auto cfg = Supla::Storage::ConfigInstance();
    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);
    }

    // Blokada Pompy
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center; margin-bottom: 12px;\">");
    sender->send("<input type=\"checkbox\" id=\"lock_pump\" name=\"lock_pump\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (l_pump) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"lock_pump\" style=\"margin: 0; cursor: pointer;\">Pompa filtracyjna</label>");
    sender->send("</div>");

    // Blokada Chloru
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center; margin-bottom: 12px;\">");
    sender->send("<input type=\"checkbox\" id=\"lock_chlor\" name=\"lock_chlor\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (l_chlor) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"lock_chlor\" style=\"margin: 0; cursor: pointer;\">Generator chloru i ozonu</label>");
    sender->send("</div>");

    // Blokada Ogrzewania
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center; margin-bottom: 12px;\">");
    sender->send("<input type=\"checkbox\" id=\"lock_heat\" name=\"lock_heat\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (l_heat) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"lock_heat\" style=\"margin: 0; cursor: pointer;\">Pompa ogrzewania</label>");
    sender->send("</div>");

    // Blokada UV
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\">");
    sender->send("<input type=\"checkbox\" id=\"lock_uv\" name=\"lock_uv\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (l_uv) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"lock_uv\" style=\"margin: 0; cursor: pointer;\">Lampa UV-C</label>");
    sender->send("</div>");

    sender->send("</div>");
  }

  bool handleResponse(const char* key, const char* value) override {
    auto cfg = Supla::Storage::ConfigInstance();
    if (!cfg) return false;

    if (strcmp(key, "lock_reset") == 0) {
      cfg->setUInt8("lock_pump", 0);   cfg->setUInt8("lock_chlor", 0);
      cfg->setUInt8("lock_heat", 0);   cfg->setUInt8("lock_uv", 0);
      lockPump = lockChlorine = lockHeating = lockUV = false;
      return true;
    }
    if (strcmp(key, "lock_pump") == 0)  { cfg->setUInt8("lock_pump", 1);  lockPump = true;     return true; }
    if (strcmp(key, "lock_chlor") == 0) { cfg->setUInt8("lock_chlor", 1); lockChlorine = true; return true; }
    if (strcmp(key, "lock_heat") == 0)  { cfg->setUInt8("lock_heat", 1);  lockHeating = true;  return true; }
    if (strcmp(key, "lock_uv") == 0)    { cfg->setUInt8("lock_uv", 1);    lockUV = true;        return true; }
    return false;
  }
};

// ======================================================
// WEB PANEL - FORMULARZ ZALEŻNOŚCI OD POMPY (UKŁAD POZIOMY)
// ======================================================
class RelayDependencyParameter : public Supla::HtmlElement {
public:
  RelayDependencyParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}

  void send(Supla::WebSender* sender) override {
    sender->send("<div class=\"box\">");
    sender->send("<h3>Pompa filtracyjna zasila</h3>");
    sender->send("<input type=\"hidden\" name=\"dep_reset\" value=\"1\">");

    auto cfg = Supla::Storage::ConfigInstance();
    uint8_t chlorine = 0;
    uint8_t uv = 0;

    if (cfg) {
      cfg->getUInt8("dep_chlorine", &chlorine);
      cfg->getUInt8("dep_uv", &uv);
    }

    // CHLORINE
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\">");
    sender->send("<input type=\"checkbox\" id=\"dep_chlorine\" name=\"dep_chlorine\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (chlorine) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"dep_chlorine\" style=\"margin: 0; cursor: pointer;\">Generator chloru i ozonu</label>");
    sender->send("</div>");

    sender->send("<hr style=\"border: 0; border-top: 1px solid #eee;\">");

    // UV
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\">");
    sender->send("<input type=\"checkbox\" id=\"dep_uv\" name=\"dep_uv\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\" ");
    if (uv) sender->send(" checked");
    sender->send(">");
    sender->send("<label for=\"dep_uv\" style=\"margin: 0; cursor: pointer;\">Lampa UV-C</label>");
    sender->send("</div>");
    
    sender->send("</div>"); 
  }

  bool handleResponse(const char* key, const char* value) override {
    auto cfg = Supla::Storage::ConfigInstance();
    if (!cfg) return false;

    if (strcmp(key, "dep_reset") == 0) {
      cfg->setUInt8("dep_chlorine", 0);
      cfg->setUInt8("dep_uv", 0);
      depChlorine = false;
      depUV = false;
      return true;
    }
    if (strcmp(key, "dep_chlorine") == 0) {
      cfg->setUInt8("dep_chlorine", 1);
      depChlorine = true;
      return true;
    }
    if (strcmp(key, "dep_uv") == 0) {
      cfg->setUInt8("dep_uv", 1);
      depUV = true;
      return true;
    }
    return false;
  }
};

// ======================================================
// SKANER ADRESÓW 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++;
    }
  }
  
  if (discoveredCount == 0) {
    oneWire.reset_search();
    delay(50);
    while (oneWire.search(tempAddr) && discoveredCount < 10) {
      if (sensors.validAddress(tempAddr) && tempAddr[0] == 0x28) {
        memcpy(discoveredAddresses[discoveredCount], tempAddr, sizeof(DeviceAddress));
        discoveredCount++;
      }
    }
  }
}

// ======================================================
// POŁĄCZONA LOGIKA ZALEŻNOŚCI ORAZ BLOKADY (NADRZĘDNEJ)
// ======================================================
void handleRelayDependencies() {
  // Wczytanie konfiguracji z pamięci flash
  auto cfg = Supla::Storage::ConfigInstance();
  if (cfg) {
    uint8_t temp = 0;
    cfg->getUInt8("dep_chlorine", &temp); depChlorine = (temp == 1);
    cfg->getUInt8("dep_uv", &temp);       depUV = (temp == 1);
    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);
  }

  // 1. OBSŁUGA NADRZĘDNEJ BLOKADY (ZABEZPIECZENIA - KANAŁ 1)
  // Przekaźnik 1 działa w odwróconej logice, więc isOn() zwraca poprawny stan logiczny działania blokady.
  if (relayLock && relayLock->isOn()) {
    if (lockPump && relayPump && relayPump->isOn())         relayPump->turnOff();
    if (lockChlorine && relayChlorine && relayChlorine->isOn()) relayChlorine->turnOff();
    if (lockHeating && relayHeatPump && relayHeatPump->isOn())   relayHeatPump->turnOff();
    if (lockUV && relayUV && relayUV->isOn())               relayUV->turnOff();
  }

  // Jeśli pompa została wyłączona przez blokadę, to wychodzimy, żeby nie uruchamiać poniższej logiki pompy.
  if (!relayPump) return;

  // 2. STANDARDOWA LOGIKA URZĄDZEŃ ZALEŻNYCH OD POMPY
  if (!depChlorine && !depUV) {
    return; 
  }

  static bool lastPumpState = false;
  static bool lastChlorineState = false;
  static bool lastUVState = false;

  bool currentPumpOn = relayPump->isOn();
  bool currentChlorineOn = (relayChlorine && relayChlorine->isOn());
  bool currentUVOn = (relayUV && relayUV->isOn());

  bool pumpWasTurnedOff = (lastPumpState == true && currentPumpOn == false);
  bool chlorineWasTurnedOn = (lastChlorineState == false && currentChlorineOn == true);
  bool uvWasTurnedOn = (lastUVState == false && currentUVOn == true);

  // Wyłączenie pompy gasi chemię/UV
  if (pumpWasTurnedOff) {
    if (depChlorine && currentChlorineOn) { relayChlorine->turnOff(); currentChlorineOn = false; }
    if (depUV && currentUVOn) { relayUV->turnOff(); currentUVOn = false; }
  }

  // Włączenie chemii/UV wymusza start pompy (tylko, jeśli sama nie jest aktualnie zablokowana)
  if (!currentPumpOn) {
    bool lockIsActive = (relayLock && relayLock->isOn());
    if ((depChlorine && chlorineWasTurnedOn && !(lockIsActive && lockChlorine)) || 
        (depUV && uvWasTurnedOn && !(lockIsActive && lockUV))) {
      if (!(lockIsActive && lockPump)) {
        relayPump->turnOn();
        currentPumpOn = true; 
      }
    }
  }

  // Ochrona przed pracą bez przepływu wody (suchobieg)
  if (!currentPumpOn) {
    if (depChlorine && currentChlorineOn) { relayChlorine->turnOff(); currentChlorineOn = false; }
    if (depUV && currentUVOn) { relayUV->turnOff(); currentUVOn = false; }
  }

  lastPumpState = currentPumpOn;
  lastChlorineState = currentChlorineOn;
  lastUVState = currentUVOn;
}

// ======================================================
// SETUP
// ======================================================
void setup() {
  delay(500);

  new Supla::Clock;

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

  httpUpdater.setup(suplaServer.getServerPtr(), "/update");

  // REJESTRACJA FORMULARZY WWW
  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 DS18B20AssignmentParameter();
  new RelayLockParameter();        // <--- Blokada (Nowość)
  new RelayDependencyParameter();  // <--- Zależności pompy

  // RELAY 1 - ZABEZPIECZENIE (Wymuszenie odwróconej logiki bezpośrednio w konstruktorze)
  relayLock = new Supla::Control::Relay(RELAY_1_GPIO, false);
  relayLock->getChannel()->setChannelNumber(0);
  relayLock->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH);
  relayLock->setInitialCaption("Zabezpieczenie");

  // RELAY 2 - POMPA FILTRACYJNA
  relayPump = new Supla::Control::Relay(RELAY_2_GPIO);
  relayPump->getChannel()->setChannelNumber(1);
  relayPump->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH);
  relayPump->setInitialCaption("Pompa filtracyjna");

  auto b2 = new Supla::Control::Button(BUTTON_2_GPIO, true, true);
  b2->addAction(Supla::TOGGLE, relayPump, Supla::ON_PRESS);

  // RELAY 3 - GENERATOR CHLORU
  relayChlorine = new Supla::Control::Relay(RELAY_3_GPIO);
  relayChlorine->getChannel()->setChannelNumber(2);
  relayChlorine->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH);
  relayChlorine->setInitialCaption("Generator chloru i ozonu");

  // RELAY 4 - POMPA OGRZEWANIA
  relayHeatPump = new Supla::Control::Relay(RELAY_4_GPIO);
  relayHeatPump->getChannel()->setChannelNumber(3);
  relayHeatPump->setDefaultFunction(SUPLA_CHANNELFNC_POWERSWITCH);
  relayHeatPump->setInitialCaption("Pompa ogrzewania");

  auto b4 = new Supla::Control::Button(BUTTON_4_GPIO, true, true);
  b4->addAction(Supla::TOGGLE, relayHeatPump, Supla::ON_PRESS);

  // RELAY 5 - LAMPA UV-C
  relayUV = new Supla::Control::Relay(RELAY_5_GPIO);
  relayUV->getChannel()->setChannelNumber(4);
  relayUV->setDefaultFunction(SUPLA_CHANNELFNC_LIGHTSWITCH);
  relayUV->setInitialCaption("Lampa UV-C");

  // RELAY 6 - OŚWIETLENIE
  auto r6 = new Supla::Control::Relay(RELAY_6_GPIO);
  r6->getChannel()->setChannelNumber(5);
  r6->setDefaultFunction(SUPLA_CHANNELFNC_LIGHTSWITCH);
  r6->setInitialCaption("Oświetlenie basenu");

  auto b6 = new Supla::Control::Button(BUTTON_6_GPIO, true, true);
  b6->addAction(Supla::TOGGLE, r6, Supla::ON_PRESS);

  // TERMOMETRY DS18B20
  scanDS18B20();

  dsSensors[0] = new ConfigurableDS18B20(0);
  dsSensors[0]->getChannel()->setChannelNumber(7);
  dsSensors[0]->setInitialCaption("Woda w basenie");

  dsSensors[1] = new ConfigurableDS18B20(1);
  dsSensors[1]->getChannel()->setChannelNumber(8);
  dsSensors[1]->setInitialCaption("Woda w solarach");

  dsSensors[2] = new ConfigurableDS18B20(2);
  dsSensors[2]->getChannel()->setChannelNumber(9);
  dsSensors[2]->setInitialCaption("Woda powrotna z solarów");

  dsSensors[3] = new ConfigurableDS18B20(3);
  dsSensors[3]->getChannel()->setChannelNumber(10);
  dsSensors[3]->setInitialCaption("Powietrze");

  // HVAC - TERMOSTAT BASENU
  auto hvacOutput = new Supla::Control::InternalPinOutput(-1);
  auto hvac = new Supla::Control::HvacBase(hvacOutput);

  hvac->getChannel()->setChannelNumber(6);
  hvac->setInitialCaption("Termostat Basenu");
  hvac->setMainThermometerChannelNo(7); 
  hvac->setTemperatureHisteresis(10);
  hvac->setTemperatureHisteresisMin(10);
  hvac->setTemperatureHisteresisMax(1000);
  hvac->setTemperatureAuxMin(2000); // Minimum: 20°C
  hvac->setTemperatureAuxMax(4000); // Maximum: 40°C

  new FirmwareUpdateButton();

  auto bThermostat = new Supla::Control::Button(BUTTON_THERMOSTAT_GPIO, true, true);
  bThermostat->addAction(Supla::TOGGLE, hvac, Supla::ON_PRESS);

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

  SuplaDevice.setSuplaCACert(suplaCACert);
  SuplaDevice.setSupla3rdPartyCACert(supla3rdCACert);
  SuplaDevice.begin(33);
}

// ======================================================
// LOOP
// ======================================================
void loop() {
  SuplaDevice.iterate();
  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);
  }
}
Niestey dalej borykam się z termostatem. Chcę aby zekres temperatur do ustawienia był 20-40, a ciągle mam 5-40, histerza podobnie, jak ustawić domyśłną wartość nastawy termostatu zamiast 21?
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
vajera
Posts: 7289
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

Lector wrote: Sun May 17, 2026 8:04 pm Niestey dalej borykam się z termostatem. Chcę aby zekres temperatur do ustawienia był 20-40, a ciągle mam 5-40, histerza podobnie, jak ustawić domyśłną wartość nastawy termostatu zamiast 21?

Code: Select all

hvac->setDefaultTemperatureRoomMin(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, hvac_room_temperature_min);

hvac->setDefaultTemperatureRoomMax(SUPLA_CHANNELFNC_HVAC_THERMOSTAT, hvac_room_temperature_max);

nastawa:

Code: Select all

hvac->setTemperatureSetpointHeat(int tHeat);
Bramka Zigbee <=> SUPLA
Więcej informacji tutaj:
https://forum.supla.org/viewforum.php?f=127
FAQ https://forum.supla.org/viewtopic.php?t=17277
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Kilka dni zabawy z dwoma czatami AI i z grubsza mam to co chciałem.
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/
User avatar
veeroos
Posts: 1080
Joined: Sun Mar 20, 2022 9:30 am
Location: Głogów
Has thanked: 1 time
Been thanked: 9 times

Post

To jeszcze się podziel programem "dla potomnych" 😉. Ale gratuluję, webserwer bardzo profesjonalnie wygląda 😀
Zamel Mew-01, Zamel PEW-01, Zamel SBW-01, Zamel THW-01, Zamel SRW-01, Zamel mSLW-02, Auraton BOX + Indor Sensor, Airly Gate By Veeroos, Zigbee to Supla Gateway + sensors, Zamel SGW-01 and more

https://github.com/v33r005
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

veeroos wrote: Wed May 20, 2026 9:40 am To jeszcze się podziel programem "dla potomnych" 😉. Ale gratuluję, webserwer bardzo profesjonalnie wygląda 😀
Kod jeszcze się piszę, czekam również za termometrami do testów.

A kod na pewno pokaże aby specjaliści jeszcze sprawdzili ;)
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Ok, daje na te chwile to co mam.

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
// ======================================================
#include <HTTPUpdateServer.h>

// ======================================================
// 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             22
#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;

// ======================================================
// DS18B20 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];

// ======================================================
// HTML WEB PANEL CLASSES
// ======================================================
class DS18B20AssignmentParameter : public Supla::HtmlElement {
public:
  DS18B20AssignmentParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}
  void send(Supla::WebSender* sender) override {
    sender->send("</div><div class=\"box\"><h3>Przypisanie czujników DS18B20</h3>");
    auto cfg = Supla::Storage::ConfigInstance();
    for (int t = 0; t < SENSOR_COUNT; t++) {
      sender->send("<div class=\"form-field\"><label>"); sender->send(SENSOR_NAMES[t]); sender->send("</label><select name=\"ds_assign_");
      char buf[8]; snprintf(buf, sizeof(buf), "%d", t); sender->send(buf); sender->send("\">");
      uint8_t selectedIdx = 255; char key[20]; snprintf(key, sizeof(key), "ds_idx_%d", t);
      if (cfg) cfg->getUInt8(key, &selectedIdx);
      sender->send("<option value=\"255\""); if (selectedIdx == 255) sender->send(" selected"); sender->send(">Nie przypisany</option>");
      for (int i = 0; i < discoveredCount; i++) {
        sender->send("<option value=\""); snprintf(buf, sizeof(buf), "%d", i); sender->send(buf); sender->send("\"");
        if (selectedIdx == i) sender->send(" selected"); sender->send(">ID: ");
        for(int b=0; b<4; b++) { char hex[4]; snprintf(hex, sizeof(hex), "%02X", discoveredAddresses[i][b]); sender->send(hex); }
        sender->send("...</option>");
      }
      sender->send("</select></div>");
    }
  }
  bool handleResponse(const char* key, const char* value) override {
    if (strncmp(key, "ds_assign_", 10) != 0) return false;
    int t = atoi(key + 10); int selectedIdx = atoi(value);
    auto cfg = Supla::Storage::ConfigInstance(); if (!cfg) return false;
    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);
    }
    return true;
  }
};

class FirmwareUpdateButton : public Supla::HtmlElement {
public:
  FirmwareUpdateButton() : HtmlElement(Supla::HTML_SECTION_FORM) {}
  void send(Supla::WebSender* sender) override {
    sender->send("<div><button type=\"button\" style=\"width:100%;height:50px;background:black;color:white;border:none;border-radius:8px;font-size:16px;font-weight:bold;\" onclick=\"window.location.href='/update'\">FIRMWARE UPDATE</button>");
  }
};

class RelayLockParameter : public Supla::HtmlElement {
public:
  RelayLockParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}
  void send(Supla::WebSender* sender) override {
    sender->send("</div><div class=\"box\"><h3>Zabezpieczenie - blokada</h3><input type=\"hidden\" name=\"lock_reset\" value=\"1\">");
    auto cfg = Supla::Storage::ConfigInstance(); 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); }
    const char* names[] = {"lock_pump", "lock_chlor", "lock_heat", "lock_uv"};
    const char* labels[] = {"Pompa filtracyjna", "Generator chloru i ozonu", "Pompa ogrzewania", "Lampa UV-C"};
    uint8_t vals[] = {l_pump, l_chlor, l_heat, l_uv};
    for(int i=0; i<4; i++) {
      sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center; margin-bottom: 12px;\"><input type=\"checkbox\" id=\"");
      sender->send(names[i]); sender->send("\" name=\""); sender->send(names[i]); sender->send("\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\"");
      if (vals[i]) sender->send(" checked"); sender->send("><label for=\""); sender->send(names[i]); sender->send("\" style=\"margin: 0; cursor: pointer;\");>");
      sender->send(labels[i]); sender->send("</label></div>");
    }
    sender->send("</div>");
  }
  bool handleResponse(const char* key, const char* value) override {
    auto cfg = Supla::Storage::ConfigInstance(); if (!cfg) return false;
    if (strcmp(key, "lock_reset") == 0) {
      cfg->setUInt8("lock_pump", 0); cfg->setUInt8("lock_chlor", 0); cfg->setUInt8("lock_heat", 0); cfg->setUInt8("lock_uv", 0);
      lockPump = lockChlorine = lockHeating = lockUV = false; return true;
    }
    if (strcmp(key, "lock_pump") == 0)  { cfg->setUInt8("lock_pump", 1);  lockPump = true;     return true; }
    if (strcmp(key, "lock_chlor") == 0) { cfg->setUInt8("lock_chlor", 1); lockChlorine = true; return true; }
    if (strcmp(key, "lock_heat") == 0)  { cfg->setUInt8("lock_heat", 1);  lockHeating = true;  return true; }
    if (strcmp(key, "lock_uv") == 0)    { cfg->setUInt8("lock_uv", 1);    lockUV = true;       return true; }
    return false;
  }
};

class RelayDependencyParameter : public Supla::HtmlElement {
public:
  RelayDependencyParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}
  void send(Supla::WebSender* sender) override {
    sender->send("<div class=\"box\"><h3>Pompa filtracyjna zasila</h3><input type=\"hidden\" name=\"dep_reset\" value=\"1\">");
    auto cfg = Supla::Storage::ConfigInstance(); uint8_t chlorine = 0, uv = 0;
    if (cfg) { cfg->getUInt8("dep_chlorine", &chlorine); cfg->getUInt8("dep_uv", &uv); }
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\"><input type=\"checkbox\" id=\"dep_chlorine\" name=\"dep_chlorine\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\"");
    if (chlorine) sender->send(" checked"); sender->send("><label for=\"dep_chlorine\" style=\"margin: 0; cursor: pointer;\">Generator chloru i ozonu</label></div><hr style=\"border: 0; border-top: 1px solid #eee;\">");
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\"><input type=\"checkbox\" id=\"dep_uv\" name=\"dep_uv\" value=\"1\" style=\"margin: 0 10px 0 0; width: 20px; height: 20px;\"");
    if (uv) sender->send(" checked"); sender->send("><label for=\"dep_uv\" style=\"margin: 0; cursor: pointer;\">Lampa UV-C</label></div></div>");
  }
  bool handleResponse(const char* key, const char* value) override {
    auto cfg = Supla::Storage::ConfigInstance(); if (!cfg) return false;
    if (strcmp(key, "dep_reset") == 0) { cfg->setUInt8("dep_chlorine", 0); cfg->setUInt8("dep_uv", 0); depChlorine = depUV = false; return true; }
    if (strcmp(key, "dep_chlorine") == 0) { cfg->setUInt8("dep_chlorine", 1); depChlorine = true; return true; }
    if (strcmp(key, "dep_uv") == 0) { cfg->setUInt8("dep_uv", 1); depUV = true; return true; }
    return false;
  }
};

class HeatingPumpParameter : public Supla::HtmlElement {
public:
  HeatingPumpParameter() : HtmlElement(Supla::HTML_SECTION_FORM) {}
  void send(Supla::WebSender* sender) override {
    sender->send("<div class=\"box\"><h3>Pompa ogrzewania</h3>");
    auto cfg = Supla::Storage::ConfigInstance(); 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); }
    
    sender->send("<div class=\"form-field\" style=\"display:block;\"><label style=\"display:block; margin-bottom:8px;\">Załączenie</label><div style=\"display: flex; align-items: center;\"><select name=\"heat_on_t1\" style=\"width:150px;\">");
    for (int i = 0; i < SENSOR_COUNT; i++) { sender->send("<option value=\""); char val[4]; snprintf(val, sizeof(val), "%d", i); sender->send(val); sender->send("\""); if(h_on_t1 == i) sender->send(" selected"); sender->send(">"); sender->send(SENSOR_NAMES[i]); sender->send("</option>"); }
    sender->send("</select><span style=\"margin:0 10px; font-size:20px;\">&lt;</span><select name=\"heat_on_t2\" style=\"width:150px;\">");
    for (int i = 0; i < SENSOR_COUNT; i++) { sender->send("<option value=\""); char val[4]; snprintf(val, sizeof(val), "%d", i); sender->send(val); sender->send("\""); if(h_on_t2 == i) sender->send(" selected"); sender->send(">"); sender->send(SENSOR_NAMES[i]); sender->send("</option>"); }
    sender->send("</select><span style=\"margin:0 10px; font-size:20px;\">o</span>");
    char buf[16]; snprintf(buf, sizeof(buf), "%d", h_on_diff); sender->send("<input type=\"number\" name=\"heat_on_diff\" style=\"width:80px;\" value=\""); sender->send(buf); sender->send("\"></div></div><hr style=\"border: 0; border-top: 1px solid #eee;\">");

    sender->send("<div class=\"form-field\" style=\"display:block;\"><label style=\"display:block; margin-bottom:8px;\">Wyłączenie</label><div style=\"display: flex; align-items: center;\"><select name=\"heat_off_t1\" style=\"width:150px;\">");
    for (int i = 0; i < SENSOR_COUNT; i++) { sender->send("<option value=\""); char val[4]; snprintf(val, sizeof(val), "%d", i); sender->send(val); sender->send("\""); if(h_off_t1 == i) sender->send(" selected"); sender->send(">"); sender->send(SENSOR_NAMES[i]); sender->send("</option>"); }
    sender->send("</select><span style=\"margin:0 10px; font-size:20px;\">&lt;</span><select name=\"heat_off_t2\" style=\"width:150px;\">");
    for (int i = 0; i < SENSOR_COUNT; i++) { sender->send("<option value=\""); char val[4]; snprintf(val, sizeof(val), "%d", i); sender->send(val); sender->send("\""); if(h_off_t2 == i) sender->send(" selected"); sender->send(">"); sender->send(SENSOR_NAMES[i]); sender->send("</option>"); }
    sender->send("</select><span style=\"margin:0 10px; font-size:20px;\">o</span>");
    snprintf(buf, sizeof(buf), "%d", h_off_diff); sender->send("<input type=\"number\" name=\"heat_off_diff\" style=\"width:80px;\" value=\""); sender->send(buf); sender->send("\"></div></div><hr style=\"border: 0; border-top: 1px solid #eee;\">");
    
    sender->send("<div class=\"form-field\" style=\"display: flex; align-items: center;\"><label style=\"margin: 0 15px 0 0; cursor: pointer;\">Minimalny czas pracy (minuty)</label>");
    snprintf(buf, sizeof(buf), "%d", h_min_time); sender->send("<input type=\"number\" name=\"heat_min_time\" style=\"width:100px;\" value=\""); sender->send(buf); sender->send("\"></div></div>");
  }
  bool handleResponse(const char* key, const char* value) override {
    auto cfg = Supla::Storage::ConfigInstance(); if (!cfg) return false;
    if (strcmp(key, "heat_on_t1") == 0)   { cfg->setUInt8("heat_on_t1", atoi(value)); return true; }
    if (strcmp(key, "heat_on_t2") == 0)   { cfg->setUInt8("heat_on_t2", atoi(value)); return true; }
    if (strcmp(key, "heat_on_diff") == 0) { cfg->setInt32("heat_on_diff", atoi(value)); return true; }
    if (strcmp(key, "heat_off_t1") == 0)  { cfg->setUInt8("heat_off_t1", atoi(value)); return true; }
    if (strcmp(key, "heat_off_t2") == 0)  { cfg->setUInt8("heat_off_t2", atoi(value)); return true; }
    if (strcmp(key, "heat_off_diff") == 0){ cfg->setInt32("heat_off_diff", atoi(value)); return true; }
    if (strcmp(key, "heat_min_time") == 0){ cfg->setInt32("heat_min_time", atoi(value)); return true; }
    return false;
  }
};

void scanDS18B20() {
  // GPIO 34 nie ma wewnętrznego pull-upu, ustawiamy jako zwykłe wejście
  // Pamiętaj o zewnętrznym rezystorze 4.7k do 3.3V!
  pinMode(ONE_WIRE_BUS, INPUT); 
  delay(100); 
  
  sensors.begin(); 
  delay(100);
  
  discoveredCount = 0; 
  DeviceAddress tempAddr; 
  oneWire.reset_search();
  
  Serial.println("Rozpoczynam skanowanie czujnikow DS18B20...");
  
  while (oneWire.search(tempAddr) && discoveredCount < 10) {
    if (sensors.validAddress(tempAddr) && tempAddr[0] == 0x28) { 
      memcpy(discoveredAddresses[discoveredCount], tempAddr, sizeof(DeviceAddress)); 
      
      // Log do Serial Monitora, żebyś widział, czy skaner cokolwiek znalazł
      Serial.print("Znaleziono czujnik [");
      Serial.print(discoveredCount);
      Serial.print("]: ");
      for(int b=0; b<8; b++) {
        Serial.printf("%02X", tempAddr[b]);
      }
      Serial.println();
      
      discoveredCount++; 
    }
  }
  
  if (discoveredCount == 0) {
    Serial.println("BŁĄD: Nie znaleziono zadnych czujnikow! Sprawdz rezystor pull-up na GPIO 34.");
  } else {
    Serial.printf("Skanowanie zakonczone. Znaleziono czujnikow: %d\n", discoveredCount);
  }
}

// ======================================================
// MAIN RELAY LOGIC (FIX: POPRAWNA OBSŁUGA BLOKAD DLA LOW-TRIGGER)
// ======================================================
          

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

// ======================================================
// BLOKADA PRZEKAŹNIKÓW
// ======================================================

if (mainLockActive) {

  if (lockPump && relayPump && relayPump->isOn()) {
    relayPump->turnOff();
  }

  if (lockChlorine && relayChlorine && relayChlorine->isOn()) {
    relayChlorine->turnOff();
  }

  if (lockHeating && relayHeatPump && relayHeatPump->isOn()) {
    relayHeatPump->turnOff();
    heatPumpRunningInAuto = false;
  }

  if (lockUV && relayUV && relayUV->isOn()) {
    relayUV->turnOff();
  }
}

  // --- SEKCJA 2: LOGIKA ZALEŻNOŚCI OD POMPY FILTRACYJNEJ ---
  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;
  }

  // --- SEKCJA 3: AUTOMATYKA POMPY OGRZEWANIA (HVAC) ---
  if (hvacPool && relayHeatPump) {

    // blokada ogrzewania
    if (mainLockActive && lockHeating) {

      if (relayHeatPump->isOn()) {
        relayHeatPump->turnOff();
      }

      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.0");

  httpUpdater.setup(suplaServer.getServerPtr(), "/update");

  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 DS18B20AssignmentParameter();
  new RelayLockParameter();
  new RelayDependencyParameter();
  new HeatingPumpParameter();

  // FIX: Włączamy flagę 'true' jako drugi parametr konstruktora Relay.
  // Parametry to: Relay(Pin, true) -> ta druga flaga oznacza "highIsOn = false",
  // czyli informuje Suplę, że przekaźnik włącza się stanem niskim (LOW).
  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");

  // Piny 7 i 8 sterują tranzystorami/płytką HVAC - zostawiamy standardowo
  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);
  buttonLight->addAction(Supla::TOGGLE, relayLight, Supla::ON_PRESS);
  buttonLight->addAction(Supla::TOGGLE, relayLock, Supla::ON_HOLD);

  auto atLight = new Supla::Control::ActionTrigger();
  atLight->setRelatedChannel(relayLight); atLight->attach(buttonLight);
  buttonLight->addAction(0, atLight, Supla::ON_PRESS);
  buttonLight->addAction(0, atLight, 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);

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

  SuplaDevice.setSuplaCACert(suplaCACert);
  SuplaDevice.setSupla3rdPartyCACert(supla3rdCACert);
  SuplaDevice.begin(21);
}

// ======================================================
// LOOP
// ======================================================
void loop() {
  SuplaDevice.iterate();
  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);
  }
}
Pod deklaracją pinów podajemy nazwy termometrów - tyle ile podamy tyle się doda.

Nie moge się uporać z mruganiem przekaźnika gdy kanał jest zblokowany przez kanał zabezpieczenie, chciałem zrobić aby przy blokadzie kanały zablokowane przechodziły do offline ale przekaźniki w padały w pętle.
Nie mogę sprawdzić czy wykrywa i działają termometry, gdyż te które posiadam 4 sztuki nie działa pod tym firmware, ale równie GG ich nie widzi (pierwszy raz mam taki problem).

Oczywiście jakby ktoś sie podjął ewentualnej poprawy byłbym bardzo wdzięczny.

Bazą jest płytka https://templates.blakadder.com/ESP32_Relay_X8.html
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Ktoś by się pochylił nad kodem?
Mi już AI staje dęba.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/
User avatar
Lector
Posts: 2470
Joined: Fri Nov 17, 2017 2:26 pm
Location: Poznań
Has thanked: 13 times
Been thanked: 25 times

Post

Po odłączeniu zasilania teraz moduł przechodzi w tryb pierwszego uruchomienia - parowania.
jakby nie zapisywał danych serwera Supla.
Niespełniony automatyk. :mrgreen:
https://3d-lamp.photos/
https://pool.lector.top/

Return to “Pomoc”