Menu
andipanda.dev

Od problemów z Carlinkit do własnej implementacji CarPlay

Od ukończenia CaRadio do nowego pomysłu

W poprzednich artykułach szczegółowo opisywałem rozwój własnego samochodowego systemu multimedialnego CaRadio. Stopniowo projekt osiągnął etap, na którym można było już normalnie korzystać z niego w samochodzie.

Po długiej podróży, podczas której testowałem urządzenie w rzeczywistych warunkach, pozostało jeszcze kilka niedociągnięć do poprawienia. Jednym z nich była obsługa dźwięku podczas korzystania z CarPlay przez adapter Carlinkit.

Problem dotyczył obsługi komunikatów nawigacyjnych.

Kiedy nawigacja zaczynała odtwarzać komunikat głosowy, CaRadio otrzymywało odpowiednie zdarzenie i mogło zmniejszyć głośność muzyki. Nie udało mi się jednak ustalić momentu zakończenia komunikatu.

W rezultacie muzyka albo wracała do poprzedniej głośności zbyt wcześnie, albo pozostawała przyciszona dłużej, niż było to konieczne.

Musiałem zastosować obejście oparte na opóźnieniach czasowych. Po kilku eksperymentach udało mi się dobrać rozwiązanie, które w praktyce działało całkiem dobrze.

Na tym etapie pierwsza wersja CaRadio była właściwie ukończona.

System działał, CarPlay się łączył, muzyka była odtwarzana, a nawigacja funkcjonowała. Co więcej, ta implementacja działa do dziś.

Doskonale jednak rozumiałem, że problem nie został rozwiązany we właściwy sposób. Zamiast reagować na rzeczywiste zdarzenie zakończenia komunikatu nawigacyjnego, program po prostu odczekiwał określony czas.

To właśnie ten niewielki kompromis techniczny ostatecznie skierował mnie na zupełnie nową drogę rozwoju projektu.

Próba zrozumienia protokołu Carlinkit

Chociaż pierwsza wersja CaRadio już działała, problem z wykrywaniem końca komunikatów nawigacyjnych nie dawał mi spokoju. Postanowiłem wrócić do analizy protokołu Carlinkit i spróbować znaleźć lepsze rozwiązanie.

W tym momencie dość dobrze rozumiałem już wymianę danych między CaRadio a adapterem. Nadal jednak pozostawały pytania dotyczące niektórych poleceń i zdarzeń. Być może Carlinkit przesyłał potrzebne informacje, ale po prostu nie wiedziałem, jak je interpretować. Nie wykluczałem również, że sam adapter w ogóle nie udostępniał odpowiednich zdarzeń.

Ponownie zacząłem szukać dokumentacji, kodu źródłowego i wyników inżynierii wstecznej. Tym razem interesowały mnie nie tylko polecenia wymieniane z Carlinkit, lecz także wewnętrzne działanie samego adaptera — w szczególności jego komunikacja z iPhone'em.

Stopniowo poszukiwania zaczęły wykraczać poza pierwotny problem. Coraz częściej trafiałem na materiały dotyczące natywnego połączenia CarPlay bez korzystania z gotowych adapterów. Informacji było niewiele i pojawiały się jedynie we fragmentach, ale właśnie wtedy ponownie zwróciłem uwagę na sprzętowe uwierzytelnianie MFi.

W pewnym momencie zadałem sobie pytanie: jeśli uda mi się zrozumieć mechanizm uwierzytelniania i bezpośredniej wymiany danych z iPhone'em, czy w ogóle będzie można obejść się bez Carlinkit?

MFi i uwierzytelnianie sprzętowe

Podczas poznawania CarPlay ponownie natrafiłem na wzmianki o MFi — programie Apple Made for iPhone.

W określonych scenariuszach komunikacji z urządzeniami Apple wykorzystywany jest specjalny układ uwierzytelniający. Pozwala on wykonywać niezbędne operacje kryptograficzne bez ujawniania kluczy prywatnych bezpośrednio oprogramowaniu.

Pomysł wykorzystania takiego układu we własnej implementacji CarPlay wydawał się interesujący.

Pojawiło się jednak oczywiste pytanie: skąd go wziąć?

Układy MFi nie należą do zwykłych komponentów, które można po prostu wybrać z katalogu części elektronicznych i zamówić w dowolnej ilości.

Miałem jednak adapter Carlinkit U2W Mini.

Postanowiłem więc sprawdzić, co znajduje się w środku.

Rozebranie Carlinkit U2W Mini

Po rozebraniu adaptera zacząłem analizować jego płytkę drukowaną.

Interesowały mnie nie tyle procesor i pamięć, ile elementy odpowiedzialne za komunikację z iPhone'em.

W rezultacie udało mi się odnaleźć potrzebny układ uwierzytelniający.

Było to ważne odkrycie: miałem już nie tylko informacje o istnieniu takich układów, ale również rzeczywisty komponent, z którym mogłem eksperymentować.

Najpierw postanowiłem jednak spróbować nieco innego podejścia.

Gdyby udało mi się uzyskać dostęp do systemu operacyjnego samego adaptera, mógłbym przeanalizować jego oprogramowanie i być może znacznie szybciej zrozumieć protokół.

Próba uzyskania dostępu przez UART

Na płytce U2W Mini znalazłem interfejs UART i spróbowałem wykorzystać go do połączenia z adapterem.

Część zadania udało się rozwiązać dość szybko.

Adapter przesyłał dane przez interfejs szeregowy, dzięki czemu mogłem obserwować jego komunikaty.

Nie udało mi się jednak uzyskać pełnej komunikacji dwukierunkowej.

