ARStreetLamp – opis projektu

Skąd pomysł na projekt?

Będąc studentem w wielkim mieście musiałem wkręcić się w nową grę Pokemon GO (ale oczywiście dopiero jak przeszedł największy szał 😉 ). Dzięki niej spacery ze słuchawkami na uszach nabrały nowego sensu. Ale najbardziej w nowej grze zaintrygowała mnie funkcja AR (rozszerzonej rzeczywistości), bo tym razem nie ograniczała się do statycznych obiektów jak w wielu dostępnych w Internecie demach. A skoro i tak już robiłem kolejne projekty w Xamarinie to trzeba było zorientować się czy mogę coś takiego osiągnąć programując w C#. Mniej więcej w tym samym momencie pojawiła się pierwsza wersja biblioteki ARCore dla mojego Xiaomi Mi 9T. Coraz więcej znaków na niebie dawało nadzieję na fajny ciekawy. Niestety po przejrzeniu przykładów w dokumentacji biblioteki ARCore tworzonej przez Google’a stwierdziłem, że temat może być znaczniej bardziej skomplikowany niż sobie bym tego życzył. Ale tutaj z pomocą przyszedł Microsoft ze swoją dokumentacją oraz twórcy biblioteki UrhoSharp. Trzeba było jeszcze znaleźć jakieś zastosowanie dla rozszerzonej rzeczywistości… I tutaj z inspiracją przyszła moja obecna praca 🙂

O aplikacji

Plan był taki, aby opracować aplikację, która pozwoli handlowcom tworzyć szybkie wizualizacje opraw oświetleniowych (drogowych, do wnętrz, itd.) dla potencjalnych klientów. Wyszedłem z założenia, że każdy pracownik posiada smartphone’a oraz umie używać wbudowanej w niego kamery. Nic by też nie stało na przeszkodzie, aby wykorzystywać aplikację do prezentacji wszystkich produktów firmy bez dodatkowej potrzeby jeżdżenia ciężarówką wypełnioną każdym wariantem produkowanej przez zakład lampy.

Zacząłem od opracowania uproszczonego modelu 3D oprawy Siled Celebra. Na początku, z racji jakiejś znajomości ze szkoły średniej/studiów, postanowiłem wykorzystać do tego oprogramowanie Autodesk Autocad. Pomimo bardzo zadowalającego efektu podczas renderowania oprawy pod różnymi kątami (jak na swój pierwszy raz w świecie 3D) okazało się, że eksport modeli do formatów obsługiwanych przez UrhoSharp’a jest trochę bardziej skomplikowany. Po obejrzeniu wielu YouTube’owych poradników skończyłem z wirtualną maszyną Debian’a i zainstalowanym Blender’em posiadającym plug-in „Urho3D-Blender„. Po wielu podejściach do różnych konfiguracji oraz wersji Blendera i rozszerzenia udało się w końcu trafić na taką, z którą robienie kolejnych modeli było już szybsze i sprawniejsze.

Pierwszy render nowej kanciastej lampy 🙂
Podczas tworzenia nowych modeli dla rozszerzonej rzeczywistości

Po opracowaniu opraw zabrałem się za wykonanie słupa. Tutaj na szczęście mogłem posiłkować się gotowymi modelami (np. śrub z plug-in’u Bolt Factory). No i w końcu coś zaczęło się prezentować.

Modelowanie podstawy słupa

Równolegle do tworzonych modeli 3D powstawał również kod źródłowy robiony w Xamarin Android. SDK zostało wybrane ze względu na pokrycie niemal w 90% funkcji dostarczanych przez Javowe SDK od Google’a i dodatkowo po prostu bardziej jestem Team Microsoft niż Team Google czy Team Oracle.

Kodzenie zacząłem od interfejsu aplikacji (w sumie znany temat, więc chociaż od początku było widać jakiś efekt). Największy problem był z pionowym suwakiem do ustalania wysokości oprawy/długości słupa. Niestety Google nie przewidziało takiej kontrolki w swojej bibliotece. Na szczęście metodą prób i błędów udało się w końcu opracować jakiś kompromis co pozwoliło na przejście do bardziej zaawansowanych rzeczy.

Pierwsze testy aplikacji w terenie

