BLE/GATT: dekodowanie wartości zwracanych przez charakterystyki (np. 16q16) – Mark II

W poprzednich tekstach na temat BTLE (Thingy:52 i Raspberry: odczyt czujników ruchu oraz Thingy 52: usługi pod Raspberry Pi Zero) pokazałem jak odczytywać wartości charakterystyk GATT.  Same wartości zwracane są w postaci binarnych wektorów, które trzeba jakoś zinterpretować, przełożyć na wartości dziesiętne czy zmiennoprzecinkowe.

Tutaj znajdziecie indeks tekstów na temat budowy podstawy teleskopu

Najpierw dokumentacja

Pierwszym krokiem koniecznym do odczytania wartości zwracanych prze usługi BLE jest zrozumienie tego, co one zawierają. Na wyjściu charakterystyki dostaniecie po prostu wektor binarnych danych. Nie ma innego wyjścia – trzeba dokopać się do dokumentacji lub jakiegoś przykładowego kodu.

Zwróćcie tutaj uwagę na UUID samej charakterystyki. Jeżeli zaczyna się w stylu 00001800000010008000 – oznacza charakterystykę wymaganą przez dany profil (typ usługi). Jej dokumentację znajdziecie na stronach Bluetooth SIG.

Gorzej jest w przypadku UUID jak „ef6801009b3549339b1052ffa9740042„. Te identyfikatory zostały stworzone przez samego producenta, który może zdefiniować je według własnego życzenia. Wujek google i dokumentacja producenta – nie ma innego wyjścia.

Big-endian czy little-endian (kolejność bajtów)

Termin „endian” odnosi się do kolejności bajtów w reprezentacji pojedynczej wartości. Porządek big-endian (grubokońcówkowość?! – błagam, nie… wiki) odnosi się do sytuacji, gdy najbardziej znaczący bajt (tzn. ten który najwięcej zmienia w wartości) transmitowany  (zapisywany) jest jako pierwszy. Ten sposób zapisu jest dla nas naturalny. Najpierw patrzymy na setki, potem dziesiątki, na końcu jedności – które najmniej zmieniają (wyobraźcie sobie pieniążki).

Little-endian porządkuje bajty w wartościach odwrotnie – najpierw te mniej znaczące, na końcu te najbardziej znaczące.

Powiedzmy, że transmitujemy liczbę dziesiątkową d310 (będę tutaj używał przedrostka 'd' do oznaczenia liczby zapisanej w systemie dziesiętnym, 'b' – binarnym 0-1, 'x' – szestnastkowym). Liczba ta wymaga zapisu na 2 bajtach:

d310 = x0136

W zapise big – endian transmisja najpierw wyśle x01 a potem x36. W przypadku little-endian – najpierw x36, potem x01. Bity w środku bajtów nie zostaną zamienione.

Jeżeli chodzi o wartości 32 bitowe, sytuacja jest odrobinę bardziej skomplikowana; rozważmy liczbę 0xaabbccdd. W porządku little-endian zostanie wysłana jako 0xddccbbaa:) Zasada najmniej znaczący bajt jako pierwszy rozciąga się tu więc najpierw na całe słowa (zakładając, że są 16-bitowe) a potem na poszczególne bajty w słowach.

Dane w charakterystykach GATT zapisywane są zgodnie z porządkiem little-endian (uwaga: mówię o BLE i pakietach danych).

Fixed Point – stałoprzecinkowe (2q14, 16q16) – bez znaku

Fixed point to sposób zapisu liczb gdzie bity w liczbie na sztywno podzielone są na te, które odnoszą się do części całkowitej – a które do części ułamkowej. W zasadzie nie ma tu jakiś standardów, spotkałęm się z:

  • 2q14: 2 bity (najstarsze) to część całkowita, pozostałe 14 to ułamkowa – a więc mówimy tu o liczbie 16 – bitowej,
  • 16q16 – 16 bitów najstarszych to część całkowita, pozostałe 16 to ułamkowa, mówimy tu o liczbie 32-bitowej.

