wtorek, 22 maja 2018

iTAG i Arduino IDE. Czy ESP32 da radę?

Możliwość skutecznego wprzęgnięcia do pracy iTAGów  w domowym IoT wymaga biblioteki  umiejącej obsłużyć taki element. Jest jednak problem. Takiej biblioteki dla Arduino IDE. póki co, jeszcze nie ma. I nie zapowiada się by pojawiła się w najbliższym czasie. BLE to wciąż bardzo nowy i mocno nieustabilizowany system komunikacji szczególnie w niekomercyjnych zastosowaniach. ESP32 ma szansę zmienić tę sytuację podobnie jak ESP8266 przebojem udostępnił WiFi hobbystom elektronikom. Czy tak się stanie zależy jedynie od tego cz pojawi się dobra i przyjazna biblioteka ESP32 BLE pozwalająca połączyć te dwa, na razie niekompatybline" światy. Na razie mamy dostępne jedynie mocno wstępne projekty autorstwa Niela Kolbana.  Do czego już dziś można je wykorzystać to treść dzisiejszego wpisu.

Nie jest oczywiście tak, iż Bluetooth BLE jest nieznany w amatorskiej elektronice. Nawet dla popularnego Arduino wyprodukowano moduły HM-10 i HM11 pozwalające jakoś tam łączyć się z urządzeniami BLE. Kluczem jest stwierdzenie "jakoś tam". Do wykorzystania tych modułów niezbędna jest dodatkowa płytka jakiegokolwiek Aduino - podobnego układu umożliwiającego sterowanie modułem BLE.  I można to zrobić jedynie przedpotopowymi komendami AT. Sytuacja jest więc analogiczna do historii wdrożenia technologii WiFi w system Arduino zanim nastało ESP8266. Dziś w podobnej sytuacji jest nowy flagowy produkt ESPRESS ESP32.

Jesteśmy na bardzo wczesnym etapie wdrożenia technologii BLE w Arduino IDE. Na oficjalnym repozytorium ESP32 wciąż wisi próba wersja biblioteki BLE. Dodajmy - mocno próbna. Totalnie nieprzyjazna,  napisana koszmarnym programistycznym żargonem, źle udokumentowana, z wieloma bugami, zajmująca całą dostępna dla użytkownika pamięć programu ..... Ale jest i "jakoś tam" działa.

Dziś pierwsza  próba  jej wykorzystania do obsługi urządzeń BLE. Zacznę od iTAGów bo jem mam i chciałbym je używać jako proste, tanie i w miarę bezpieczne piloty domowego IoT. I może uda wycisnąć się z nich nieco więcej możliwych funkcji niż tylko obsługa przycisku.

Co będzie potrzebne:


Kod programu

Przejdźmy od razu do sedna. Działający (w miarę) kod umieściłem tu https://github.com/krzyspx/ESP32_BLE_iTAG_press_detection

Jak to skrócie działa? Program automatycznie skanuje w poszukiwaniu urządzeń BLE. Jeśli znajdzie - łączy się z pierwszym znalezionym. Program przechodzi do trybu oczekiwania na odbiór informacji z serwera (iTAGa) Gdy przycisk iTAG zostanie naciśnięty wywoływana jest funkcja informująca o tym zdarzeniu  a następnie program wraca do nasłuchu.  Jeśli nastąpi utrata połączenia z iTAGiem wywoływana jest funkcja informująca o tym zdarzeniu a program powraca do początkowego skanowania. I to na razie tyle.

Cały program to złożenie złożenie różnych funkcji (bloków) z dostępnych na sieci przykładów choć nie wszystkie elementy są dla mnie jasne. Od siebie dodałem elementy sterujące całością programu tak by realizował powyższy schemat działania.

Bloki programu


  • Skanowanie - funkcja poszukiwania modułów BLE. Jeśli zostanie znaleziony wywoływana jest funkcja onResult()  w klasie MyAdvertisedDeviceCallbacks
  • Połączenie i sygnalizacja braku połączenia - jeśli znaleziono moduł BLE posiadający usługę FFE0 (iTAG z przyciskiem) następuje automatyczne z nim połączenie. Test połączenia - w bibliotece zaszyta jest klasa MyClientCallbacks wywołująca funkcje onConnect() i onDisconnect przy połączeniu i rozłączeniu z serwerem (iTAGiem) 
  • Odbiór notyfikacji - jeśli przycisk iTAG zostanie naciśnięty wywoływana jest funkcja notifyCallback
  • Blok sterujący - realizujący schemat działania programu oparty na dwu flagach serverfound - znaleziono iTAG, deviceBleConnected - połączono z iTAGiem