Pierwszą z nich było wykonanie jakiegoś algorytmu pozwalającego na tworzenie całej instalacji lamp składającej się z zadanej ilości. W tym celu w świecie AR należało dodać minimum dwie oprawy, a algorytm robił resztę – obliczał odległość między najdalszymi oprawami i generował symetrycznie kolejne w tych samych odległościach. Do ekstrapolacji zostało wykorzystane proste równanie liniowe – w planach było opracowanie kolejnych wariantów algorytmu wykorzystujących bardziej wymyślne równania krzywych. Niestety świat w którym pracowałem występował w 3 wymiarach, więc estymacji potrzebnych parametrów musiałem dokonywać z pomocą równań macierzowych w funkcji StartCreatingNewInstallation() (czemu oczywiście podołałem 😉 ).

Nocne testy aplikacji. Migotanie opraw powodowane jest przez nadmierne obciążenie procesora telefonu ze względu na jednoczesne generowanie sceny w rozszerzonej rzeczywistości oraz rejestrowanie obrazu z ekranu (bez nagrywania wideo efekt ten nie występował)

Kolejną rzeczą na której mi zależało było generowanie cienia przez słup z lampą zgodnie z aktualnym położeniem słońca. Do wykonania tej funkcjonalności potrzebowałem dodać obsługę kompasu oraz GPS’u. Na bazie tych informacji (oraz aktualnej daty i godziny) mogłem obliczyć położenie słońca na niebie. Później wystarczyło dodać node’a ze źródłem światła znajdującym się w obliczonym miejscu i można było patrzeć jak wirtualny cień pokrywa się z tym rzeczywistym. W planach było jeszcze uzależnienie jasności źródła światła od wartości natężenia światła odczytywanego ze smartphone’owego czujnika, ale ostatecznie nie wcieliłem tego planu w życie.

Testy wirtualnych cieni rzucanych przez lampy vs. rzeczywiste cienie plażowiczów nad polskim Bałtykiem

Iiiiiiii… Potem czar UrhoSharp’a prysnął. Przy próbie wykonania bardziej zaawansowanych rzeczy w tym silniku graficznym zaczęły pojawiać się błędy i artefakty (np. nieprawidłowe generowanie cienia – szczegóły w zrzutach ekranu i nagraniach wideo). Na pomoc w Internecie też za bardzo nie było co liczyć – okazało się, że UrhoSharp był portem silnika graficznego Urho3D i użytkownicy Urho3D oraz UrhoSharp’a za bardzo się nie lubili (przynajmniej takie były moje odczucia, gdy prosiłem o wsparcie) 😉 . Microsoft reklamował UrhoSharp’a na stronie głównej dotyczącej Xamarin’a (na szczęście już z tego pomysłu się wycofał) i niestety dałem się zwieść. Na dodatek dzisiaj nie mam możliwości przetestowania mojej aplikacji na nowym telefonie Samsung Galaxy Note 10+, ponieważ najnowsza wersja ARCore’a generuje wyjątki zaraz po uruchomieniu opracowanej aplikacji (a UrhoSharp już od kilku lat nie jest aktualizowany) – teoretycznie jest możliwość manualnej aktualizacji potrzebnych bibliotek, ale stwierdziłem, że szkoda na to zachodu.

Głównym przekonującym argumentem za UrhoSharp’em (oprócz bardzo dużej prostoty przy tworzeniu wirtualnego świata) była licencja MIT, ale dzisiaj podchodząc jeszcze raz do tego projektu poszedłbym pewnie w stronę Unity albo bezpośrednio w ARCore’a – może kiedyś podejmę się przeportowania aplikacji, kto wie.

Ale co się nauczyłem przy tym projekcie to moje 😉

TODO

Poniżej prezentuję listę rzeczy, które chciałem uwzględnić w rozwoju tej aplikacji:

  • Poprawa działania światła dziennego i generowanego przez nie cienia,
  • Ulepszenie algorytmu do generowania całych instalacji lamp,
  • Dodanie nowych modeli opraw,
  • Opracowanie interfejsu pozwalającego na sterowanie wirtualnymi oprawami za pomocą aplikacji zarządzania inteligentnymi oprawami (CMS).

Zrzuty ekranu

Kody źródłowe

Kody źródłowe

Aplikacja

Tak jak wspominałem wcześniej aplikacja została opracowana raczej jako proof of concept i po nabytych doświadczeniach nie nadaje się raczej do rozwoju/udostępnienia 🙂 Oczywiście jak ktoś będzie naprawdę jej potrzebował to zapraszam do kontaktu/sekcji komentarzy albo zachęcam do własnoręcznej kompilacji ze źródeł dostępnych na moim GitHub’ie.

