Klasa dla Cubic PM1006K (czujnik z Ikea Vindriktning)

User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

Piszę sobie właśnie klasę dla czujnika, który jest w Ikea Vindriktning (do wykorzystania m.in. z https://www.laskakit.cz/en/laskakit-esp ... sp-32-i2c/). Założenie jest takie, że nie mam głównej płytki z Ikea a tylko sam czujnik. Sterowanie nim ma odbywać się z poziomu ESP (Supli) a nie tylko podsłuchiwanie komunikatów przesyłanych z czujnika do płytki (co robi chyba klasa w GG).

Zamierzam korzystać z https://github.com/kevinlutzer/Arduino-PM1006K

I mam najpierw takie pytanie do @klew:

Inicjowanie czujnika w tej bibliotece wygląda tak:

Code: Select all

    Serial1.begin(PM1006K::BAUD_RATE, SERIAL_8N1, PM1006K_RX_PIN, PM1006K_TX_PIN);
    pm1006k = new PM1006K(&Serial1);
Zasadnicze pytanie (a właściwie trzy odpowiedzi do wyboru)
1. w konstruktorze klasy SensorSupla wywołać dokładnie to co wyżej
2. Definicję seriala zostawić do zrobienia przed utworzeniem elementu klasy SensorSupla i przekazać do niej tylko wskaźnik na seriala
3. w konstruktorze klasy SensorSupla utworzyć nowy element klasy Stream (nie wykorzystywać SerialX) i użyć go do utworzenia obiektu klasy PM1006K?
Który z wariantów jest najbardziej w zgodzie z założeniami z SuplaDevice?

Klasa będzie też sterowała wiatraczkiem. Ale to dopiero plan na za kilka dni. Być może będę miał kolejne pytania z tym związane.
Last edited by malarz on Tue Feb 25, 2025 6:17 pm, edited 1 time in total.
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
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

malarz wrote: Mon Feb 24, 2025 6:43 pm Zasadnicze pytanie (a właściwie trzy odpowiedzi do wyboru)
1. w konstruktorze klasy SensorSupla wywołać dokładnie to co wyżej
2. Definicję seriala zostawić do zrobienia przed utworzeniem elementu klasy SensorSupla i przekazać do niej tylko wskaźnik na seriala
3. w konstruktorze klasy SensorSupla utworzyć nowy element klasy Stream (nie wykorzystywać SerialX) i użyć go do utworzenia obiektu klasy PM1006K?
Który z wariantów jest najbardziej w zgodzie z założeniami z SuplaDevice?

Klasa będzie też sterowała wiatraczkiem. Ale to dopiero plan na za kilka dni. Być może będę miał kolejne pytania z tym związane.
Na pewno nie nazywaj jej "SensorSupla" :)

Możesz dać przekazywanie wskaźnika na serial do konstruktora tej klasy.
A w kostruktorze sprawdzać czy jest nullptr i jeśli jest nullptr, to inicjować domyślnymi wartościami, j/w.
Ewentualnei możesz dać dwa konstruktory: jeden z serialem, drugi z gpio tx i rx. Wtedy albo bierzesz serial zrobiony wcześniej w kodzie, albo generujesz go dla konkretnych gpio.

Stream do czego?
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

klew wrote: Tue Feb 25, 2025 12:39 pm
malarz wrote: Mon Feb 24, 2025 6:43 pm Zasadnicze pytanie (a właściwie trzy odpowiedzi do wyboru)
1. w konstruktorze klasy SensorSupla wywołać dokładnie to co wyżej
2. Definicję seriala zostawić do zrobienia przed utworzeniem elementu klasy SensorSupla i przekazać do niej tylko wskaźnik na seriala
3. w konstruktorze klasy SensorSupla utworzyć nowy element klasy Stream (nie wykorzystywać SerialX) i użyć go do utworzenia obiektu klasy PM1006K?
Który z wariantów jest najbardziej w zgodzie z założeniami z SuplaDevice?