Blok Skanowania

To standardowy fragment kodu z przykładu N Kolbana. U mnie to są dwie procedury. Skanowania

void scanBLEservers() {
BLEDevice::init("");
  serverfound = false;
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(30);
}

i wyświetlania wyszukanych urządzeń BLE

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.println(); Serial.print("BLE Advertised Device found: ");      Serial.println(advertisedDevice.toString().c_str());

      advertisedDevice.getScan()->stop();

      pServerAddress = new BLEAddress(advertisedDevice.getAddress());
      serverfound = true;
    } // onResult
}; // MyAdvertisedDeviceCallbacks

W skanowaniu funkcja  pBLEScan->start(1); rozpoczyna skanowanie przez określony czas. Standardowo jest to 30 sek. U mnie tylko 1 sek. Tak krótki czas nie zatrzymuje mi programu na dłużej a jest wystarczający do odnalezienia wszystkich aktywnych urządzeń.
W procedurze wyświetlania istotna jest funkcja  advertisedDevice.getScan()->stop();
Zatrzymuje ona dalsze skanowanie po odnalezieniu pierwszego urządzenia. Jeśli ją wyrzucę program wyszuka wszystkie aktywne elementy BLE a do dalszej obróbki weźmie ostatni znaleziony.
W przyszłości zamierzam tu dodać warunek "jeśli znajdziesz element o takim adresie to zrób to i to"
A to wynik działania obu procedur przy trzech załączonych iTAGach.

Starting Arduino BLE Client application...

BLE Advertised Device found: Name: ITAG, Address: fc:58:fa:05:a4:62, serviceUUID: 00001803-0000-1000-8000-00805f9b34fb, txPower: 0
BLE Advertised Device found: Name: iTAG            , Address: ff:ff:c2:0e:e0:e5, appearance: 961, serviceUUID: 0000ffe0-0000-1000-8000-00805f9b34fb, txPower: 0
BLE Advertised Device found: Name: ITAG, Address: fc:58:fa:04:cb:03, serviceUUID: 00001803-0000-1000-8000-00805f9b34fb, txPower: 0

Blok Połączenie i sygnalizacja braku połączenia 

Blok połączenia to procedura connectToServer(BLEAddress pAddress) gdzie pAddress to znaleziony podczas skanowania adres urzadzenia. Z tym fragmentem kodu miałem najwięcej kłopotów. Przykład z github typowego klienta BLE zawiera większość potrzebnych procedur ale bez kilku istotnych poprawek nie działał tak jak oczekiwałem.

Pierwsza zmiana to dodanie procedury   pClient->setClientCallbacks(new MyClientCallbacks()); 
i klasy  MyClientCallbacks: odpowiedzialnej

class MyClientCallbacks: public BLEClientCallbacks {
    void onConnect(BLEClient *pClient) {
      deviceBleConnected = true;                                                  // set ble connected flag
      Serial.println("connected to my server");
    };

    void onDisconnect(BLEClient *pClient) {
      pClient->disconnect();
      deviceBleConnected = false;                                                 // clear ble connected flag
      Serial.println("disconnected from my server");
      connected = false;
      serverfound = false;
    }
};

Procedury te generują komunikaty przy nawiązaniu i co ważniejsze po utracie połączenia klient-serwer. Bez tego fragmentu kodu program się sypał przy każdym rozłączeniu iTAGa z ESP32. Procedurę znalazłem przypadkowo i N Kolbana na końcu przykładu i do tego zaremowaną a jej rozwinięcie głęboką ukrytą w innym przykładzie. Nigdzie więcej ta procedura się nie pojawia! nie mówiąc już o choćby podstawowym jej opisie.

Drugi ważny dodatek to fragment kodu załączający notyfikację w iTAGach w których informacja o naciśnięciu przycisku przekazywana jest nie przez zmianę wartości (READ) a poprzez wygenerowanie wiadomości (NOTYFI). O tym, iż należy włączyć notyfikację dowiedziałem się w poprzednim wpisie.

  const uint8_t bothOff[]        = {0x0, 0x0};
  const uint8_t notificationOn[] = {0x1, 0x0};
  const uint8_t indicationOn[]   = {0x2, 0x0};
  const uint8_t bothOn[]         = {0x3, 0x0};

  pRemoteCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t*)notificationOn, 2, true); //rem for iTAG witch "ffe1" characteristic type read - not notyfi
  Serial.println("Nitification ON ");