mearm-stm32f429i

Skąd pomysł na projekt?

Kolejny rok, kolejne święta – w związku z tym musiałem wziąć udział w kolejnej edycji wydarzenia Reddit Gifts, które polega na anonimowej wymianie prezentów przez użytkowników serwisu Reddit. Niestety w tym roku miałem wymianę z osobami z Wielkiej Brytanii, a połączenie tej lokalizacji z Brexitem nie wyszło mi najlepiej (problemy z dostawami przesyłek, Eurotunelem, itd. – ale nie o tym w tym wpisie). Z tej okazji otrzymałem z brytyjskiego Amazona w prezencie „manipulator z linijek”, czyli MeArm Robot Classic Maker Kits. Niestety w najbiedniejszej wersji, więc bez żadnego sterowania – goła konstrukcja z serwomechanizmami FS90MG. I tutaj trzeba było coś wymyślić 😉

O projekcie

Trochę czasu mi zajęło, żeby znaleźć sposób na oprogramowanie ramienia. Na początku musiałem to niestety złożyć, czyli dużo odklejania folii i skręcania konstrukcji. Za późno też znalazłem wideo z całym procesem składania i trochę czasu przez to zmarnowałem. Ale po jakimś czasie konstrukcja była gotowa. Teraz brakowało tylko sterowania.

Przed złożeniem…
…i po 🙂

W sprawie sterowania na początku próbowałem pójść najłatwiejszą drogą, czyli znaleźć coś gotowego. Po przeszukaniu GitHub’a i nie znalezieniu niczego co mógłbym zrobić bez dodatkowych wydatków zrozumiałem, że muszę wziąć sprawy w swoje ręce. Na początku planowałem napisać coś na Arduino czy Rapsberry, ale z jednej strony byłby problem z zakupem potrzebnych sterownikiem serw a z drugiej z wykonaniem jakiegoś przyjemnego interfejsu do sterowania.

Dlatego skończyłem z tym co już w zeszłym roku do takich zadań poznałem, czyli oprogramowaniem TouchGFX, które generuje interfejsy graficzne dla mikrokontrolerów ST – połączenie elastycznego interfejsu graficznego dla ekranu dotykowego i możliwości mikrokontrolera ARM. Do projektu postanowiłem wykorzystać posiadaną przeze mnie płytkę STM32F429I-DISC1. Jest wyposażona w mikrokontroler STM32F429ZIT6 i rezystancyjny ekran dotykowy, a więc wszystko czego potrzebowałem do projektu.

Potem było już z górki. Opracowałem interfejs w TouchGFX, wygenerowałem potrzebne kody źródłowe, dokonałem ich edycji w oprogramowaniu STM32CubeIDE (dodając kody z inicjalizacją i konfiguracją timer’ów, DMA, itd. działające w oparciu o bibliotekę HAL), skompilowałem wszystko i voilà – ramię robota ożyło 🙂 Potem tylko kalibracja serw i hardware był gotowy. Byłem zdziwiony, że do sterowania serwomechanizmami wystarczy napięcie 2.92 V, a nawet 2.80 V (nie wiem czy moja płytka nie jest przypadkiem uszkodzona).

Pierwsze testy gotowego układu
Ostateczna wersja interfejsu pozwalającego na kontrolę ramienia robota

Film z działaniem można zobaczyć poniżej:

Kody źródłowe i schematy

Kody źródłowe i schematy

Podsumowanie

Okazało się, że był to całkiem przyjemny projekt dla osoby, która nigdy wcześniej się takimi rzeczami nie zajmowała (czyli sterowaniem serwomechanizmami). W przyszłości przydałoby się zautomatyzować działanie ramienia, więc pewnie skończy się to na możliwości nagrania sekwencji kolejnych pozycji robota i jej powtarzania – ale to może jak będzie trochę więcej wolnego czasu 😉

Aktualizacja 14.04.2021 r.

Sequence recorder

Stało się to prędzej niż później – funkcja pozwalająca na nagrywanie sekwencji położeń ramienia gotowa! 🙂 Dzięki niej możliwe jest zapętlone powtarzanie zadanych pozycji. Poniżej przykład działania:

Ciężka praca robota 😉

Jak to działa?

W oprogramowaniu TouchGFX opracowałem nowy ekran pozwalający na tworzenie sekwencji (opis okna i przycisków można znaleźć na GitHub’ie):

