Etykiety

C# (4) Disclaimer (2) Druk 3D (1) Komputery (10) O blogu (5) Opowiadanie (4) Osobiste (25) Polityka (22) Post odzyskany (36) Programowanie (4) Spostrzeżenia (41) wazektomia (1)
Pokazywanie postów oznaczonych etykietą Komputery. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą Komputery. Pokaż wszystkie posty

30 września 2019

C# samouczek część IV, czyli znam już trochę zmienne i co mogę z nimi zrobić.



W poprzednim odcinku objaśniałem czym są zmienne i co można do nich wpakować. Czego nie wyjaśniałem, to jak z kolei z tych zmiennych korzystać. 


Aby zadeklarować jakąś zmienną musimy w kodzie podać jej typ, oraz nazwę (identyfikator). O ile typy już znamy, o tyle o nazwach jeszcze nic nie wspomniałem. Nazwa naszej zmiennej musi składać się z liter (ale tylko łacińskich, bo amerykanie, którzy opracowali ten język są nieufni wobec liter, których nie ma w ich alfabecie) i może zawierać cyfry, lub znaki “_” lub “@” (chociaż tego drugiego zdecydowanie nie polecam, a wyjaśnię szerzej, gdy dojdziemy do operacji na łańcuchach znaków). Nazwa nie może zaczynać się od cyfry. Generalnie zasady te odnoszą się do wszystkiego co można w C# sobie nazwać po swojemu i nie będę już do nich wracał. Gdybyśmy chcieli więc oznajmić naszemu programowi, że ma stworzyć 32 bitową zmienną całkowitoliczbową i nadać jej imię Zuzanna robimy to tak: 

int Zuzanna; 

Zadeklarowanej w ten sposób zmiennej nie powinniśmy odczytywać (poprawnie ustawiony kompilator zresztą, czy też środowisko programistyczne wskaże nam to jako błąd), ponieważ w tym momencie zawartość tej zmiennej może być dość przypadkowa, ponieważ tworząc zmienną po prostu rezerwujemy sobie na nią pamięć i tyle. Żeby móc więc ją odczytać, najpierw wypadałoby coś do niej zapisać. Aby zapisać coś naszej Zuzannie wystarczy posłużyć się operatorem “=” (operatorów jest trochę więcej, ale o tym jak zwykle - później). 

Zuzanna = 22; 

Zuzanna ma więc wartość 22. Ale to są dwie linijki kodu, a programiści są znani z tego, że aby zaoszczędzić kilka minut czasu są zdolni poświęcić nawet kilka godzin, dlatego wpadli na to, że właściwie wszystko (a więc deklaracje i przypisanie wartości) można zrobić za jednym zamachem. 

int Zuzanna = 22; 

Okazuje się, że z tym lenistwem i oszczędzaniem linijek kodu można pójść nawet dalej i zrobić tak: 

int Zuzanna = 22, Adrian = 23; 

Tak moi drodzy, można nie tylko zadeklarować dwie zmienne na raz, ale też i od razu je zainicjalizować (czyli przypisać im jakąś wartość początkową). Mamy więc już aż dwie zmienne, czy można coś z nimi zrobić? W końcu komputer to po angielsku “maszyna licząca” tak więc możemy coś sobie policzyć na tych zmiennych. Możemy dodeklarować trzecią zmienną “wynik”. 

int wynik; 

I tenże wynik może być np. różnicą wartości Zuzanny i Adriana. 

wynik = Adrian - Zuzanna; 

W tej chwili wynik odejmowania Zuzanny od Adriana, albo wynik odejmowania wartości ukrytych pod tymi identyfikatorami zostaje zapisany do zmiennej “wynik”. Aż się prosi o to, aby sobie trochę rzecz uprościć i za jednym zamachem zadeklarować zmienną i zapisać jej wartość. 

int wynik = Adrian - Zuzanna; 

Nasuwa się więc pytanie, skoro takie jednolinijkowce przechodzą tak bezboleśnie, to czy można w jednej linijce zadeklarować dwie zmienne, zainicjalizować je i wykorzystać do inicjalizacji kolejnej? Otóż nie. Programiści odpowiedzialni za język C# (albo raczej jego architekci) jeszcze nie doszli do tego, że komuś mogłoby to ułatwić życie. Ja osobiście wolę w jednej linijce deklarować i inicjalizować tylko jedną zmienną, bo w razie co łatwiej jest ją wyrzucić. 

Ok. Wiemy już jak deklarujemy zmienne, a czy wiemy gdzie w kodzie można to zrobić? Odpowiedź brzmi prawie wszędzie, ale dokładne miejsce zależy od tego do czego nam ta zmienna będzie służyć i gdzie ma być dostępna. Co do zasady nasze zmienne powinny być niedostępne tam, gdzie nie są potrzebne. Posługując się więc naszym programem z rozdziału drugiego (lekko tylko zmodyfikowanym) postaram się wyjaśnić różnice. 

1  using System;                          
2  using System.Collections.Generic;      
3  using System.Text;                     
4                                         
5  namespace HelloWorld                   
6  {                                      
7                                         
8   class Program                         
9   {                                     
10                                        
11     static void Main(string[] args)    
12     {                                  
13                                        
14       Console.WriteLine("Hello World");
15     }                                  
16   }                                    
17 }                                      

Specjalnie zostawiłem kilka pustych linii aby łatwiej było sobie wyobrazić, że tam właśnie wylądują nasze deklaracje.

Linia 4 - tutaj nie możemy deklarować zmiennych. Nie i już. Możemy oczywiście próbować, ale kompilator się na nas obrazi i nie zechce podjąć współpracy. 

Linia 7 - zmienne umieszczone tutaj będa dostępne dla całej przestrzeni nazw “HelloWorld” (bo taka jest jej nazwa). Przestrzeń nazw pozwala nam połączyć ze sobą w logiczną całość kilka klas, aby było łatwiej się do nich odwoływać. Na tak małym przykładzie trudno jest niestety dostrzec sensowność takiego rozwiązania, ale przydaje się to np. gdy łączymy kilka małych projektów w jedną dużą całość - wtedy nawet gdyby te projekty używały tych samych zmiennych to dzięki poupychaniu ich w różne przestrzenie nazw nie będziemy mieć żadnych konfliktów (choć bardziej niż zmiennych dotyczy to nazw klas). 

Linia 10 - zmienna umieszczona tutaj będzie dostępna dla wszystkich elementów danej klasy (a właściwie instancji tej klasy - ale o tym jak zwykle - później) bez ograniczeń. Pozostałe obiekty mogą używać tych zmiennych tylko gdy są one oznaczone jako publiczne za pomocą słowa kluczowego public przed deklaracją typu, natomiast elementy wewnątrz tej klasy będą mogły sobie używać tej zmiennej do woli. Generalnie nie jest zalecane bezpośrednie wystawianie zmiennych, ale o tym jak sobie z tym poradzić opowiemy sobie, gdy dojdziemy do programowania obiektowego. 