Klasa będzie też sterowała wiatraczkiem. Ale to dopiero plan na za kilka dni. Być może będę miał kolejne pytania z tym związane.
Na pewno nie nazywaj jej "SensorSupla" :)
Szkoda. Taka ładna nazwa. :-)
klew wrote: Tue Feb 25, 2025 12:39 pm Możesz dać przekazywanie wskaźnika na serial do konstruktora tej klasy.
A w kostruktorze sprawdzać czy jest nullptr i jeśli jest nullptr, to inicjować domyślnymi wartościami, j/w.
Ewentualnei możesz dać dwa konstruktory: jeden z serialem, drugi z gpio tx i rx. Wtedy albo bierzesz serial zrobiony wcześniej w kodzie, albo generujesz go dla konkretnych gpio.
To na razie wykorzystuję zmienną Serial1 i konfiguruję ją parametrami wewnątrz konstruktora. Ale nie wiem czy mogę je tak bezkarnie używać. W małym projekcie się to sprawdza (ESP+PM1006K). Nie wiem jak by działało w czymś większym, gdzie potrzeba paru seriali.
klew wrote: Tue Feb 25, 2025 12:39 pm Stream do czego?
Nie Stream, Jakąś kolejną instancję klasy Serial (ale nie wiem jaki to konkretnie typ powinien być). "Stream" bo tak jest nazwany typ zmiennej w konstruktorze PM1006K w bibliotece czujnika.

W międzyczasie już to wczoraj w nocy napisałem. Jak na razie działa. Muszę jeszcze doświadczalnie sprawdzić na jaki czas włączać wiatrak przed odczytem (albo znaleźć informację na ten temat). W tym kodzie jest sztywno ustalony na 15 sekund.

Code: Select all

/*
   Copyright (C) malarz

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   */

// Dependencies:
// https://github.com/kevinlutzer/Arduino-PM1006K

#ifndef SRC_SUPLA_SENSOR_PM1006K_H_
#define SRC_SUPLA_SENSOR_PM1006K_H_

#include <supla/sensor/general_purpose_measurement.h>

#include <PM1006K.h>

namespace Supla {
namespace Sensor {
class pm1006k : public GeneralPurposeMeasurement {
 public:
  // rx_pin, tx_pin: pins to which the sensor is connected
  // refresh: time between readings (in minutes: 1-1440)
  explicit pm1006k(int rx_pin, int tx_pin, int fan_pin = 0, int refresh = 10)
      : GeneralPurposeMeasurement(nullptr, false) {
    if (refresh < 1) {
      refresh = 10;
    } else if (refresh > 1440) {
      refresh = 10;
    }

    // FAN setup
    fanPin = fan_pin;
    if (fanPin) {
      pinMode(fanPin, OUTPUT);
      fanOff = false;
      digitalWrite(fanPin, HIGH);
      SUPLA_LOG_DEBUG("PM1006K FAN: started & on");
    }

    // Setup and create instance of the PM1006K driver
    // The baud rate for the serial connection must be PM1006K::BAUD_RATE.
    Serial1.begin(PM1006K::BAUD_RATE, SERIAL_8N1, rx_pin, tx_pin);
    sensor = new PM1006K(&Serial1);

    refreshIntervalMs = refresh * 60 * 1000;
    setDefaultUnitAfterValue("μg/m³");
    setInitialCaption("PM 2.5");
    getChannel()->setDefaultIcon(8);
  }

  void onInit() override {
    iterateAlways();
  }

  void iterateAlways() override {
    // 15 sec befor reading sensor
    if (millis() - lastReadTime > refreshIntervalMs-15000) {
      if (fanPin && fanOff) {
        fanOff = false;
        digitalWrite(fanPin, HIGH);
        SUPLA_LOG_DEBUG("PM1006K FAN: on");
      }
    }

    if (millis() - lastReadTime > refreshIntervalMs) {
      int32_t value = NAN;
      if(!sensor->takeMeasurement()) {
        SUPLA_LOG_DEBUG("PM1006K: failed to take measurement");
      } else {
        value = sensor->getPM2_5();
        SUPLA_LOG_DEBUG("PM1006K: read: %d", value);
      }

      if (fanPin) {
        fanOff = true;
        digitalWrite(fanPin, LOW);
        SUPLA_LOG_DEBUG("PM1006K FAN: off");
      }

      if (isnan(value) || value <= 0) {
        if (invalidCounter < 3) {
          invalidCounter++;
        } else {
          lastValue = NAN;
        }
      } else {
        invalidCounter = 0;
        lastValue = value;
      }
      channel.setNewValue((double)lastValue);
      lastReadTime = millis();
    }
  }

