Pytania techniczne dotyczące kodu [bramka ZigBee]

Moderator: vajera

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

vajera wrote: Mon Dec 22, 2025 6:13 pm @klew - zadziałało po tym, jak usunąłem urządzenie z Cloud i pozwoliłem bramce dodać się ponownie - to jest jedyna opcja?


obraz_2025-12-22_191447061.png
W sumie to nie wiem.
Jeśli się nie pojawiło od razu, to może serwer to tylko przy dodawaniu kanałów ustawia.
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
vajera
Posts: 7290
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

klew wrote: Mon Dec 22, 2025 7:52 pm
vajera wrote: Mon Dec 22, 2025 6:13 pm @klew - zadziałało po tym, jak usunąłem urządzenie z Cloud i pozwoliłem bramce dodać się ponownie - to jest jedyna opcja?


obraz_2025-12-22_191447061.png
W sumie to nie wiem.
Jeśli się nie pojawiło od razu, to może serwer to tylko przy dodawaniu kanałów ustawia.
To przy okazji wrzuć przykładowy kod dla tych przycisków Identyfikuj/Resetuj/Usuń, proszę.
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
klew
Posts: 13908
Joined: Thu Jun 27, 2019 12:16 pm
Location: Wrocław
Has thanked: 134 times
Been thanked: 137 times

Post

vajera wrote: Mon Dec 22, 2025 11:29 pm To przy okazji wrzuć przykładowy kod dla tych przycisków Identyfikuj/Resetuj/Usuń, proszę.
Restartuj (w sensie "soft reset" urządzenia) oraz identyfikuj robi się podobnie.
Kanały urządzenia potrzebują flag:

Code: Select all

  channel.setFlag(SUPLA_CHANNEL_FLAG_CALCFG_RESTART_SUBDEVICE);
  channel.setFlag(SUPLA_CHANNEL_FLAG_CALCFG_IDENTIFY_SUBDEVICE);
W kodzie musisz mieć jakiś jeden obiekt dziedziczący po Supla::Element, który obsługuje dane urządzenie. Może to być klasa np. dziedzicząca po Supla::Control::Relay, czy też własna "class Fingerbot : public Supla::Element". W tej klasie robisz override na metodę:

Code: Select all

bool Fingerbot::isOwnerOfSubDeviceId(int subDeviceId) const {
  return (subDeviceId == getChannel()->getSubDeviceId());
}
Identify i restart lecą przez tzw. wiadomości CALCFG. One są odbierane tutaj:
https://github.com/SUPLA/supla-device/b ... c.cpp#L313
We wiadomości będzie podany numer kanału, którego dotyczy wiadomość (cloud wysyła to na pierwszy kanał danego subdevice, ale nie zakładałbym, że tak zawsze będzie).
Następnie dla tego kanału jest szukany "element", który go obsługuje:
https://github.com/SUPLA/supla-device/b ... c.cpp#L342
i jest próbowane odpalenie metody obsługującej CALCFG:
https://github.com/SUPLA/supla-device/b ... c.cpp#L346
Element obsługujący "kanał" może nie wiedzieć jak się obsługuje te CALCFG dotyczące urządzenia, więc jest sprawdzane, czy ten element posiada "subdevice" i jeśli tak, to ta metoda "isOwnerOfSubdeviceId" jest używana, aby znaleźć klasę zajmującą się obsługą tego subdevice. Wtedy na niej jest odpalana ta metoda do obsługi CALCFG:
https://github.com/SUPLA/supla-device/b ... c.cpp#L362

ja tu mam klasę bazową "Device", która mi to załatwia dla wszystkich urządzeń w bramce:

Code: Select all

int Device::handleCalcfgFromServer(TSD_DeviceCalCfgRequest *request) {
  if (request) {
    if (request->Command == SUPLA_CALCFG_CMD_RESTART_SUBDEVICE) {
      if (!request->SuperUserAuthorized) {
        return SUPLA_CALCFG_RESULT_UNAUTHORIZED;
      }
      restartDevice();
      return SUPLA_CALCFG_RESULT_DONE;
    }
    if (request->Command == SUPLA_CALCFG_CMD_IDENTIFY_SUBDEVICE) {
      identifyDevice();
      return SUPLA_CALCFG_RESULT_DONE;
    }
  }
  return SUPLA_CALCFG_RESULT_NOT_SUPPORTED;
}
restartDevice() i identifyDevice() - tu wchodi implemnetacja tych metod dla danego urządzenia.
Najlepsze suple dla Twojego domu :mrgreen:
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