Bez tego fragmentu dwa moje iTAGi pomimo połączenia z ESP32 nie chcą wysyłać informacji o naciśniętym przycisku. Niestety jeśli przyłączony jest iTAG zielony ten fragment kodu powoduje reset mikrokontrolera. Nie wiem na razie jak to pogodzić ze sobą by działały oba typy iTAGów.

Odbiór notyfikacji


static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
  Serial.println(); Serial.print("Notify from  iTAG");  // Serial.print(" length ");  Serial.println(length);

}

To funkcja zarejestrowana w bloku połączenia poprzez procedurę
pRemoteCharacteristic->registerForNotify(notifyCallback); 

To ważna funkcja zawierająca informacje co dzieje się w iTAG. Poprawny sposób jej użycia by wydobyć z iTAGa wszystkie potrzebne dane jest dla mnie jeszcze zagadką. Na razie wywołanie tej funkcji jest jednoznaczne z naciśnięciem przycisku.

UWAGA:
Notyfikacja pojawia się każdorazowo PO zmianie  stanu wewnątrz iTAG wywołanym naciśnięciem przycisku. Z nieznanych mi przyczyn rejestrowany jest jedynie stan "naciśnięcia" a "zwolnienia" już nie. Tak więc po pierwszym (po uruchomieniu iTAGa) naciśnięciu przycisku i pojawieniu się notyfikacji, następne naciśnięcia NIE GENERUJĄ już wysłania wiadomości. Trzeba programowo wpisać inną wartość do iTAGa niż ta, która jest zapisywana przy naciśnięciu przycisku ("1"). 

W moim programie za takie wpisanie odpowiada procedura

    String newValue = "T";
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); 

Jak widać na obrazku cyklicznie w pętli głównej wpisuję do iTAGa znak "T".

Blok sterujący

To mój wkład w program dekodujący naciskanie przyciku w iTAGu. Wszystko w zasadzie odbywa się w pętli głównej programu. Tylko inicjalizacja biblioteki BLE  BLEDevice::init("") trafiła do setup().

void loop() {
  if (serverfound) {
    if (connected == false) {
      if (connectToServer(*pServerAddress)) {
        connected = true; Serial.println("Server UP");
      } else   {
        Serial.println("Server DOwN");
        deviceBleConnected = false;
      }
    }
  } else {
    scanBLEservers();
  }
  if (deviceBleConnected) {
    Serial.print("+");
    String newValue = "T";
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());     // Set the characteristic's value to be the array of bytes that is actually a string.
  }
  Serial.print("-");
  delay(1000);
} // End of loop

Działanie całego programu opiera się na dwu flagach  serverfound  informującej czy znaleziono urządzenie BLE i  connected ustawianej /zerowanej gdy nastąpiło połączenie / rozłączenie z serwerem BLE.  A efekt działania programu ?



I to na razie tyle w tym temacie. Należy mi się wypoczynek po udanych bojach z biblioteką BLE. Oj daleko jej jeszcze by stała się ona najlepszym przyjacielem elektronika w projektach z użyciem modułów Bluetooth Low Energy.
Więc nie bardzo wiadomo czy i kiedy ciąg dalszy ewentualnie jeszcze nastąpi
   
W poprzednich odcinakach serialu o ESP32 BLE i iTAGach wystąpili:

http://100-x-arduino.blogspot.com/2018/05/itag-ble-jak-je-odczytac.html
http://100-x-arduino.blogspot.com/2018/05/esp32-ble-i-poprawione-arduino-ide.html
http://100-x-arduino.blogspot.com/2018/05/esp32-ble-itag-nowy-rozdzia-iot.html
http://100-x-arduino.blogspot.com/2018/05/esp32-czy-warto.html


Przydatne linki i lektura uzupełniająca

https://evothings.com/control-an-led-using-hm-10-ble-module-an-arduino-and-a-mobile-app/
https://github.com/nkolban/ESP32_BLE_Arduino/blob/master/examples/BLE_client/BLE_client.ino
https://github.com/nkolban/esp32-snippets/issues/269
https://github.com/nkolban/esp32-snippets/issues/391
https://github.com/nkolban/esp32-snippets/issues/397
142

Brak komentarzy:

Publikowanie komentarza