Przykładowo, dla 2q14: weźmy liczbę x3FFD. W systemie binarnym:

b00111111 11111101

Tu nawiasy oddzielają część całkowitą od ułamkowej:

b(00)(111111 11111101)

Widać, że wartość całkowita to dwa najstarsze bity to „00” – reszta to część ułamkowa. Ale konwersja jest prosta – wystarczy potraktować całość jako liczbę całkowitą i podzielić przez potęgę precyzji części ułamkowej. Dla przykładu, wartość b00111111 11111101 to dziesiętnie d16381. Część ułamkowa ma tu 14 bitów, więc:

x = 16381 / 2^14 = 0.999816895

I podobnie:

2q14/d h Wynik
63 253 x3F FD
(00)(11 1111 1111 1101)
 16381 / 2^14 =
0.999816895
1 15 x01 x0F
b(00)(000001 00001111)
271/2^14 =
0.0165
64 0 x40 x00

(01) (100100 00000000)

25600/2^14

1

Stałoprzecinkowe ze znakiem

Ciekawiej jest w przypadku wartości ze znakiem: rezerwuje się na niego najstarszy bit. Procedura w tym przypadku jest następująca:

  • Jeżeli najstarszy bit ma wartość 1 – zapamiętaj, że to liczba ujemna,
  • Znajdź dopełnienie liczby – negacja,
  • Dodaj 1,
  • Podziel przez potęgę precyzji i zmień znak na przeciwny.
2q14/d h Wynik
255 255 xFF xFF
(11)(111111 11111111)
Odwrócenie:
(00)(000000 00000000)
Dodaję „1”
(00)(000000 00000001)
1/2^14 = 0.000061
i zmiana znaku:  -0.000061
254 240 xFE xF0
(11)(11111011110000)
Dopełnienie:
(00)(00000100001111)
Dodaję '1′
(00)(00000100010000)
276/2^14 = 0.0166
i zmiana znaku: -0.0166

Razem

Dla przykładu, notyfikacja o kierunku:

Weźmy wpis: xfcfe4601. Po kolei:

  • Po konwersji związanej z kolejnością bajtów otrzymamy: x0146fefc,
  • Najważniejszy bit (na pozycji 2^15) ma wartość „0”, więc mówimy o liczbie dodatniej,
  •  x0146fefc to d2143001
  • Część ułamkowa to 16 bitów więc dzielimy przez 2^16 – wychodzi 32.6996

A teraz kod w pythonie (wybaczcie, mam braki w znajomości bibliotek- a więc pewne rzeczy liczę na piechotę):

Przydatne

Kierunek wyraziliśmy w stopniach (32,6996). Pozycje gwiazd (stellarium, kstars) podawane są raczej w miutach i sekundach.  W tym przypadku powinno nam wyjść 32° 41′ 58.56″ (zob. https://www.fcc.gov/media/radio/dms-decimal). Przelicza się to następująco (zob. calculatorsoup.com):

  • stopnie: część całkowita, tu: 32,
  • minuty: to co zostanie (0.6996) pomnóżcie przez 60 – część całkowita wyniku to liczba minut, tu: 0.6996 * 60 = 41,976, a więc 41 minut,
  • sekundy: to, co zostanie (0,976) pomnóżcie przez 60 – i uzyskacie sekundy, 0,976 * 60 = 58.56 sekund

Kod, który znalazłem w sieci (na stackoverflow):

 

Komentarze? Zapraszam na facebook.

Źródła

  • https://www.mathworks.com/help/matlab/import_export/track-orientation-of-bluetooth-low-energy-device.html
  • https://www.mathworks.com/help/matlab/import_export/work-with-device-characteristics-and-descriptors.html
  • https://www.bluetooth.com/specifications/gatt/characteristics/
  • https://www.fcc.gov/media/radio/dms-decimal
  • https://www.calculatorsoup.com/calculators/conversions/convert-decimal-degrees-to-degrees-minutes-seconds.php