vajera wrote: Mon Dec 22, 2025 11:29 pm To przy okazji wrzuć przykładowy kod dla tych przycisków ... Usuń
Usuń to bardziej skomplikowany temat.
Przycisk "usuń" w Cloud po prostu kasuje dany kanał (jak klikniesz przy subdevice, to kasuje wszystkie kanały danego subdevice), odpowiednio je oznacza i robi restart połączenia z urządzeniem.
Następnie urządzenie się łączy i rejestruje z tymi usuniętymi kanałami (bo urządzenie jeszcze nie wie o usunięciu). W odpowiedzi dostaje channel confilict z informacją, że kanały zostały usunięte.
Urządzenie musi po swojej stronie skasować te kanały (i nie wstawiać niczego na ich miejsce) i ponownie się zarejestrować. Serwer je wspuści dopiero, jak urządzenie skasuje wszystkie kanały skasowane na serwerze.

To ogólnie jest bardziej złożony temat niż te poprzednie przyciski....

W skróce:
Trzeba dodać flagi do SuplaDevice:

Code: Select all

  SuplaDevice.addFlags(SUPLA_DEVICE_FLAG_BLOCK_ADDING_CHANNELS_AFTER_DELETION);
  SuplaDevice.addFlags(SUPLA_DEVICE_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION);
Lub:

Code: Select all

    sensor->getChannel()->setFlag(
        SUPLA_CHANNEL_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION);
Wariant SUPLA_DEVICE_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION pozwala kasować wszystkie kanały z urządzenia (po stronie Clouda). Tego używam w Box.
Natomiast Z2S ma kanały "systemowe", które chyba nie są kasowalne (notyfikacje?)
Wtedy użyj tej flagi SUPLA_CHANNEL_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION - tylko ją trzeba ustawić na wszystkich kanałach, które można kasować (tego używam w HPD Alert RC1 ).

Możesz też tego użyć np. do kasowania "wirtualnych kanałów" z bramki.

Natomiast cała trudność jest w logice kasowania kanałów i urządzeń.

Potrzeba dodać klasę:

Code: Select all

class ConflictResolver : public Supla::Device::ChannelConflictResolver
i zaimplementować tą metodę:

Code: Select all

  virtual bool onChannelConflictReport(
      uint8_t *channelReport,
      uint8_t channelReportSize,
      bool hasConfilictInvalidType,
      bool hasConfilictChannelMissingOnServer,
      bool hasConflictChannelMissingOnDevice) = 0;
Instancję tej klasy podpinasz do:

Code: Select all

SuplaDevice.setChannelConflictResolver(instancja);
Tak wygląda przykładowa implementacja tej metody:

Code: Select all

bool ConflictResolver::onChannelConflictReport(
    uint8_t *channelReport,
    uint8_t channelReportSize,
    bool hasConflictInvalidType,
    bool hasConflictChannelMissingOnServer,
    bool hasConflictChannelMissingOnDevice) {
  if (hasConflictChannelMissingOnDevice) {
    SUPLA_LOG_ERROR("ConflictResolver: Channel conflict - channel missing "
        "on device. Not recoverable. Aborting...");
    return false;
  }
  if (hasConflictInvalidType) {
    SUPLA_LOG_ERROR("ConflictResolver: Channel conflict - channel type "
        "mismatch. Not recoverable. Aborting...");
    return false;
  }
  if (hasConflictChannelMissingOnServer) {
    SUPLA_LOG_INFO(
        "ConflictResolver: Channel conflict - channel missing "
        "on server. Trying to remove affected devices...");
    auto maxChannelNumber = Supla::RegisterDevice::getMaxChannelNumberUsed();
    for (int i = 0; i <= maxChannelNumber; i++) {
      if ((i >= channelReportSize || channelReport[i] == 0) &&
          !Supla::RegisterDevice::isChannelNumberFree(i)) {
        Supla::Element *device = getDeviceByChannelNumber(i);
        if (device != nullptr) {
          Supla::AutoLock lock(sdc->getTimerAccessMutex());
          device->purgeConfig();
          delete device;
          device = nullptr;
          if (!Supla::Storage::IsStateStorageValid()) {
            Supla::Storage::WriteStateStorage();
          }
        }
      }
    }
  }
  return false;
}
Tutaj szukamy "elementów" obsługujących dany channel number. Możliwe, że u Ciebie będzie wygodniej przejść z tego elementu o danym numerze kanału, na element obsługujący dany "subdevice" (w poprzednim poście są linki do supla srpc, gdzie jest przykład jak to zrobić).

Jest teź mutex ustawiony o nazwie "lock". Tutaj lukfud ostatnio trochę działała i ESP32 w Supli pod Arduino dostało obsługę mutexów, więc to powinno działać :). Lock jest potrzebny, aby nie usunąć instancji danej klasy, np. gdy jest dla niej aktywne przetwarzanie w metodach onFastTimer lub onTimer - co by mogło skutkować crashem.