Linia 13 - zmienna zadeklarowana tutaj będzie dostępna tylko wewnątrz funkcji (prawidłowa nazwa, to “metoda”, ale ja po prostu pierwsze kroki w programowaniu stawiałem w Turbo Pascalu i tam było takie fajne rozgraniczenie, że to co nie zwraca wyniku, to procedura, a to co zwraca - to funkcja, potem w C wszystko już było funkcją i jakoś w nazewnictwie na tym się zatrzymałem, bo ludzie już nie wiedzą co to jest procedura, ale jeszcze pamiętają czym jest funkcja, więc generalnie wiedzą o co mi chodzi) w której została zadeklarowana i choćby się skały zesrały nic już z tym nie można zrobić, chyba, że zwrócimy ją jako wynik funkcji. Zgadliście - o tym też będzie później. O czym trzeba pamiętać, że po zakończeniu wykonywania się funkcji zawartość zmiennych jest nie do odzyskania. Każde nowe odpalenie funkcji zaczyna “na świeżo” (program może zwolnić pamięć przydzieloną dla tych zmiennych, co też prędzej czy później się dzieje). 

Dowolny (no prawie dowolny) blok kodu zamknięty nawiasami klamrowymi wewnątrz funkcji - zmienna będzie dostępna tylko w tym bloku kodu, a po jego opuszczeniu zostanie “zapomniana”, a przydzielona jej pamięć będzie prędzej, czy później zwolniona. W przypadku operowania na zmiennych, które zajmują dużo pamięci można posiłkować się deklaracją takiej zmiennej z użyciem słowa kluczowego using, co pozwala na zwolnienie pamięci natychmiast, gdy nie jest już potrzebna. Taki blok kodu możemy zastosować tylko wewnątrz funkcji i wyglądać on może np. tak: 

