NodeMCU – Implementacja przycisku resetowania

Po lekturze ostatnich atykułów o NodeMCU powinniśmy wiedzieć już jak bezboleśnie skonfigurować i uruchomić ESP8266. Jednak jak na pewno zauważyliście na wszelkiej maści routerach itp. można znaleźć malutki przycisk służący do przywracania urządzenia do stanu fabrycznego. Aby dopełnić moją serię poradników w dzisiejszym artykule dowiemy się jak zaimplementować taki przycisk :)

Implementacja klasy

To w zasadzie pierwszy artykuł o NodeMCU, w któym będziemy potrzebowali fizycznego komponentu – mianowicie przycisku o takeigo np. Zaprogramujemy go na 2 tryby. Krótkie wciśnięcie spowoduje restart urządzenia, a długie przytrzymanie (min. 4 sekundy) poskutkuje wymazaniem wszystkich danych. W tym celu zaimplementujemy sobie prostą klasę zawierającą dwa listenery: shortClickHandler oraz longClickHandler.

#ifndef BUTTONDRIVER_H
#define BUTTONDRIVER_H

class ButtonDriver {
  private:
    static const int LONG_PRESS_TIME = 4000;
    int pin;
    unsigned long buttonPressStartTime;
    bool lastButtonState;
    void (*shortClickHandler)() = NULL;
    void (*longClickHandler)() = NULL;
    
  public:
    ButtonDriver(int pin);
    void poll();
    void setShortClickHandler(void (*handler)());
    void setLongClickHandler(void (*handler)());
};

#endif /* BUTTONDRIVER_H */

Jak widać mamy w tym przypadku więcej pól niż metod. Przeanalizujmy je więc, bo dwa z nich mogą być szczególnie niepokojące dla kogoś kto nie pisał dużo w C++.

Poza wiele mówiącymi buttonPressStartTime oraz lastButtonState i pin mamy dwa tajemnicze zapisy w formie typ (*f)(). Taki zapis mówi kompilatorowi, że w zmiennej o nazwie f przechowujemy adres do jakiejś funkcji bez parametrów, zwracającej typ typ, którą możemy wykonać odwołując się do tej zmiennej. Dzięki temu możemy zaimplementować tzw. listener, któy wykona się w odpowiednim momencie.

void ButtonDriver::setShortClickHandler(void (*handler)()) {
  shortClickHandler = handler;
}

void ButtonDriver::setLongClickHandler(void (*handler)()) {
  longClickHandler = handler;
}

W taki sposób ustawiamy jaka funkcja ma się wykonać przy odpowiednim wydarzeniu. Wystarczy jako parametr do którejś z tych metod przekazać nazwę funkcji, która zwraca void’a i nie przyjmuje parametrów.

Zobaczmy co dzieje się przy tworzeniu obiektu:

ButtonDriver::ButtonDriver(int pin) {
  this->pin = pin;
  pinMode(pin, INPUT_PULLUP);
  lastButtonState = digitalRead(pin);
}

Zapamiętujemy w obiekcie do jakiego pinu podłączyliśmy przycisk, ustawiamy ten pin jako wejście z podciągnięćiem do VCC, i sprawdzamy jego obecny stan.

Na koniec tej prostej klasy pozostało nam już tylko zaimplementowanie metody poll():

void ButtonDriver::poll() {
  bool currentButtonState = digitalRead(pin);
  unsigned long buttonPressedTime = millis() - buttonPressStartTime;
  if(currentButtonState == lastButtonState) {
    return;
  }
  if(currentButtonState) {
    if(buttonPressedTime < LONG_PRESS_TIME) {
      if(shortClickHandler != NULL) {
        shortClickHandler();
      }
    } else {
      if(longClickHandler != NULL) {
        longClickHandler();
      }
    }
  } else {
    buttonPressStartTime = millis();
  }
  lastButtonState = currentButtonState;
}

To tutaj kryje się cała prosta logika tego mechanizmu. Chciałbym jednak zwrócić szczególną uwagę na wywoływanie shortClickHandler longClickHandler. Przed każdym wywołąniem bezwzględnie należy sprawdzić, czy te wskaźniki nie są NULLami! Jeżeli tego nie zrobimy mogą stać się rzeczy niestworzone ;)

Wykorzystanie w programie

Oczywiście pierwsze co musimy zrobić to załączyć naszą bibliotekę:

#include "ButtonDriver.h"

Do każdego przycisku, który chcemy podłączyć należy zainicjować odpowiedni obiekt w taki sposób:

#define BTN_RESET D6

ButtonDriver resetBtn(BTN_RESET);

Aby podłączyć jakieś funkcje do krótkiego i długiego wciśnięcia należy je najpierw napisać. U mnie wyglądają tak:

void reboot() {
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  ESP.reset();
}

void reset() {
  configManager.reset();
  reboot();
}

Teraz w funkcji setup() możemy podpiąć je do naszego obiektu w taki sposób:

resetBtn.setShortClickHandler(reboot);
resetBtn.setLongClickHandler(reset);

Ostatnim krokiem jest wywołanie metody poll() wszędzie tam, gdzie ma działać nasz przycisk. Należy pamiętać że ta funkcja musi wykonywać się cały czas, bez żadnych dłuższych przerw.

resetBtn.poll();

Podsumowanie

To już wszystko, jeżeli chodzi o zapis konfiguracji w pamięci ESP. W kolejnym artykule dowiemy się w jaki sposób napisać prostą aplikację na system Android, która poprzez zaimplementowaną wcześniej w urządzeniu funkcjonalność UDP w przyjazny sposób (bez Packet Sendera) pozwoli je skonfigurować. Będzie to już ostatnia rtykuł z tej serii, ale na pewno nie ostatni o ESP8266 :)