Zasada działania polega wyznaczeniu w głównym oknie pozycji robota, którą chcemy dodać do sekwencji, a następnie na ekranie sequenceScreen przyciskiem nr 6 dodajemy pozycję do przewijanej listy (nr 4). Czynności powtarzamy do momentu opracowania całej listy. Następnie możemy odtworzyć opracowaną sekwencję przyciskiem nr 7 (uruchamiany wtedy jest asynchroniczny wątek w systemie FreeRTOS odpowiedzialny za przesuwanie ramienia do właściwych pozycji).

Niestety w przypadku zresetowania mikrokontrolera tracona jest cała utworzona lista z powodu przechowywania jej tylko w pamięci RAM. Aby usunąć ten problem postanowiłem dodać zapisywanie listy oraz prędkości odtwarzania sekwencji do nieulotnej pamięci Flash (tej samej w której przechowywany jest mój program). Po analizie rozkładu pamięci Flash w oprogramowaniu STM32CubeIDE wybrałem do tego celu ostatni sektor mikroprocesora (23; nawet w połowie go nie zapełniam). Dodatkowo zabezpieczyłem się przed nadpisywaniem sektora przez linker poprzez zmniejszenie ilości dostępnej pamięci na program o wielkość sektora (128 KB).

Największym problemem była dynamiczna lista, której długość miała być zależna od ilości dodanych pozycji. Niestety po długim czasie kręcenia się w kółko zostałem uświadomiony, że mikrokontrolery nie żyją w zgodzie z dynamiczną alokacją pamięci jak pod klasycznymi systemami operacyjnymi. Ostatecznie skończyłem ze statycznym buforem dla tekstu wszystkich pozycji w liście oraz odpowiadającym im kontenerami positionContainer (czyli bufory oraz kontenery istnieją w pamięci programu przez cały okres jego życia, a w razie potrzeby wykorzystania po prostu edytowane są ich parametry, a następne dodawane do przewijanej listy). I tak bym musiał taką sytuację rozważyć jako najbardziej krytyczny przypadek, gdybym chciał korzystać z dynamicznej alokacji pamięci, więc nie jest to taki problem.

Do komunikacji między wątkami (interfejsu graficznego i sterowania pozycją ramienia) wykorzystuję zmienne bool’owskie klasy sequenceScreenView, które przekazują do funkcji handleTickEvent() polecenia odświeżenia listy pozycji lub obszaru tekstowego dotyczącego informowania użytkownika o stanie działania algorytmu.

Film przedstawiający ostateczną wersję projektu można obejrzeć poniżej:

Podsumowanie

Teraz opracowana aplikacja w końcu wydaje się kompletna 🙂 No i jak na Automatyka i Robotyka przystało w końcu mam własne skonstruowane przeze mnie ramię robota 🙂

W przyszłości może warto byłoby dodać jakieś przesyłanie komend z pozycjami ramienia przez UART/USB, podłączyć ramię do komputera/ESP32 i sterować nim poprzez sieć… Ale to może w jakiejś dalszej w przyszłości, bo pomysłów na projekty tylko przybywa, a trochę gorzej jest z ich realizacją 😉

Pilot – opis projektu

pobierz z Google Play

Skąd pomysł na projekt?

Za studenckich czasów, mieszkając „na pokoju”, korzystałem ze stacjonarnego komputera do oglądania filmów i seriali. Najwygodniejszym miejscem do tego było łóżko. Niestety, jak to często na Netflix’ie czy YouTube’ie bywa, po włączeniu odtwarzania należało wybrać jeszcze odpowiednie ustawienia (jak np. język napisów lub lektora albo rozdzielczość wideo) o czym oczywiście przypominałem sobie jak już byłem wygodnie ułożony na łóżku. Jedynym ratunkiem był smartphone, który zawsze gdzieś tam obok się znajdował. Akurat zbiegło się to z zadaniem wykonania projektu aplikacji na przedmiot Programowanie Sieciowe, więc pomysł był gotowy 😉 Dodatkowo chciałem zacząć zabawę z Xamarin Forms (ze względu na wieloplatformowość, która zawsze mnie ciekawiła).

Byłem świadomy, że istnieją takie rozwiązania jak TeamViewer albo Microsoft Remote Desktop, ale dla mnie był to przerost formy nad treścią. Natomiast inne podobne rozwiązania miały reklamy albo mikropłatności, więc to też nie było to czego potrzebowałem.

O aplikacji serwerowej

Główne okno aplikacji serwera