Metoda dostaje info o tym jakiego rodzaju konflikt kanałów jest wykryty. Przy "braku kanału po stronie urządzenia" oraz "niewłaściwy typ kanału" nie ma tutaj procedury ratunkowej ;). Ale ten trzeci typ, to jest to co robimy przy "usuń" po stronie serwera.

Oczywiście to co trzeba usunąć i jak to poprawnie zrobić, to zostaje do implementacji po stronie bramki.
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
vajera
Posts: 7290
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

klew wrote: Tue Dec 23, 2025 8:15 am ...
klew wrote: Tue Dec 23, 2025 8:44 am ...
Dziękuję - jak zawsze spójnie i logicznie :ugeek:
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
vajera
Posts: 7290
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

klew wrote: Tue Dec 23, 2025 8:44 am ....
Mam kilka pytań ;)

1. Rozumiem, że ta poniższa flaga musi zostać włączona, żeby po usunięciu kanału/urządzenia w Cloud pojawił się konflikt i mógł się odpalić ConflictResolver?

Code: Select all

  SuplaDevice.addFlags(SUPLA_DEVICE_FLAG_BLOCK_ADDING_CHANNELS_AFTER_DELETION);
2. Dla większości kanałów włączam:

Code: Select all

    sensor->getChannel()->setFlag(
        SUPLA_CHANNEL_FLAG_ALWAYS_ALLOW_CHANNEL_DELETION);
To powoduje, że pojawia się przycisk Usuń w dwóch miejscach:
Zrzut ekranu 2025-12-27 130653.png
Zrzut ekranu 2025-12-27 130712.png
Pozwala to na skasowanie zarówno pojedynczego kanału, jak i całego urządzenia ze wszystkimi kanałami.

Rozumiem, że na poziomie SuplaDevice nie ma możliwości rozróżnienia, w którym miejscu został naciśnięty ten przycisk?

W bramce rozwiązałem to tak, że na końcu metody onChannelConflictReport sprawdzam, czy zostały jakiekolwiek kanały z danym Zigbee device id - jeżeli nie, to przyjmuję, że mogę również usunąć urządzenie.

Natomiast w Twoim przykładzie :

Code: Select all