 protected:
  ::PM1006K* sensor;
  uint32_t refreshIntervalMs = 600000;
  uint32_t lastReadTime = 0;
  uint32_t lastValue = NAN;
  int fanPin = 0;
  bool fanOff = false;
  int invalidCounter = 0;
};

}  // namespace Sensor
}  // namespace Supla

#endif  // SRC_SUPLA_SENSOR_PM1006K_H_
Jakbyś ogólnie stwierdził czy nie ma jakiś poważnych głupot to dopracuję szczegóły i wrzucam na githuba. Zasadniczo to są chyba dwie kwestie:
1. czy rozbić to na plik .cpp i .h, czy może zostać wszystko w .h
2. czy takie użycie seriala jest w porządku, czy lepiej tego tak nie zostawiać, bo ktoś w głównym programie może go użyć do innych zadań
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

Może jeszcze jedno pytanie. Czy metoda "getValue" używana w wielu klasach sensorów jest do czegoś potrzebna? Tutaj całość odczytów wrzuciłem do IterateAlways() po to aby odczyty były zsynchronizowane z włączaniem wiatraczka.
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
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

Łatwiej mi się przegląda i komentuje na github.
Rozdziel pliki na h i cpp. Zanim wrzucisz, upewnij się że esp8266 i Arduino mega się kompilują (spróbuj kompilować jakiś prosty przykład z sd, np Dallas temperaturę)
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

klew wrote: Tue Feb 25, 2025 6:17 pm Łatwiej mi się przegląda i komentuje na github.
Rozdziel pliki na h i cpp. Zanim wrzucisz, upewnij się że esp8266 i Arduino mega się kompilują (spróbuj kompilować jakiś prosty przykład z sd, np Dallas temperaturę)
Już wrzucona od piątku. Niestety nie kompiluje się poprawnie na githubie ze względu na brak biblioteki, z której korzystam. Co trzeba zrobić aby poszło dalej?
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
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

Trzeba dodać bibliotekę do dockera, który to ogarnia (jest w extras docker).
Natomiast klasy, które wymagają zewnętrznych bibliotek najlepiej gdyby były robione jako header only.
Wtedy Arduino ich domyślnie nie kompiluje i nie narzuca to konieczności instalacji bibliotek u wszystkich użytkowników sd.
Najlepsze suple dla Twojego domu :mrgreen:
User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

klew wrote: Tue Feb 25, 2025 6:17 pm Rozdziel pliki na h i cpp.
A ja głupi się posłuchałem :-(
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
User avatar
malarz
Posts: 506
Joined: Wed Jan 27, 2021 4:04 pm
Been thanked: 2 times

Post

klew wrote: Sun Mar 02, 2025 8:56 pm Trzeba dodać bibliotekę do dockera, który to ogarnia (jest w extras docker).
Natomiast klasy, które wymagają zewnętrznych bibliotek najlepiej gdyby były robione jako header only.
Wtedy Arduino ich domyślnie nie kompiluje i nie narzuca to konieczności instalacji bibliotek u wszystkich użytkowników sd.
OK. Podałeś dwa rozwiązania. Które wybieramy?
Próbuję przerobić pomysły na działające projekty w ArduinoIDE.
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

Przepraszam, dawno nic nie było dodawane co wymaga zewnętrznych bibliotek.

Ja bym zrobił obie rzeczy, czyli trzeba tą bibliotekę dodać do dockera oraz zmienić Twój kod na tylko h.

Tylko h, aby nie zmuszać wszystkich do instalacji tej biblioteki.
Docker, aby dodać przykład z użyciem tego i wrzucić do CI aby sprawdzał, czy kompilacja działa
Najlepsze suple dla Twojego domu :mrgreen:

Return to “supla-dev”