Pierwotnie na zaliczenie wykonałem prostą aplikację konsolową (v1.0.0.0) opartą o bibliotekę .NET Framework, która miała za zadanie otwierać port TCP i oczekiwać na połączenia klientów. Umożliwiała kontrolowanie myszki i klawiatury oraz uruchamianie stron internetowych (np. Netflix lub Youtube) w domyślnej przeglądarce.

Ze względu na zainteresowania w dziedzinie bezpieczeństwa IT postanowiłem uwzględnić ją w moim projekcie – w tym celu dodałem szyfrowanie symetryczne przesyłanych komend pomiędzy klientem a serwerem. W przypadku .NET Framework’a jest to bardzo proste i wygodne, bo wystarczy odszyfrować przesyłane bajty takim samym hasłem, które zostało wykorzystane do zaszyfrowania (kryptografia symetryczna; hasło jest definiowane przez samego użytkownika). Gdy wystąpi niezgodność w wykorzystywanym kluczu to biblioteka generuje wyjątek, który wystarczy obsłużyć. Nie jest to jeszcze idealne rozwiązanie, bo nadal aplikacja jest podatna na zjawisko powtarzania przechwyconych pakietów, ale szyfrowanie na pewno ogranicza możliwości atakującego.

Chciałem także zautomatyzować wyszukiwanie serwera w sieci, aby osoby mniej techniczne nie musiały weryfikować adresów IP wykorzystywanych komputerów. W tym celu wykorzystałem zestaw technik Zero Configuration Networking. Dzięki nim możliwe jest rozgłaszanie obecności serwera w sieci za pomocą pakietów UDP przesyłanych na porcie 5535. Działanie technik uzależnione jest od konfiguracji sieci (np. router’a), dlatego problemy z automatycznym wykrywaniem serwerów mogą być niezależne od mojej aplikacji.

Na pewno wadą aplikacji w pierwszej wersji było zamykanie połączenia po przesłaniu każdej komendy. Sprowadziło mnie to do zastanawiania się nad przejściem na transmisję UDP, ale okazało się, że wystarczy tylko nie zamykać połączenia, żeby uzyskać szybszą i nadal niezawodną transmisję.

W nowej wersji aplikacji serwera (v2.0.0.0) naprawiono tę wadę. W ramach aplikacji działają dwa wątki: TcpServer (odpowiedzialny za uruchomienie drugiego wątku oraz akceptację połączeń z nowymi klientami i usuwanie klientów z którymi nie ma już połączenia) i ConnectedClientsManager (odpowiedzialny za wykonywanie poleceń odbieranych od aplikacji klientów oraz sprawdzanie czy połączeni klienci nadal są obecni). Dzięki nim możliwe było zredukowanie opóźnień w wyniku braku potrzeby ciągłej akceptacji połączeń od klientów przy odbiorze każdego polecenia.

Dodatkowo w ramach nowej wersji opracowałem interfejs graficzny (wykorzystując bibliotekę WPF), który umożliwia korzystanie z aplikacji serwera w bardziej przyjazny dla użytkownika sposób. Inną wprowadzoną funkcją była możliwość minimalizacji aplikacji do zasobnika systemowego oraz dodawanie do systemowego autostartu. W oparciu o poradniki znalezione w Internecie opracowałem lokalizację dla języka angielskiego (język można zmienić w ustawieniach aplikacji – domyślnie wybierany jest na podstawie języka używanego w systemie operacyjnym). Oba tłumaczenia przechowywane są w postaci plików RESX.

Do opracowania instalatorów wykorzystałem system instalacyjny Inno Setup.

TODO

  • Dodanie obsługi połączenia przez protokół UDP
  • Dodanie zabezpieczenia przed powielaniem pakietów z poleceniami

O aplikacji klienta

Główne okno aplikacji klienta

Głównym wymaganiem w przypadku aplikacji klienta była obsługa systemu operacyjnego Android, aby sterować odtwarzaniem multimediów z poziomu posiadanego przeze mnie smartphone’a. Javą pobawiłem się trochę na początku studiów, ale niestety mnie nie urzekła. Bliżej mi było do języka C# ze względu na większe podobieństwo do poznanych wcześniej języków C/C++ i prostszą współpracę z systemem Windows. Na szczęście zawczasu dowiedziałem się o takim rozwiązaniu jak Xamarin Forms i wtedy postanowiłem nauczyć się programować w oparciu o ten framework. Dzięki temu opracowując kod dla systemu Android automatycznie otrzymywałem gotowy kod dla systemów Windows (UWP) i iOS (niestety ze względu na nieposiadanie żadnego urządzenia Apple nie miałem możliwości przetestowania aplikacji).