bool ConflictResolver::onChannelConflictReport(
    auto maxChannelNumber = Supla::RegisterDevice::getMaxChannelNumberUsed();
    for (int i = 0; i <= maxChannelNumber; i++) {
      if ((i >= channelReportSize || channelReport[i] == 0) &&
          !Supla::RegisterDevice::isChannelNumberFree(i)) {
        Supla::Element *device = getDeviceByChannelNumber(i);
        if (device != nullptr) {
          Supla::AutoLock lock(sdc->getTimerAccessMutex());
          device->purgeConfig();
          delete device;
          device = nullptr;
          if (!Supla::Storage::IsStateStorageValid()) {
            Supla::Storage::WriteStateStorage();
  
wygląda to trochę tak, jakby usunięcie pojedynczego kanału od razu usuwało całe urządzenie?
You do not have the required permissions to view the files attached to this post.
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
vajera
Posts: 7290
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

Sam sobie odpowiadam na pytanie - w przypadku grupowania na subdevices nie da się usunąć pojedynczego kanału (z poziomu Cloud) - próba usunięcia jednego kanału wymusza usunięcie pozostałych kanałów danego subdevice. Oczywiście bramka dalej umożliwia taką opcję z poziomu GUI, ale w Cloud trzeba skasować całe urządzenie.
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
klew
Posts: 13908
Joined: Thu Jun 27, 2019 12:16 pm
Location: Wrocław
Has thanked: 134 times
Been thanked: 137 times

Post

Przycisk parowania urządzeń/czujników w Cloud :)

Dodajemy flagę i ustawiamy wskażnik na naszą klasę obsługującą parowanie:

Code: Select all

  SuplaDevice.addFlags(SUPLA_DEVICE_FLAG_CALCFG_SUBDEVICE_PAIRING);
  SuplaDevice.setSubdevicePairingHandler(pairingManager);
Klasa PairingManager dziedziczy po Supla::Device::SubdevicePairingHanlder i w zasadzie implementuje jedną metodę "startPairing", ale aby to dobrze działało to trzeba trochę własnej logiki dopisać. startPairing dostaje wskaźnik na sprc i na result. Srpc trzeba zapisać, aby potem asynchronicznie podesłać odpowiedź. W result pakujemy odpowiedź na request, coś w tym stylu:

Code: Select all

class PairingManager : public Supla::Device::SubdevicePairingHandler {
 public:
  bool startPairing(Supla::Protocol::SuplaSrpc *srpc, TCalCfg_SubdevicePairingResult *result) override {
    if (result != nullptr) {
      result->MaximumDurationSec = pairingTimeoutSec;
      if (isPairingActive()) {
        SUPLA_LOG_DEBUG("Pairing already in progress");
        if (linkStartTimeMs != 0) {
          result->ElapsedTimeSec = (millis() - linkStartTimeMs) / 1000;
        }
        result->PairingResult = SUPLA_CALCFG_PAIRINGRESULT_ONGOING;
        return false;
      }

      if (state != State::READY) {
        SUPLA_LOG_DEBUG("Pairing not started, device is not ready");
        result->PairingResult = SUPLA_CALCFG_PAIRINGRESULT_NOT_STARTED_NOT_READY;
        return false;
      }

      result->PairingResult = SUPLA_CALCFG_PAIRINGRESULT_PROCEDURE_STARTED;
    }
 
    if (state != State::READY) {
      SUPLA_LOG_DEBUG("Pairing not started, device is not ready");
      return false;
    }

    setState(State::PAIRING);
    linkStartTimeMs = millis();
    this->srpc = srpc;
    return true;
    }
};
Ja w tej powyższej klasie mam zrobioną mini maszynę stanów (state). Dodatkowo mam taką metodę pomocniczą:

Code: Select all

void PairingManager::notifySrpcAboutParingEnd(int pairingResult, const char *deviceName) {
  if (srpc) {
    TCalCfg_SubdevicePairingResult result = {};
    if (linkStartTimeMs != 0) {
      result.ElapsedTimeSec = (millis() - linkStartTimeMs) / 1000;
    }
    int len = 0;
    if (deviceName &&
        pairingResult != SUPLA_CALCFG_PAIRINGRESULT_NO_NEW_DEVICE_FOUND) {
      len = strnlen(deviceName, sizeof(result.Name) - 1);
      strncpy(result.Name, deviceName, len);
      len++;  // NameSize should have a terminating null byte inclueded
    }

    result.MaximumDurationSec = pairingTimeoutSec;
    result.NameSize = len;
    result.PairingResult = pairingResult;

    srpc->sendPendingCalCfgResult(-1, SUPLA_CALCFG_RESULT_TRUE, -1,
        sizeof(result), &result);
    srpc->clearPendingCalCfgResult(-1);
    linkStartTimeMs = 0;
  }
}
Tutaj są stałe jakie można zwarać w rezultacie parowania:
https://github.com/SUPLA/supla-device/b ... to.h#L2231

Czyli ogólnie:
1. jak dostajemy "startPairing" to odpowiadamy w stylu: rozpoczynam, busy/ongoing, nie jestem gotowy (nie wiem czy to ma zastosowanie w z2s).
2. nasza klasa powinna rozpocząc procedurę parowania
3. na koniec używam trzech przypadków:
- znaleziono niewspierane urządzenie (DEVICE_NOT_SUPPORTED) - powinno dać się przesłać nazwę do Clouda (ten "no binding ...")
- znaleziono urządzenie, ale nie mamy już zasobów na obsłużenie więcej (przekroczono limit kanałów, albo mało ramu)
- nie znaleziono urządzenia (timeout)
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
vajera
Posts: 7290
Joined: Wed Oct 31, 2018 7:58 am
Location: Biedrusko
Has thanked: 289 times
Been thanked: 161 times

Post

klew wrote: Wed Dec 31, 2025 8:09 am Przycisk parowania urządzeń/czujników w Cloud :)
...
Pierwsze testy wypadły pomyślnie, ale oczywiście od razu mam pytanie - jak powiadomić Cloud o rozpoczęciu parowania lokalnie? Czy nie ma takiej możliwości?
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
Robert Błaszczak
Posts: 5222
Joined: Sat Dec 22, 2018 8:55 pm
Location: Zielona Góra
Has thanked: 37 times
Been thanked: 27 times

Post

vajera wrote: Sat Jan 10, 2026 12:41 pm
klew wrote: Wed Dec 31, 2025 8:09 am Przycisk parowania urządzeń/czujników w Cloud :)
...
Pierwsze testy wypadły pomyślnie, ale oczywiście od razu mam pytanie - jak powiadomić Cloud o rozpoczęciu parowania lokalnie? Czy nie ma takiej możliwości?
Już mi się micha cieszy :-)

W Auratonie jak wciśniemy na bramce przycisk parowania, to nic się w Cloud nie zmienia. Więc temat przycisku parowania w Cloud dla bramki Z2S możesz uznać za zrealizowany :-)

Z mojej strony już piszę DZIĘKUJĘ BARDZO.
Pozdrawiam
Robert Błaszczak


Moja prywatna strona: www.blaszczak.pl

Return to “Bramka ZigBee”