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:
- iTAG
- moduł ESP32
- Arduino IDE biblioteka ESP32zwiększenie dostępnej pamięci
- bibloteka BLE ESP32
- duużo wolnego czasu
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:
Prześlij komentarz