Aplikacja klienta pozwala na kontrolowanie myszki, klawiatury (tutaj musiałem użyć widget’u Entry, który domyślnie posiada tekst o długości jednego znaku zawierający spację i w przypadku zmiany zawartości generuje odpowiednio interpretowane zdarzenie), odtwarzania multimediów (wznawianie/zatrzymywanie odtwarzania, zmiana poziomu głośności, zmiana odtwarzanego utworu – operacje te udostępniane są poprzez bibliotekę user32.dll umożliwiającą kontrolę klawiatury) i otwieranie adresów stron WWW.

W ustawieniach aplikacji można wpisać parametry do połączenia z serwerem lub wyszukać dostępne serwery w sieci w oparciu zestaw technik Zero Configuration Networking.

Również w przypadku aplikacji klienta dodano lokalizację dla języka angielskiego. W przypadku, gdy język systemu operacyjnego to język polski, wtedy aplikacja klienta działa również w języku polskim. W każdym innym przypadku aplikacja klienta zostaje uruchomiona w języku angielskim. Również w tym przypadku tłumaczenia zostały oparte o pliki RESX.

TODO

  • Dodanie zabezpieczenia przed powielaniem pakietów z poleceniami
  • Zmiana biblioteki do wykrywania gestów dotyku
  • Zmiana sposobu generowania komendy wciśnięcia prawego przycisku myszy

Jak używać aplikacji?

  1. Na początku należy pobrać najnowszą wersję aplikacji serwera w postaci instalatora (plik *.exe) z mojego Github’a.
  2. Po uruchomieniu aplikacji serwera na komputerze z systemem Windows należy udzielić zgody na połączenie sieciowe (w przypadku monitów od systemowego firewall’a).
  3. W ustawieniach aplikacji serwera należy ustawić numer portu oraz hasło potrzebne do komunikacji (domyślnie jest to port 22222 oraz puste hasło).
  4. Uruchomić serwer poprzez przycisk „Włącz serwer” w głównym oknie.
  5. Pobrać aplikację klienta z mojego Github’a (wersja dla systemu Windows [UWP] lub Android [plik *.apk]) lub ze sklepu Google Play (link na początku tego wpisu) i zainstalować na docelowym urządzeniu.
  6. Uruchomić aplikację klienta i w ustawieniach (ikona z zębatką) ustawić takie same parametry połączenia jak w przypadku serwera (numer portu i hasło; aplikacja klienta i serwera muszą się „widzieć”, czyli najczęściej muszą być uruchomione w tej samej sieci np. WiFi). Adres IP można wpisać ręcznie (jest to adres IP komputera z uruchomioną aplikacją serwera) lub wyszukać automatycznie (konfiguracja sieci może blokować poprawną pracę automatycznego wyszukiwania).
  7. Kliknąć przycisk „Połącz” w ustawieniach. W przypadku, gdy aplikacja klienta podczas próby połączenia wyświetla błąd, należy sprawdzić poprawność wprowadzonych parametrów połączenia. Czasami też połączenie może być blokowane przez konfigurację sieci lub firewall (zaporę sieciową) uruchomiony na komputerze (w takiej sytuacji należy sprawdzić konfigurację firewall’a lub go wyłączyć – jeżeli przy wyłączonym firewall’u wszystko działa poprawnie to należy zmodyfikować konfigurację zapory).
  8. O statusie połączenia informuje ikona w prawym górnym rogu na ekranie głównym w aplikacji klienta. W przypadku uzyskania połączenia, na ekranie głównym, możliwa jest kontrola kursora myszy (za pomocą gestów wykonywanych na szarym obszarze z symbolem myszki komputerowej oraz jednokrotnego tapnięcia dla LPM i dłuższego wciśnięcia obszaru dla PPM) oraz klawiatury. Kontroli multimediów oraz uruchamiania stron internetowych można dokonywać na stronie Skrótów.

W przypadku innych problemów z działaniem zapraszam do zgłaszania błędów w komentarzach lub na Github’ie 🙂 Jestem również otwarty na wprowadzanie nowych funkcji 🙂

Kody źródłowe

Aplikacja serwera

Aplikacja klienta

Aplikacje

Aplikacja serwera

Aplikacja klienta