using (JakasBardzoDuzaKlasa jakisBardzoDuzyObiekt = new JakasBardzoDuzaKlasa()
{
  jakisBardzoDuzyObiekt.JakisSkomplikowanyKodDoWykonania();
}

Na koniec - dobra rada. Wymyślając nazwy dla swoich zmiennych człowiek dość szybko ulega pokusie, aby zastąpić je pojedynczymi literami - bo przecież działa, ale generalnie nie polecam, no może poza sytuacjami, gdy zmienna naprawdę potrzebna jest tylko “na chwilkę”. Na co dzień jednak lepiej, aby nazwa zmiennej odzwierciedlała do czego też owa zmienna ma służyć. W przykładzie powyżej zastosowałem dwie metody nazewnictwa zalecane przez autorów C#. PascalCase stosujemy do elementów dostępnych publicznie i polega na nazywaniu tych elementów zgodnie z ich funkcją zaczynając od wielkiej litery i tak, jeśli mamy gdzieś publiczną funkcję która policzy nam wiek Zuzanny powinniśmy ją nazwać np. ObliczWiekZuzanny. Z kolei do elementów prywatnych, a więc dostępnych tylko dla klasy w której się akurat szarogęsimy zalecany jest tzw. camelCase. Różni się tylko tym, że pierwsze litera jest literą małą - w związku z tym, gdyby nasza funkcja była metodą prywatną powinna się nazywać obliczWiekZuzanny. Gdyby jednak, jakimś cudem zdarzyło się Wam zdryfować w kierunku języków w których nie mamy jako takich typów zmiennych, albo mamy tzw. typowanie słabe (nie wprost), wtedy nieoceniona jest tzw. notacja węgierska, czyli camelCase lub PascalCase poprzedzone pierwszą literką typu - czyli gdyby nasza metoda zwracała wartość całkowitoliczbową nazywałaby się iObliczWiekZuzanny.

W następnym odcinku - operatory matematyczne i logiczne.
W poprzednim odcinku - typy zmiennych.


P.S. Haifisch - cover lepszy od oryginału.

10 września 2019

C# - samouczek, część III - proste typy zmiennych i po co komu te cholerne zmienne.

Zacznijmy od tego, czym jest zmienna. Otóż zmienna jest odnośnikiem do pamięci, który akurat przechowuje dane mogące nam się z różnych przyczyn przydać. Mówiąc bardziej po ludzku - to takie nasze myślowe pudełko i jak to z pudełkami bywa, można coś do niego włożyć, przechować i wyjąć gdy będzie potrzebne. Można też gdzieś wysłać gdy jest taka potrzeba. 


Czym są więc typy zmiennych? Typ zmiennych mówi nam jak program ma interpretować dane, które w tej pamięci siedzą. Nie jest wiedzą tajemną, że każda informacja jaką nasz komputer w sobie przechowuje ma postać cyfrową - najczęściej zer i jedynek, natomiast zmienne pozwalają nam zinterpretować te liczby bądź jako ciąg znaków, bądź liczbę, bądź odpowiedź na pytanie o życie wszechświat i całą resztę (nawiasem mówiąc - to 42). 

W przypadku C# powinniśmy sami określać jaki rodzaj danych mają nasze zmienne (i chociaż da się to trochę obejść, to strasznie utrudnia to poprawianie kodu, który zawiera błędy logiczne), co (wracając do pudełkowej analogii) znowuż możemy sobie wyobrazić jako te nieszczęsne styropianowe wkładki, które powodują, że do naszego pudełka włazi tylko jeden konkretny produkt. Niestety tutaj przydatność pudełka jako analogii kompletnie się kończy, ponieważ ze zmiennymi możemy zrobić kilka sztuczek, które nie udadzą się z żadnym kartonikiem, dlatego zacznijmy już nasz krótki przegląd zmiennych. 

Typy całkowite. 

Typy całkowite pozwalają nam przechowywać liczby całkowite. Ponieważ informację o tym, jak wygląda liczba całkowita większość z nas zdążyła zapomnieć gdzieś w 4 klasie szkoły podstawowej przypomnę, że to po prostu liczba, która nie posiada części ułamkowej. 

Najprostszym typem całkowitym jest byte i jego koleżka sbyte (w C# wielkość liter ma znaczenie, więc aby uniknąć konfliktu z moim edytorem tekstu będę się starał nazwy pisane małą literą umieszczać gdzieś w środku zdania). Jak już sama nazwa wskazuje zajmują w naszej cennej pamięci dokładnie jeden, ośmiobitowy bajt (bajty nie zawsze muszą być ośmiobitowe, choć takie się najczęściej stosuje). Nasuwa się pytanie, czym więc się różnią? Literka “s” w sbyte bierze się od angielskiego “signed”, co w tym wypadku oznacza “ze znakiem”. Dzięki temu sbyte umożliwia nam zapisanie liczby ze znakiem. Pierwszy bit zużyjemy więc na opisanie czy nasza liczba jest dodatnia, czy ujemna, zostanie nam więc jeszcze 7 bitów do zapisania liczby. Każdy bit może przyjąć dwa stany - zero lub jeden, a więc możemy zapisać na nim 2 do 7 kombinacji czyli 128. Jedna kombinacja odpada nam na zapisanie liczby zero, dlatego maksimum jakie możemy zapisać to 127. Ponieważ zero nie ma znaku (komputery jednakże dla ułatwienia przyjmują zero za dodatnie), to dla zakresu liczb ujemnych nic nie musimy zabierać i zostaje nam 128 kombinacji. Dlatego pełny zakres sbyte to od -128 przez 0 do 127. Dla odmiany byte nie przechowuje żadnej informacji o znaku i każdą liczbę zapisaną w tym typie zmiennych traktuje jako dodatnią. Mamy więc do dyspozycji 8 bitów, a więc 2 do 8 potęgi kombinacji, czyli 256, minus jedna kombinacja na zapisanie liczby zero. Daje nam to zakres od 0 do 255. 

Przebrnęliśmy jakoś przez te najprostsze typy, ale zasada co do zakresu jest zawsze podobna. I tak przechodząc do zmiennych całkowitych szesnastobitowych mamy short i ushort. Zderzamy się tutaj z rzeczą bardzo, bardzo często spotykaną w wielu językach programowania. Jest ona straszna i czasem doprowadza do białej gorączki - niekonsekwencja. Tym razem ktoś wymyślił, żeby lepiej nie dodawać dodatkowej literki “s” przed nazwą typu który może być ujemny. Zdecydowano dodać “u” od “unsigned” (czyli "bez znaku") przed typem, który nie może ujemny (chociaż tak naprawdę to byte jest tutaj czarną owcą). Reszta podobnie - w short 1 bit na znak, 15 na liczbę co daje nam zakres od -32768 przez 0 do 32767. W ushort wykorzystujemy pełne 16 bitów co daje nam do dyspozycji zakres od 0 65535. 

Następne w kolejności zmienne 32 bitowe to ulubiony przez wszystkich int i jego rzadziej stosowany brat uint. Podobnie jak poprzednio dla int dysponujemy 31 bitami na liczbę plus jeden na znak, co daje nam dość konkretny już zakres od -2 147 483 648 do 2 147 483 647 (z zerem włącznie) i nie mniej poważny dla uint od 0 do 4 294 967 295. 

Na koniec zostają nam już tylko 64-bitowe potwory long i ulong. W pierwszej zapiszemy liczby od -9 223 372 036 854 775 808 do 9 223 372 036 854 775 807, a w drugiej nie mniej poważny zakres od 0 do 18 446 744 073 709 551 615. Są to już naprawdę poważne liczby, ale mimo to mogą się okazać za małe, niestety aby wykroczyć poza nie trzeba już złożonych typów danych i raczej nie planuję poruszać tego tematu w tym samouczku. 

Typy zmiennoprzecinkowe 

Nie samymi jednak liczbami całkowitymi człowiek żyje i czasem musi popełnić jakiś ułamek. Aby uporać się z tym zadaniem wymyślono typy zmiennoprzecinkowe i tej nazwy raczej nie pamiętacie z matematyki, a to dlatego, że to nie to samo co liczby rzeczywiste poznane w szkole. Generalnie rozchodzi się o sposób zapisu w tzw. notacji naukowej. Gdzie np 125,68 zapiszemy jako 1,2568*10^2.

Zapis bitowy liczby zmiennoprzecinkowej to generalnie koszmar, ale warto mieć w pamięci, że ponieważ bity przyjmują tylko dwa stany wszystko rozchodzi się o podnoszenie dwójki do odpowiedniej potęgi. Z ułamkami jest podobnie, tylko jako podstawę przyjmujemy nie 2, a ½ (czyli 2 do -1) i sumujemy ułamki, aż wyjdzie mniej więcej to co trzeba. Im więcej mamy tych ułamków do dyspozycji tym dokładniej będzie opisana nasza liczba. Niestety trudności sprawiają już nawet bardzo nieskomplikowane liczby jak np. ⅕. W zapisie dwójkowym to by było mniej więcej tak: 0 * ½ + 0 * ¼ + 1*⅛ + 1*1/16 = 3/16 ~ ⅕. Im więcej wstawię ułamków, tym bliżej będę pożądanej wartości, ale nigdy jej w 100% nie osiągnę i zawsze gdzieś pojawi się to nieszczęsne zaokrąglenie. Dlatego w ich przypadku istotniejsza jest precyzja zapisu (więcej ułamków) niż zakres. To wszystko jednak nie wyjaśnia dlaczego te liczby nazywają się zmiennoprzecinkowymi - otóż samo zapisanie ułamka to nie wszystko. W końcu liczby rzeczywiste nie ograniczają się do zakresu od 0 do 1. Aby problem rozwiązać, a sprawy nieco skomplikować znowu musimy gdzieś dorzucić bit znaku, a i jeszcze trzeba by zapisać część całkowitą no i gdzieś przechować wykładnik potęgi… Krótko mówiąc koszmar i tyle. 

Mając jednak to wszystko na uwadze trzeba pamiętać, że liczby zmiennoprzecinkowe są prawie zawsze obarczone pewnym błędem i wiele wielu operacji na tych samych zmiennych tylko sprawę pogarsza. 

Wracając jednak do meritum w C# znajdziemy trzy podstawowe typy zmiennych zmiennoprzecinkowych:
float o zakresie od ±1.5 x 10^(−45) do ±3.4 x 10^(38) i dokładności ~6-9 cyfr
double dające od ±5.0 x 10^(−324) do ±1.7 x 10^(308) i dokładności 15-17 cyfr
decimal w zakresie od ±1.0 x 10^(-28) do ±7.9228 x 10^28 i dokładności 28-29 cyfr

Ponieważ w pamięci komputera zapis tych liczb jest bardzo różny w kodzie musimy podawać jakim dokładnie typem się posługujemy. Aby więc pomóc kompilatorowi rozróżnić jakiego typu jest liczba 0.1 dodajemy na jej końcu odpowiednie literki: f lub F dla float, d lub D (albo po prostu liczbę z kropeczką) dla double i aby dobrze nam się kojarzyło z mamoną - m lub M dla decimal.

Których więc używać? Najszybsze są zmienne typu float, ale też najmniej dokładne. Najdokładniejsze są zmienne typu decimal i z tego tytułu zalecane jest ich używanie przy obliczeniach finansowych, ale ze względu na rozmiar operacje na nich są najwolniejsze - no i za ich pomocą można przechować najmniejsze liczby. Złotym środkiem jest double, który daje średni czas operacji, niezłą dokładność i największy zakres. Niestety oficjalna dokumentacja milczy na temat ilości zajmowanej pamięci przez poszczególne typy, nie mniej jednak należy założyć, że im większa precyzja, tym większy rozmiar. Najprawdopodobniej jednak mamy 32 bity na float, 64 na double i 128 na decimal

Typ znakowy. 

I na koniec bliski koleżka typu byte czyli char. Podobnie jak on zajmuje w pamięci 1 bajt, ale jest interpretowany nie jako liczba, a jako znak. Dokładniej pojedynczy, niesformatowany znak z rozszerzonego zakresu ASCII. Same w sobie używane dosyć rzadko, ale za to nieocenione, gdy weźmie się stadko takich znaków i ułoży z nich napis np. “Koniec tej części samouczka, bo już mnie palce bolą od pisania”. 

Lekcja już wyszła dosyć długa, choć temat należał raczej do tych prostszych, nie mniej jednak warto byłoby poruszyć jeszcze jedną kwestię. Otóż jak i gdzie mamy powiedzieć naszemu programowi, że chcemy użyć jakiejś zmiennej? O tym będzie w następnej części (no i pojawią się jakieś przykłady). 

W poprzednim odcinku: Witaj świecie!

29 sierpnia 2019

C# - samouczek, część II - “Witaj Świecie!” czyli omówienie składni na podstawie najprostszego programu

Jakoś tak przyjęło się, że ucząc się nowego języka wypada stworzyć na nim jakiś program. Tradycyjnie jest to aplikacja, której jedynym zadaniem jest przywitać w imieniu użyszkodnika świat. Ponieważ tradycja ta jest nieszkodliwa - i ponieważ tak twierdzi tytuł tego wpisu - także i ja nie zamierzam porzucić tego zwyczaju.


Zacznijmy więc coś kodzić - w poprzednim wpisie poleciłem IDE Microsoftu znane szerszej publiczności jako Visual Studio (wersja dla użytkowników prywatnych ma swojej nazwie słowo Community i jest wystarczająca dla zdecydowanej większości zastosowań). Aby stworzyć program, który umożliwi nam powitanie ze światem musimy w pierwszej kolejności stworzyć sobie nowy projekt (screeny są z VS 2017, ale dla innych wersji zasada jest podobna):


i wybrać jego typ:


Warto od razu wyrobić sobie nawyk porządnego nazywania swoich aplikacji i biblioteki - inaczej szybko grozić nam będzie organizacyjny burdel, który może być szczególnie bolesny, gdy będziemy chcieli znaleźć kawałek kodu do ponownego wykorzystania. Po kilku miesiącach w morzu katalogów ConsoleApp (lub podobnych) jest to nieco trudniejsze niż się człowiek spodziewa. Idąc więc za swoją radą nazwę swój program HelloWorld. 


Po kliknięciu OK naszym pełnym samozadowolenia oczom ukaże się kod prawie gotowego programu. Należy go uzupełnić o dosłownie jedną linijkę by dotrzeć do celu. Aby tego dokonać musimy wiedzieć nie tylko co napisać, ale też i gdzie. Przyjrzyjmy się więc temu, co już mamy:


using System;
using System.Collections.Generic;
using System.Text;
4  
5  namespace HelloWorld
6  {
7      class Program
8      {
9          static void Main(string[] args)
10         {
11         }
12     }
13 }


A jak widać trochę się tutaj dzieje. Na początek na warsztat bierzemy pięć pierwszych linijek:


using System;
using System.Collections.Generic;
using System.Text;
4  
5  namespace HelloWorld


Pierwsze co rzuca się w oczy to słowo kluczowe “using”. Oznacza ono, że posiłkować się będziemy instrukcjami zawartymi klasach, bądź bibliotekach poza przestrzenią nazw zdefiniowaną w piątej linijce naszego programu. Czym są klasy i biblioteki zajmiemy się ciut później. Na tą chwilę wystarczyć powinna wiedza, że ułatwiają one współdzielenie kodu pomiędzy wieloma aplikacjami, a biblioteki dodatkowo mogą być napisane w innym języku niż ten w którym piszemy. Przestrzeń nazw, to takie ustrojstwo, które ułatwia nam trochę smarowanie kodu rozpisanego w kilku klasach. Możemy wtedy trochę mniej szczegółowo opisywać gdzie dokładnie znajduje się instrukcja, której chcemy użyć. Żeby było śmieszniej tak naprawdę już w tych pierwszych linijkach odwołujemy się nie tylko do biblioteki “System”, ale też i do zdefiniowanych w niej przestrzeni nazw “Text” oraz “Collections” i zawartej w niej podprzestrzeni “Generic”. O tym, że schodzimy w dół po zależnościach świadczą użyte w nazwie kropki. 


Jeśli cały powyższy akapit brzmi dla Ciebie jak bełkot możesz to sobie póki co w głowie przedstawić jako różne grupy instrukcji, które nie są normalnie zdefiniowane w czystej wersji języka i nie zagłębiać się w szczegóły. 


Każdy wiersz “Using” kończy się średnikiem, który oznacza koniec instrukcji. Chociaż wydawałoby się, że instrukcję kończyć powinien znak końca linii (czyli tzw. ENTER), to jednak tak nie jest. wielokrotne spacje, tabulatory, czy “entery” właśnie są przez język właściwie ignorowane w większości przypadków. Jak to się więc przekłada na pisanie kodu? Otóż tak, że zarówno taki zapis:


using System; using System.Collections.Generic; using System.Text;


jak i taki zapis:


using 
    System;
using 
    System.Collections.Generic;
using 
    System.Text;


Będą poprawne. Na pierwszy rzut oka może się to wydawać zbędnym utrudnieniem, ale w tym szaleństwie jest metoda - dzięki niej można tak przedstawić kod, aby jego czytanie było w miarę wygodne. Osobiście dość często korzystam z możliwości zapisania wywołania funkcji w kilku wierszach w sytuacji, gdy pokazanie całego kodu wymagałoby przesuwania widoku w oknie na boki.


Następnie mamy mały, ale dość istotny znak:
6         {
oraz jego nieodłączny braciszek:
13        }


Oznaczają one blok kodu. Nie ważne jaki blok - jakiś. To może być zawartość klasy, funkcji, czy zestaw instrukcji do wykonania po spełnieniu jakiegoś warunku. Każdy otwierający nawias musi mieć zamknięcie, każdy zamykający - najpierw musi być otwarty. Od tej zasady nie ma wyjątków, ale na szczęście ani IDE, ani tym bardziej kompilator nam tego nie przepuści. Trzeba zwracać naprawdę dużą uwagę gdzie i jak umieszcza się nawiasy klamrowe - beztroskie ich rozrzucanie tu i tam skończyć się może kilkugodzinną sesją grzebania w kodzie z hasłem “bo mi tu nie działa!”. Aby się nie zagubić w tym bałaganie bardzo pomocne są wcięcia - bloki kodu będące na tym samym poziomie mają identyczną głębokość wcięcia. IDE Visual Studio będzie się starało w tym pomóc, ale nie zawsze sobie samo poradzi.


Wreszcie dochodzimy do:


7      class Program


C# jest językiem obiektowym i naprawdę wszystko jest w nim obiektem. Aby powstał taki obiekt, potrzebna jest instrukcja jego wykonania i tą instrukcją jest właśnie klasa. W tej linii definiujemy więc klasę program, która jest przepisem na wykonanie obiektu. Tak jak w życiu - jeden przepis możemy wykorzystywać wiele razy i tak samo jest w programowaniu obiektowym - jedna klasa może “dać życie” wielu obiektom. Do tego co można zrobić z klasami (i po co) jeszcze wrócimy. Na tą chwilę wiemy już jak ją zadeklarować.


Na koniec zostaje to co najważniejsze:


9          static void Main(string[] args)
10         {
11         }


Deklaracja funkcji. Dzieje się tu naprawdę dużo. Najpierw mamy tutaj słowo kluczowe “static” oznacza ono, że funkcja którą deklarujemy może być wywołana bez tworzenia jakiegoś konkretnego obiektu z naszej klasy. Drugie słowo kluczowe “void”. Normalnie w tym miejscu deklaracji funkcji powinien stać typ zmiennej jaki zwróci nam funkcja. Ponieważ jednak typów zmiennych jeszcze nie znamy (niektórzy być może jeszcze nie wiedzą co to jest zmienna - spokojnie, będzie wyłożone później) nie chcemy, aby nasza funkcja zwracała cokolwiek, albo aby dosłownie zwróciła nam nic - “void” to w angielskim “pustka”, czyli nasze upragnione nic. Następne słowo “Main” to nazwa naszej funkcji. W językach z rodziny C, czy też C-podobnych jest to funkcja wywoływana zawsze na starcie programu. Następnie “(string[] args)”. Tutaj naprawdę dużo się dzieje i bez zahaczenia o typy zmiennych (i to złożone) ciężko będzie dokładnie wytłumaczyć co się dzieje. Wytłumaczę więc rzecz póki co po łebkach, a z czasem wszystko stanie się jaśniejsze. Otóż w tym nawiasie definiujemy co ma być wejściem dla naszej funkcji (najczęściej dane, choć nie zawsze), ale dla nas póki co najwazniejsze, że ten nawias może być też pusty. W tym konkretnym wypadku będą to argumenty jakie można wpisać po wywołaniu programu w linii poleceń.


Skoro więc jesteśmy w głównej (“Main”) funkcji naszego programu, to właśnie tutaj napiszemy linijkę kodu , która pozwoli przywitać nam świat. Wpychamy się więc pomiędzy nasze nawiasy klamrowe i piszemy:


Console.WriteLine(“Hello World”);


Zacznijmy od tego, że wielkość liter jest ważna w większości języków programowania i C# niej jest tutaj wyjątkiem. Czasem może to prowadzić do pomyłek w kodzie, bo łatwiej nam zapamiętać tekst tak jak on brzmi, a nie jak jest napisany. Na szczęście tutaj też IDE okaże się nam wielce pomocne podpowiadając, które nazwy są dostępne z uwzględnieniem wielkości liter. W przypadku VS najważniejszym wg mnie skrótem klawiszowym jest Ctrl+spacja. Włącza on podpowiedzi, które pozwalają szybko ogarnąć jak jeszcze można pomęczyć nasz kod. Dobra, tyle naskrobałem, a ani słowem nie wytłumaczyłem o co chodzi w linii kodu jaką każę wam wpisać.No to jedziemy. “Console” jest to klasa statyczna (czyli zawierająca same funkcje statyczne - a co to jest już wiemy), która pozwala nam pastwić się nad konsolą, czy jak kto woli wierszem poleceń. “WriteLine” dosłownie znaczy “PiszLinię” i dokładnie to robi - wypisuje linię tekstu na konsoli, natomiast jako argument naszej funkcji wpisujemy w nawiasach “Hello World” (razem z cudzysłowami, a dlaczego - o tym przy okazji typów zmiennych).


Możemy teraz radośnie kliknąć ikonkę:
lub wcisnąć klawisz F5.


Zachurkocze, zafurkocze coś tam mignie jakieś czarne okienko i natychmiast zniknie, a w okienku pod kodem przeczytacie coś w stylu:


(kod 0 oznacza, że nasz program skończył się prawidłowo).


Niektórzy z was - ci nie mający okazji dobrze poznać specyfiki systemu Windows - mogą poczuć się oszukani, no bo jak to?! Program się skończył, a nic nie było widać. Otóż było widać - przez ułamek sekundy w czarnym okienku, które nam mignęło na ekranie. Nie trzymając nikogo dłużej w niepewności podpowiem jak się dobrać do tego wyniku. Przesuwamy wzrok na prawą część ekranu i klikamy prawym przyciskiem myszy “Rozwiązanie XXX” (gdzie XXX to nazwa naszego projektu), a następnie wybieramy:


Co otworzy nam okienko eksploratora w lokalizacji, gdzie znajduje się nasz projekt. Musimy jednak dostać się do folderu “Debug” który można odnaleźć według schematu poniżej (lądujemy w folderze zaznaczonym niebieskim tłem, a chcemy się dostać do folderu zaznaczonym niebieską, nieregularną figurą, która miała być piękną elipsą.
Gdy już tam będziemy trzymając klawisz SHIFT klikamy prawym przyciskiem myszy gdzieś w polu naszego okna i otrzymujemy (w zależności od zainstalowanych programów zawartość okienka może się różnić od trochę do bardzo, bardzo):


I klikamy “Otwórz tutaj okno programu PowerShell” - w Windows 7 będzie to coś bardziej w stylu “Otwórz wiersz poleceń tutaj”, ale jako, że siódemki już jakiś czas nie używam, to dokładnie nie pamiętam (ósemki nie używałem wcale, więc kompletnie nie wiem co tam wypisuje). Jeśli wyskoczy nam niebieskie okienko wpisujemy:
.\helloworld 
(wielkość liter nie ma znaczenia), a jeśli czarne to możemy radośnie pominąć dwa pierwsze znaki (przy założeniu, że komputer ma standardową konfigurację).


Jeśli nic nie wybuchło i kod został prawidłowo przeklejony (bo nie wierzę, żeby ktoś go przepisał - a warto, bo wtedy IDE fajnie siecze podpowiedziami) to wtedy nasz program się z nami przywita.


Gratulacje - omówiliśmy Twój pierwszy program (mój jakoś nie - ja jakoś zaczynam naukę nowych języków od napisania symulatora oddziaływań grawitacyjnych, ale ja to w ogóle dziwny jestem).

17 sierpnia 2019

C# - samouczek, część I - wprowadzenie.

C# to (za wikipedią) obiektowy język programowania opracowany przez firmę Microsoft pod koniec XX wieku. Symbol krzyżyka użyty w nazwie ma nawiązywać do przejścia jakościowego podobnego jak pomiędzy C a C++ (gdzie znak krzyżyka ma jest jakby czterema plusami).

Przyznam szczerze, że mając w pamięci upierdliwość programowania w czystym C objawiającym się takimi kwiatkami jak działanie (lub nie) programu w zależności od liczby linii w kodzie (w tym linii zawierających tylko komentarz, bądź tylko znak końca linii) bardzo długo odrzucałem C++ i C# jako języki, których chciałbym się nauczyć. O ile jednak C++ jakoś do tej pory nie stał mi się specjalnie bliski o tyle C# w swojej czystej formie jest dość prosty, lekki, łatwy i przyjemny, więc nauczenie się go było niejako czystą przyjemnością.

C# jest językiem bardzo uniwersalnym. Umożliwia pisanie programów konsolowych (czyli takich, które komunikują się z użytkownikiem za pomocą komunikatów wyświetlanych w linii poleceń), łatwe pisanie programów zawierających elementy graficzne systemu windows (standardowe przyciski, okna dialogowe etc.), oraz niestandardowe elementy interfejsu (ponieważ wszystko jest obiektem do wszystkiego można przypisać odpowiednie zdarzenia i je obsługiwać, w ten sposób np. obrazek może funkcjonalnie stać się przyciskiem). Ponadto język ten umożliwia pisanie aplikacji webowych, obejmując zarówno frontend (to co widać), jak i backend (czyli silnik pod spodem). Ponadto dość łatwo w C# zaimplementować wielowątkowość.

Z ciekawostek - C# jest jednym z języków w którym można pisać skrypty w środowisku do tworzenia gier Unity. C# ma też składnię bardzo podobną (jeśli nie identyczną) do języka JAVA, jednakże jest pozbawiony kilku z jej upierdliwości - m.in. maszyny wirtualnej, która zżera zasoby jak opętana, czy ręcznego zarządzania pamięcią. Zresztą w początkach istnienia tego języka uznawany był za “kolejny, głupi klon JAVY”. C# jest też językiem który nie jest kompilowany do kody wykonywalnego. Jest on kompilowany do kodu pośredniego, który następnie jest wykonywany przez środowisko .NET. Daje to pewną bardzo fajną właściwość, jaką jest możliwość zmiany kodu w trakcie jego wykonywania. Bardzo ułatwia to poszukiwanie błędów w kodzie i ich poprawę.

Na koniec warto by było wspomnieć o tym czego używać do pisania programów w C#. Teoretycznie do pisania kodu używać można jakiegokolwiek edytora tekstu zapisującego pliki w formacie czysto tekstowym. Wygoda takiego rozwiązania pozostawia jednak sporo do życzenia, dlatego moim zdaniem najlepiej korzystać z rozwiązań zwanych IDE (Integrated Developing Environment), czyli zintegrowanych środowisk programistycznych, które oprócz edytora kodu i kompilatora zawierają wiele narzędzi do debugowania (“odpluskwiania”, czyli usuwania błędów) zawierają też dokumentację języka, bądź umożliwiają łatwy dostęp do niej online. Dla C# obecnie najpopularniejsze IDE to Visual Studio (istnieje darmowa, nieźle rozbudowana wersja dla zastosowań edukacyjnych, dla małych przedsiębiorstw i dużych projektów open source) do pobrania ze stron Microsoftu.

W kolejnej części (o ile kiedyś powstanie) - trochę o składni języka na podstawie słynnego “Hello World”.

P.S.
“Prętem po jajach”

25 stycznia 2019

Science fiction w Twoim domu.

Jestem wielkim fanem SF, a w szczególności Star Treka (sorki drodzy miłośnicy Gwiezdnych Wojen - je też uwielbiam, ale to jednak bardziej fantasy niż SF - deal with it). Spośród wszystkich Star Treków najbardziej ukochałem sobie The Next Generation (Discovery też jest niezłe, ale nie miał jednej fajnej rzeczy, która była w TNG) i niesamowitą technologię, którą nam pokazywał. Najfajniejsze trzy (w kolejności od najfajniejszego, do ociupinkę mniej fajnego od najfajniejszego), to holodek, gdzie można było przeżywać najróżniejsze przygody - póki co musimy zadowolić się VR - transporter, który mógł przenosić materię na duże odległości w mgnieniu oka, choć obecnie znane nam prawa fizyki wykluczają powstanie takiego urządzenia, oraz replikator, który mógł stworzyć dowolną nieożywioną materię - w tym organiczną. I właśnie realizacji tego ostatniego urządzenia jesteśmy najbliżej.

Oczywiście jak to zwykle w bajkach bywa replikatory, z którymi możemy się zetknąć dziś nie mają absolutnie nic wspólnego z tymi z ST:TNG - tamte konwertowały energię na materię, nasze póki co nie potrafią zmieniać jednej substancji w inną. Mowa tutaj o drukarkach 3D. Już w tej chwili istnieją drukarki, które w kilku technologiach drukują plastik, czekoladę, czy szkło. Jeszcze 10-15 lat temu były to niesamowicie drogie urządzenia do zastosowań naukowych czy przemysłowych. Miały umożliwiać szybkie i tanie (w wielu wypadkach na pewno nie to pierwsze, a i z tym drugim różnie bywa) wytworzenie prototypu, który można było przetestować nie tylko na ekranie komputera, ale po prostu w rzeczywistości. Pierwsze wydruki o których słyszałem (i które miałem w ręku jakieś 12 lat temu) powstawały z żywicy światłoutwardzalnej - w wannie z żywicą lasery ultrafioletowe warstwa po warstwie tworzyły obiekt. Nie mniej jednak sama technologia jest trochę starsza, o czym możecie poczytać sobie tutaj.

Dziś mamy jednak rok 2019, oraz dzięki AliExpress możliwość kupowania bezpośrednio od producenta tanich, budżetowych drukarek 3D. W krótkim czasie z korporacyjnych laboratoriów sprzęt ten przewędrował pod strzechy tracąc bardzo dużo ze swej ceny i niewiele z możliwości. Sam od niedawna zostałem dumnym posiadaczem takiej drukarki, choć byłem zbyt niecierpliwy aby czekać na dostawę z Chin i kupiłem ją na naszym rodzimym portalu aukcyjnym (nawet się opłacało, bo dorzucili dwie szpule materiału do drukowania, który idealnie “zasypał” różnicę w cenie, no i dostawa w 3 dni). W tej chwili jestem zajęty drukowaniem części do swojego robota, co nawet nieźle mi idzie.

Samo drukowanie części jest procesem dość długotrwałym - urządzenie mam zwykle tak zaprogramowane, aby grubość nanoszonej warstwy wynosiła 0.1 mm, czyli każdy mm w górę to 10 warstw. Można ustawić większe wartości - zmniejsza to czas wydruku, ale ma też duży wpływ na jego jakość. 0,1 mm to nie jest szczyt możliwości tego urządzenia - potrafi drukować i 0,06mm warstwy, ale już nie przesadzajmy. Na szczęście nie wymaga ciągłej kontroli - zwykle najbardziej ryzykowna jest pierwsza warstwa, ponieważ może się odkleić co uniemożliwia poprawne nałożenie pozostałych warstw i cały model jest do wyrzucenia. Kolejne warstwy nie są już aż tak podatne na zepsucie i do tej pory nie zdarzyło mi się abym musiał przerywać wydruk np. w połowie jeśli tylko pierwsza warstwa się udała. Nie mniej jednak teoretyczna możliwość istnieje dlatego warto czasem zajrzeć czy wszystko idzie cacy.

Bardziej upierdliwe z kolei jest projektowanie części. Co prawda oprogramowanie generujące kod dla drukarki jest w stanie dołożyć w odpowiednich miejscach “łatwoodłamywalne” podpory, ale po pierwsze jest to stracony materiał, a po drugie stracony czas. Dlatego projektując części trzeba mieć na uwadze cały czas ograniczenia jakie wynikają z tej technologii. I tak zawsze trzeba pamiętać, że idziemy od dołu, do góry, że kąty poniżej 45 stopni do podłoża będą wymagać dodatkowych podpór, wreszcie trzeba pamiętać o tym, że materiał z drukarki nie ma takich samych właściwości we wszystkich kierunkach. Dużo łatwiej jest uszkodzić część jeśli siła działa równolegle do płaszczyzny warstw. Co więcej - przynajmniej w przypadku mojej taniochowatej drukarki osie poziome są o wiele mniej dokładne od osi pionowej (być może jest to problem z naciągiem pasków zębatych - jeszcze nie weryfikowałem). I nie ma lekko - jeśli Twój wydruk ma być zrobiony porządnie to trzeba to wszystko wziąć pod uwagę. Z rzeczy, które z kolei ułatwiają życie - można łatwo samemu określić stopień wypełnienia modelu podporami. Typowo tylko 20% wnętrza modelu jest wypełniona - najczęściej przestrzenną konstrukcją, która znacząco zmniejsza wagę nie zmniejszając jakoś dramatycznie wytrzymałości, choć oczywiście nie ma porównania z pełnym modelem.

Bardzo ciekawym aspektem jest też fakt, że na jakość wydruku może mieć wpływ również tzw. slicer, czyli program, który tnie nam model na poszczególne warstwy i generuje kod programu CNC dla naszej drukarki. Co ciekawe dostępnych jest co najmniej kilka takowych, darmowych narzędzi dających bardzo dobre rezultaty. Równie dobrze wygląda kwestia programów do tworzenia modeli 3D w dającym się wydrukować formacie - dostępnych jest całkiem sporo darmowych rozwiązań o różnym stopniu zaawansowania (zarówno narzędzia jak i umiejętności wymaganych przez użyszkodnika).

Duża rozpiętość cenowa modeli drukarek 3D przeróżnych klas i pracujących w najróżniejszych technologiach i z przeróżnym obszarem roboczym powoduje, że każdy może dziś znaleźć sprzęt dla siebie - możliwie najbardziej spełniający jego wymagania przy dostępnym budżecie (chyba, że będzie wymagać super dokładności, w olbrzymiej przestrzeni roboczej i to jeszcze szybko i tanio - wtedy można rzeczywiście przeżyć pewne rozczarowanie). Co więcej - w internetach dostępna jest masa części zamiennych pozwalających bardzo mocno przebudować swój sprzęt przyprawiając go o parametry o jakich nie śniło się jego konstruktorom, a w kilku wypadkach można sobie nawet takie części wydrukować.

Krótko mówiąc - jeśli tylko masz ochotę możesz już dziś mieć w domu odrobinę science fiction.

P.S. Nawet pisząc ten tekst coś drukowałem.
P.S.2 Mój robot ma już prawie wszystkie części na pokładzie i jest szansa, że na wiosnę przejdzie publiczną prezentację.

24 listopada 2018

Walki robotów.

Choć raczej należałoby powiedzieć walka z robotem. Nawet nie z całym robotem, tylko z jego cholernym oprogramowaniem. W dzisiejszym odcinku dowiemy się co to jest SLAM i AHRS, oraz poznamy jeszcze kilka innych ciekawych akronimów.

Jak mówią posługujący się na co dzień językiem Shakespeare’a “najpierw rzeczy pierwsze”. Otóż historia ta zaczyna się na początku tego roku, gdy na swoją zgubę wpadłem na pomysł, aby mojego robocika (który jest tak naprawdę zdalnie sterowanym przez WiFi pojazdem gąsienicowym o możliwościach i wyglądzie zabawki) wyposażyć w LIDAR (ang. akronim od słów Light Detection and Ranging) i odrobinę autonomii. Po sprawdzeniu kosztów zakupu LIDARu pomysł upadł szybciej niż Pakt Stabilizacyjny za poprzednich rządów PiS. Troszkę szperania w podstawowym źródle wiedzy współczesnej ludzkości i ta-dam - dotarłem do tego, że mój pomysł jest oczywiście nie tylko niezbyt oryginalny, ale też, że ma już swoją nazwę - SLAM. SLAM to kolejny anglojęzyczny akronim tym razem od słów Simultaneous Localisation and Mapping (początkowo bałem się, że L jest od LIDAR i ciekawiło mnie jak by się zmienił ten akronim gdyby jednak użyć radaru). W internecie jest wiele ciekawych przykładów robotów, które jeżdżąc sobie po różnych pomieszczeniach jednocześnie rysują ich mapy, pomyślałem więc sobie - czemu nie… i zarzuciłem projekt.

Tak się jednak złożyło, że moja żona wraz z synami wybrali się na jakiś event z robotami jakiś czas później. Dzieciaki były zachwycone tym, co zobaczyły, a i żona była pod wrażeniem, gdy tylko wróciła zostałem zaatakowany hasłem - “startujemy w przyszłym roku!”. Se myślę… OK i przygotowuję w myślach projekt. Miałem już z grubsza obmyślone jakie chcę użyć silniki, akumulatory, jakie sterowanie i zabierałem się za projekt podwozia, gdy nieopatrznie pochwaliłem się żonie kilkoma screenami sprzętu jaki by mi się do tego przydał. Sprzęt niestety miał oprócz fotki bardzo wyraźnie napisaną cenę, tak więc zostałem wyśmiany i skierowany do wymyślenia czegoś tańszego - najlepiej na starym podwoziu, a w najlepszym wypadku na nowym ale z AliExpress. Wstępne założenia projektu właściwie legły w gruzach. Bez większego przekonania pooglądałem sobie co oferuje internet w dziedzinie gotowych podwozi do robotów i po mniej więcej tygodniu doszedłem do wniosku, że albo wydam majątek, albo kupię kolejne gówniane podwozie od zabawki. Skoro jedno już miałem, to nie było sensu zabierać się za drugie. Umyśliłem więc sobie, że powrócimy do projektu SLAM i wystartujemy w konkurencji pokonywania labiryntów. Nauczony doświadczeniem postanowiłem z tych nauk nie skorzystać i wyszukałem sobie kilka naprawdę czujników do realizacji zadania, które nie należały do najtańszych. Po konsultacji z żoną dostałem wybór - albo szukam tańszych rozwiązań, albo mam się pożegnać z tym projektem. No cóż… ponieważ piszę tą notkę, a jeszcze nie dotarłem do AHRS zapewne domyślacie się, że zacząłem coś czarować z tańszymi czujnikami. Ponieważ nie mogłem sobie pozwolić na LIDAR, kupiłem cyfrowy czujnik odległości (w dalszej części będę go nazywał dalmierzem, choć jestem przekonany, że to nie jest poprawna nazwa) z laserem podczerwonym (fajnie wychodzi na zdjęciach), oraz tzw. czujnik położenia o 10 stopniach swobody (częściej jednak używa się angielskiego skrótu DOF - Degrees of Freedom). Oba komunikują się po szynie I2C, można więc ich używać np. z Arduino czy Raspberry Pi.

Założenie było proste - z czujnika położenia ściągam położenie i orientację robota w przestrzeni, natomiast dalmierzem odległość od robota. Razem powinno mi to dać coś na kształt skanera 3D, który będzie sobie ładnie mapował wszystko punkt po punkcie tworząc sobie w pamięci zarys pomieszczenia po którym ma się poruszać. I w tym momencie powinienem wziąć łopatę i pierdolnąć się nią w łeb… Okazało się, że pomiary z czujnika 10-DOF to jakaś porażka, szyna I2C to mniejsza, ale jednak porażka, ale największą porażką okazała się dokumentacja przygotowana przez producenta czujnika. Po kilkukrotnej lekturze producenckiej papierologii ciągle nie wiedziałem jak czytać poszczególne wartości, dopiero później w otchłaniach internetu znalazłem drugi dokument (chuj wie, czemu był osobno) zawierający mapę rejestrów. Dopiero wtedy za pomocą dość zresztą wolnej szyny I2C udało mi się zebrać jakiekolwiek pomiary. Ich stabilność była mniej więcej taka sama jak pijaka po dwóch tygodniach alkoholowego ciągu, czyli żadna, a jeszcze nawet nie dotarłem do żyroskopów! Czesanie internetów ujawniło, że w zasadzie jest to normalka i te pomiary w tańszych akcelerometrach tak wyglądają (przy czym zaznaczano iż tańsze w tym wypadku oznaczają setki dolarów, a mój cały czujnik w przeliczeniu kosztował około 12$ i to z VATem), a jakiej takiej stabilności można oczekiwać po czujnikach droższych (tysiące $), z żyroskopami rzecz miała się w zasadzie podobnie. Załamany wynikami tych rozważań zacząłem rozważać porzucenie projektu, ale wrodzona niechęć do marnowania pieniędzy (przeznaczonych na czujniki) nie pozwoliła mi się poddać. Internety początkowo podsunęły dwa rozwiązania - filtr Kalmana i filtr Alfa-Beta. Pierwszy - podobno lepszy - jest całkiem nieźle opisany teoretycznie, problem jednak polega na tym, że wymaga on matmy na poziomie nie stosowanym przeze mnie od jakichś 15 lat, którą na dodatek kompletnie zapomniałem (pamiętam jeszcze tylko pojedyncze całeczki, ale równania różniczkowego już bym nie rozpykał, a - chwaląc się - byłem w tym całkiem niezły). Na dodatek próby implementacji w Excelu (na podstawie zebranych wcześniej danych) zakończyły się sromotną klęską. Ten filtr poszedł więc w odstawkę. Filtr Alfa-Beta był znacznie prostszy w teorii i implementacji, przy czym nawet dawał jakieś wyniki, ale ustabilizowanie pomiaru (ale nie będę nikogo tutaj oszukiwał - do ideału brakowało dość sporo) obnażyło problem “zanieczyszczenia” wyników grawitacją. Próby prostego, czysto teoretycznego podejścia spaliły na panewce w zasadzie już na etapie rozważań. Żeby “odjąć” grawitację, trzeba znać orientację w przestrzeni, a te cholerne żyroskopy dawały równie gówniane wyniki jak akcelerometry. Kolejna załamka i kolejny zwrot do internetów w poszukiwaniu natchnienia. Tym razem wykopałem wspomniany na wstępie AHRS.

AHRS to jeszcze jeden skrót tym razem od słów Attitude and Heading Reference System, co po naszemu można przetłumaczyć jako układ odniesienia pozycji i kursu. Okazuje się, że są nawet na to gotowe algorytmy korzystające dokładnie z danych jakie zbiera mój czujnik (i to korzystając aż z dziewięciu z nich, bo oprócz wcześniej wspomnianych dochodzi jeszcze magnetometr, który służy jako kompas). Implementacja jednego z nich (konkretnie Madgwicka - podobno najlepszy) i przesłanie wynikowego kwaternionu na mocniejszy komputer, który mógł ładnie zobrazować wyniki rozczarowywała. Dopiero jakiś przypadkowy filmik z YouTube, oraz potwierdzenie tego w dokumentacji czujnika uzmysłowiło mi, że jakiś geniusz wpadł na to, aby osie magnetometru nie pokrywały się z osiami akcelerometru/żyroskopu. Nie wiem kim był człowiek, który to wymyślił i co nim powodowało, ale serdeczne “chuj Ci w dupę” kieruję właśnie do Niego. Po skorygowaniu tego niedopatrzenia wyniki są… dalej rozczarowujące.

Dalszej części historii na razie nie opiszę z dwóch powodów - po pierwsze Google Docs (w którym powstaje ta notka) pokazuje mi, że zacząłem już trzecią stronę tekstu, a to oznacza, że jest on stanowczo za długi, a po drugie dlatego, że dotarłem w tej opowieści do punktu w którym jestem teraz. Tak, ciągle jestem w czarnej dupie, ciągle nie mam zadowalających wyników, a od SLAM jestem kilka lat świetlnych. Dobre wieści są następujące - po analizie użytego przeze mnie algorytmu AHRS doszedłem do wniosku, że może on się sprawdzać najlepiej gdy osie X i Y tworzą płaszczyznę poziomą (albo mniej więcej poziomą), natomiast Z jest skierowane w górę, lub dół. w tej chwili czujnik mam zamontowany w ten sposób, iż osie X i Y tworzą płaszczyznę pionową, ale tak mi po prostu najlepiej pasuje w płytce stykowej. Druga rzecz, która wymaga sprawdzenia to częstotliwość pomiarów. W tej chwili wykonuję około 80 pomiarów na sekundę, ale (podobno) to za mało, drugi więc kierunek działań zakłada wykorzystanie wbudowanej w czujnik 512 bajtowej kolejki FIFO dla akcelerometru i żyroskopu (algorytm Madgwicka podobno zakłada, że dane z magnetometru nie spływają, albo nie zmieniają się tak często jak pozostałe - do sprawdzenia). Bardziej liczę na pierwsze, ale jestem gotowy wprowadzić oba rozwiązania. Drugie jest o tyle gorsze, że komputerowi może zabraknąć mocy obliczeniowej na jednoczesne zbieranie danych i ich obróbkę - trzeba więc będzie wykorzystać do obliczeń drugą, mocniejszą maszynę, ale dotychczasowe próby napawają mnie umiarkowanym optymizmem. Jednym z obejść tego problemu może być przepisanie wszystkiego na któryś z języków kompilowanych, bo obecnie wszystko powstaje w Pythonie, z kolei C znam słabo i na dodatek go nie lubię… Trzeba będzie rozpoznać możliwość pisania, albo raczej kompilowania C# pod Raspberry Pi.

Jeśli ktoś kiedy trafi na tą notkę i będzie miał jakieś pytania niech się nie krępuje i zostawi komenta - ponieważ do tej pory żadnego jeszcze nie otrzymałem, to nie wiem czy dostanę jakieś powiadomienie, na odpowiedź można więc trochę poczekać.

P.S. Wiem, że długie, ale i tak mało kto (nikt?) to czyta.
P.S.2. Google Docs ma dość wybiórczy słownik - czasem brakuje mu nawet dość pospolitych słów.

Wybrałem się do prywatnego szpitala, żebyście wy nie musieli

Odzywam się po przerwie. W sumie co się dzieje w kraju i za granicą to chyba wszyscy wiedzą, nie ma więc sensu tutaj kolejny raz tego przypo...