Mimo że dane z adaptera docierały, wysyłane do niego polecenia nie przynosiły oczekiwanego rezultatu.

Próbowałem ustalić przyczynę, ale szybkie uzyskanie dostępu do systemu operacyjnego okazało się niemożliwe.

Nie chciałem wtedy poświęcać więcej czasu na ten kierunek, dlatego tymczasowo odłożyłem eksperymenty z UART.

Samo rozebranie adaptera przyniosło jednak rezultat: wiedziałem już, gdzie znajduje się układ MFi, i mogłem wrócić do niego później.

Poszukiwanie informacji o Native CarPlay

Po nieudanej próbie uzyskania dostępu do systemu operacyjnego U2W Mini kontynuowałem poszukiwania informacji.

Teraz jednak interesował mnie już nie tylko protokół Carlinkit.

Stopniowo pojawił się inny pomysł: czy korzystanie z Carlinkit jest w ogóle konieczne?

Jeśli udałoby się zrozumieć mechanizmy połączenia z iPhone'em i uzyskać dostęp do odpowiedniego układu uwierzytelniającego, teoretycznie można byłoby spróbować uruchomić CarPlay bezpośrednio na Raspberry Pi.

Oznaczałoby to, że gotowy adapter nie byłby już potrzebny jako pośrednik między iPhone'em a moim systemem multimedialnym.

Problemem był brak informacji.

Publicznie dostępne były pojedyncze fragmenty dokumentacji, dyskusje o protokołach, przykłady komunikacji z MFi oraz niektóre wyniki badań.

Złożenie z tego kompletnej, działającej implementacji nie było jednak łatwym zadaniem.

Poświęciłem sporo czasu na stopniowe zbieranie informacji, dosłownie kawałek po kawałku.

Mimo to znalezione materiały pozwalały przypuszczać, że uruchomienie Native CarPlay na Raspberry Pi jest całkiem możliwe.

I wtedy ponownie przypomniałem sobie o projekcie LIVI.

Nieoczekiwane odkrycie w LIVI

LIVI znałem już z wcześniejszych eksperymentów z CarPlay.

Projekt ten pomógł mi wcześniej zrozumieć niektóre szczegóły komunikacji z adapterami i był jednym ze źródeł informacji podczas tworzenia CaRadio.

Postanowiłem sprawdzić, co zmieniło się od czasu, gdy ostatnio przeglądałem jego kod źródłowy, i ponownie otworzyłem repozytorium.

Odkryłem coś, czego zupełnie się nie spodziewałem.

W LIVI pojawiła się już implementacja Native CarPlay.

Co więcej, odpowiednie zmiany były bardzo świeże. Sądząc po historii projektu, pojawiły się dosłownie w ciągu kilku tygodni, kiedy zajmowałem się testowaniem CaRadio i późniejszymi poprawkami.

Powstała dość zabawna sytuacja.

Poświęciłem mnóstwo czasu na szukanie rozproszonych informacji, analizowanie możliwych metod uwierzytelniania, rozbieranie Carlinkit i próby uzyskania dostępu do jego systemu operacyjnego.

Tymczasem w znanym mi już projekcie pojawiła się implementacja dokładnie tego, do czego stopniowo dochodziłem samodzielnie.

Oczywiście nie oznaczało to, że wystarczy skopiować kilka plików, aby otrzymać w pełni działający system.

Nadal musiałem przeanalizować kod źródłowy, podłączyć własny sprzęt, sprawdzić komunikację z iPhone'em i dostosować niezbędne komponenty do swoich potrzeb.

Teraz miałem jednak znacznie konkretniejszy punkt wyjścia.

Od pomysłu do nowego projektu

Na tym etapie pierwotne zadanie znacznie się zmieniło. Wszystko zaczęło się od próby poprawienia obsługi komunikatów nawigacyjnych w CaRadio, ale teraz pojawiła się możliwość stworzenia własnego połączenia CarPlay bez korzystania z gotowego adaptera.

Po odkryciu implementacji Native CarPlay w LIVI postanowiłem zacząć od części sprzętowej. Potrzebny układ MFi został już odnaleziony na płytce rozebranego Carlinkit U2W Mini. Pozostało ustalić, jak podłączyć go do Raspberry Pi.

Na szczęście układ miał niewiele wyprowadzeń. Dość szybko udało mi się zidentyfikować zasilanie, a analiza dokumentacji procesora V851 pozwoliła ostatecznie potwierdzić, które piny są wykorzystywane przez magistralę I²C.

Przylutowałem cienkie przewody do odpowiednich punktów i podłączyłem je do magistrali I²C Raspberry Pi 3B+.

Następnie uruchomiłem skanowanie magistrali.

I niemal od razu zobaczyłem to, na co liczyłem — adres układu MFi.

Był to niewielki, ale bardzo ważny rezultat. Układ wyprowadzony z gotowego adaptera był teraz wykrywany przez Raspberry Pi jako osobne urządzenie I²C. Kolejnym krokiem miało być poznanie poleceń uwierzytelniania i sprawdzenie, czy uda się wykorzystać ten układ do bezpośredniego połączenia z iPhone'em.

Właśnie od tego momentu rozpoczęły się praktyczne prace nad nowym projektem, który później otrzymał nazwę CaRaBack — od CaRadio i Backend.

Jednak wykrycie układu MFi na magistrali było dopiero pierwszym krokiem. Przede mną stało znacznie trudniejsze zadanie — doprowadzić do tego, aby iPhone nawiązał połączenie z moją własną implementacją CarPlay.

O tym opowiem w następnym artykule.