Pierwsze kroki

W tym przewodniku przedstawiono sposób konfiguracji PLNXT oraz przykłady użycia od najprostszych do bardziej skomplikowanych.

Zakłada się, że użytkownik posiada podstawową znajomość języka Prolog i poprawnie skonfigurował system operacyjny do współpracy z modułem komunikacyjnym, którego ma zamiar używać. Jeśli nie, proszę wrócić do opisu konfiguracji.

Użyteczność modułów komunikacyjnych

Obecnie dostępne są 4 moduły, ale moduł sockets ma poważne uchybienia i nie nadaje się do użytkowania.

Moduł simulator korzysta z prologowej biblioteki graficznej xpce. Umożliwia uruchamianie programów napisanych przy użyciu biblioteki PLNXT bez zestawu Mindstorms NXT. Nie jest w rzeczywistości modułem komunikacyjnym, bo jednynie w prymitywny sposób symuluje działanie NXT (jednego lub więcej) przez wyświetlanie stanu silników i możliwość wybierania wartości odczytywanych przez sensory. Mimo swojej prostoty, dzięki możliwości spowolnienia w czasie „ruchu” wirtualnego robota, simulator pozwala śledzić działanie nawet bardziej złożonych programów.

Dwa moduły komunikacyjne serial i iCommand dostarczają dwóch alternatywnych sposobów kontrolowania prawdziwego robota. ICommand ma jednak poważne minusy względem serial. Wymaga wirutalnej maszyny javy i zainstalowanej biblioteki javowej o identycznej nazwie iCommand. Biblioteka ta stanowi warstwę pośredniczącą między obsługą portu i PLNXT. Jej uchybieniem jest brak pewnych funkcji:

  • synchroniczne poruszanie silnikami,
  • kontrolowanie wielu robotów jednocześnie.

Zaleca się używanie modułu serial.

Konfiguracja PLNXT

W pliku konfiguracyjnym plnxt.pl dostępny jest szereg ustawień.

Należy wybrać moduł komunikacyjny przez odkomentowanie jednej z linii w poniższym bloku:

%%% Choose communication module.
%%% One and only of lines below should be uncommented.
:- use_module(lib/nxt_actions_serial).
%:- use_module(lib/nxt_actions_icommand).
%:- use_module(lib/nxt_actions_simulator).
%:- use_module(lib/nxt_actions_sockets).

Następnie definiuje się roboty, których będzie się używać. Każdemu z nich nadaje się alias. Pierwszy z robotów jest robotem domyślnym, którego można kotrolować z konsoli w głównym wątku SWI-Prolog.

Definicja każdego robota wygląda następująco:

% nxt_goal_definition(Alias,Device,Conn_type,Sync_mode,Readings,WheelCircumference,AxleLenght,LeftMotor,RightMotor,PincerMotor,
%	      Reverse,TouchPort,SoundPort,LightPort,UltrasonicPort).
nxt_goal_definition(my_robot,'/dev/rfcomm0',bt,off,on_demand,17.5,11,'C','B','A',false,'S1','S2','S3','S4').

Znaczenie poszczególnych ustawień:

  • Alias - alias robota (słowo current nie jest dozwolone).
  • Device - port szeregowy używany do komunikacji.
  • Conn_type - rodzaj połączenia, usb albo bt (bluetooth).
  • Sync_mode - synchronizacja silników (on albo off). Może być włączona (on) jedynie, gdy używany jest moduł serial.
  • Readings - sposób odczytywania pomiarów sensorów (cyclic albo on_demand). Wyjaśnienie tutaj.
  • WheelCircumference - obwód koła w cm. Standardowa opona w zestawie NXT ma obwód 17,5 cm.
  • AxleLength - odległość pomiędzy prawym i lewym kołem w cm.
  • LeftMotor - port, do którego podłączony jest lewy silnik ('A', 'B' albo 'C').
  • RightMotor - port, do którego podłączony jest prawy silnik ('A', 'B' albo 'C').
  • PincerMotor - port, do którego podłączony jest dodatkowy silnik ('A', 'B' albo 'C').
  • Reverse - true albo false. NIE ZAIMPLEMENTOWANE!
  • TouchPort - port, do którego podłączony jest sensor dotyku ('S1', 'S2', 'S3' albo 'S4').
  • SoundPort - port, do którego podłączony jest sensor dźwięku ('S1', 'S2', 'S3' albo 'S4').
  • LightPort - port, do którego podłączony jest sensor światła ('S1', 'S2', 'S3' albo 'S4').
  • UltrasonicPort - port, do którego podłączony jest sensor odległości ('S1', 'S2', 'S3' albo 'S4').

Odczyt sensorów

Są dwa podejścia przy wyzwalaniu zdażeń odczytami sensorów. W PLNXT zastosowano tzw. triggery, które cyklicznie sprawdzają prawdziwość pewnego warunku niezbędnego do wyzwolenia akcji. Jeśli użytkownik chce aby robot zatrzymał się przed ścianą, stworzy taki trigger, aby np. po zbliżeniu się do przeszkody na 15 cm, robot stanął. Trigger ten będzie mierzył odległość aż warunek będzie prawdziwy. W przypadku, gdy użytkownik zechce, aby np. po zbliżeniu się do ściany na 60 cm, robot otworzył i zamknął szczypce, a po zbliżeniu na 30 cm odegrał melodyjkę, każda z tych akcji będzie wymagać osobnego triggera. Każdy z nich będzie oddzielnie dokonywał pomiaru odległości, a tym samym sensor ultradźwiękowy będzie potrójnie obciążony. W takich przypadkach należy się zastanowić, czy nie opłaca się uruchomić wątku, który cyklicznie odczytuje pomiary i zapisuje je, a wywołanie odczytu w programie np. predykatem nxt_ultrasonic nie powoduje dokonywania pomiaru, a jedynie jego odczyt spośród zapisanych wartości. Ustawianie w definicji robota opcji Readings na cyclic spowoduje uruchomienie takiego mechanizmu. Standardowy sposób dokonywania pomiarów tylko wtedy, gdy są wymagane, odbywa się, gdy Readings ustawione jest na on_demand.

Inne moduły niż //serial//

Jedynie w przypadku używania modułu komunikacyjnego serial znaczenie mają opcje Device i Conn_type. Dla innych modułów mogą być ustawione dowolnie.

Należy bezwględnie zwrócić uwagę, aby nie ustawiać opcji Sync_mode na on, gdy używa się innego modułu niż serial. W przeciwnym wypadku PLNXT nie będzie działać poprawnie.

Moduł //simulator//

Dla ułatwienia obserwacji zdarzeń przy użyciu modułu simulator wprowadzono czynnik czasu:

nxt_simulator_time_factor(0.25).

Jego ustawienie na wartość 1 powoduje wykonywanie akcji w czasie rzeczywistym. Wartości większe od 1 przyspieszają upływ czasu. Wartości mniejsze od 1 - spowalniają. Np. wartość 0,25 powoduje 4-krotne spowolnienie.

Sterowanie z konsoli

Test połączenia

Otwarcie połączenia

$ pl
?- [plnxt].
?- nxt_open.

Proszę zwrócić uwagę na sygnalizację zawarcia połączenia na wyświetlaczu jednostki centralnej.

Wydanie polecenia

Sprawdzenie poziomu baterii (najprostsze polecenie, które korzysta i z zapisu i z odczytu z urządzenia):

?- nxt_voltage_millivolt(Voltage).

Powinien pojawić się poziom baterii, jeżeli nie, mamy problem!

Zamknięcie

?- nxt_close.

Proszę zwrócić uwagę na sygnalizację braku połączenia na wyświetlaczu jednostki centralnej.

Praca w powłoce SWIPL

Proszę uruchomić powłokę SWIPL i załadować plik plnxt.pl ([plnxt].), a następnie:

  • Otworzyć połączenie: nxt_open.
  • Wykonać serię wybranych z dokumentacji poleceń (przemieszczanie, odczyt sensorów). Np.:
    • nxt_go(300). % Jazda do przodu z prędkością 300 stopni/sekundę.
    • nxt_stop.
    • nxt_go_cm(400,80). % Jazda do przodu z prędkościa 400 stopni/sekundę. Zatrzymanie po 80 cm.
    • nxt_touch(Value). % Odczyt sensora dotyku.
    • nxt_sound(Value). % Odczyt sensora dźwięku.
    • nxt_light(Value). % Odczyt sensora światła.
    • nxt_light_LED(activate). % Włączenie diody sensora światła.
    • nxt_light(Value).
    • nxt_light_LED(passivate).
    • nxt_ultrasonic(Value).
    • nxt_rotate(350,360). % Obrót w prawo o 360 stopni z prędkością 350 stopni/sekundę.
    • nxt_play_tone(500,2000). % Wydanie dźwięku o częstotliwości 500 Hz i czasie trwania 2000 ms.
  • Zamknąć połączenie: nxt_close.

Tworzenie programów krok po kroku

Poniższe przykłady prezentują wszystkie mechanizmy dostępne w PLNXT. Zapoznają ze stosowaniem tego interfejsu od najprostszych przykładów do bardziej skomplikowanych. Wszystkie przykłady znajdują się w pliku demos/examples.pl.

Otwieranie połączenia

W definicjach robotów w pliku plnxt.pl po to nadawane są aliasy, aby można było w łatwy sposób zadać cel konkretnemu robotowi.

Najprostszy program otwierający połączenie, odczekujący 10 sekund i zamykający je może wyglądać następująco:

start1 :-
	nxt_goal(my_robot,nxt_open),
	sleep(10),
	nxt_goal(my_robot,nxt_close).

Jeśli my_robot jest zdefiniowany jako pierwszy w pliku plnxt.pl, jest domyślnym robotem i powyższy program może wyglądać dla niego prościej:

start2 :-
	nxt_open,
	sleep(10),
	nxt_close.

Obie wersje różnią się nieznacznie wykonaniem poleceń. Predykat nxt_goal tworzy nowy wątek dla jednego ze zdefiniowanych robotów realizujący zadany cel. Sama procedura otwierania połączenia trwa zauważalnie długo (2-3 sekundy). Stąd działanie drugiego programu, które odbywa się w jednym wątku, trwa trochę dłużej, gdyż odczekanie 10 sekund nie jest zrównoleglone z otwieraniem połączenia.

Poruszanie

Dodajmy do powyższego programu ruch do przodu i do tyłu:

start3 :-
	nxt_open,
	nxt_go_cm(300,40),
	nxt_go_cm(-300,40),
	sleep(3),
	nxt_close.

Robot przemieszcza się 40 cm do przodu z prędkością obrotową kół 300 stopni na sekundę. Po zatrzymaniu wykonuje ruch do tyłu z tą samą prędkością.

Opcja force

W powyższym przykładzie działa mechanizm sekwencjonowania poleceń. Robot czeka z wykonaniem drugiego do zatrzymania. Większość predykatów najwyższej warstwy PLNXT (movement) może przyjmować opcjonalnie dodatkowy argument force. Wymusza on wykonanie polecenia natychmiast, bez względu na to czy robot stoi, czy się porusza.

W przykładzie robot rozpocznie ruch do przodu bez ograniczenia, ale natychmiast polecenie się zdeaktualizuje, gdyż rozpocznie się wykonywanie następnego: wymuszony ruch do tyłu.

start4 :-
	nxt_open,
	nxt_go(300),
	nxt_go_cm(-300,40,force),
	sleep(3),
	nxt_close.

Odczyt sensorów

start5 :-
	nxt_open,
	segment5,
	segment5,
	segment5,
	segment5,
	segment5,
	nxt_close.
 
segment5 :-
	nxt_light(Light),
	nxt_ultrasonic(Distance),
	nl,
	write('Light: '), write(Light),nl,
	write('Distance: '), write(Distance),nl,
	nxt_go_cm(300,20).

Robot porusza się do przodu i co każde 10 cm zatrzymuje się oraz dokonuje pomiaru odległości i natężenia światła.

Timer

Timer uruchamia akcję po upływie zadanego czasu. W przykładzie robot poruszą się 80 cm do przodu, 80 cm do tyłu, a następnie zamyka połączenie. W międzyczasie po upływie dwóch sekund od startu otwiera i zamyka szczypce.

start6 :-
	nxt_open,
	nxt_go_cm(300,80),
	timer_create(_,2,clap6),
	nxt_go_cm(-300,80),
	nxt_close.
 
clap6 :-
	nxt_pincer(open,force),
	sleep(1),
	nxt_pincer(close,force).

Trigger

Trigger sprawdza cyklicznie prawdziwość pewnego warunku, który wzwala akcję. Akcja jest wyzwalana tylko raz. Po zadziałaniu triggera jego wątek zostaje zakończony.

W przykładzie robot przemieszcza się do przodu, a utworzony trigger sprawdza, czy nie zbliża się przeszkoda (warunek check_distance). Gdy robot podjedzie na odlełość mniejszą niż 20 cm, zostanie uruchomiona akcja obstacle - robot zatrzyma się i zamknie połączenie.

start7 :-
	nxt_open,
	nxt_go(300),
	trigger_create(_,check_distance7,obstacle7).
 
check_distance7 :-
	nxt_ultrasonic(Value,force),
	Value < 20.
 
obstacle7 :-
	nxt_stop,
	nxt_close.

W następnym przykładzie robot pełni rolę magazyniera. Dojeżdza do piłki umieszczonej przed nim, łapie ją szczypcami, odwraca się i zostawia pod ścianą.

start8 :-
	nxt_open,
	nxt_pincer(open),
	nxt_go(400),
	trigger_create(_,check_ball8,ball8).
 
check_ball8 :-
	nxt_touch(Value,force),
	Value = 1.
 
ball8 :-
	nxt_stop,
	nxt_pincer(close),
	nxt_rotate(400,180),
	trigger_create(_,check_distance8,wall8),
	nxt_go(400).
 
check_distance8 :-
	nxt_ultrasonic(Value,force),
	Value < 20.
 
wall8 :-
	nxt_stop,
	nxt_pincer(open),
	nxt_go_cm(-400,20),
	nxt_pincer(close),
	sleep(1),
	nxt_stop,
	nxt_close.

W kolejnym przykładzie robot porusza się przypadkowo po pomieszczeniu unikając przeszkód. Program przerywa działanie po zadaniu celu: stop9/0.

start9 :-
	nxt_open,
	nxt_go(300),
	trigger_create(_,check_distance9,obstacle9).
 
check_distance9 :-
	nxt_ultrasonic(Value,force),
	Value < 25.
 
obstacle9 :-
	nxt_stop,
	nxt_go_cm(-300,25),
	Angle is 110 + random(140),
	nxt_rotate(200,Angle),
	nxt_go(300),
	trigger_create(_,check_distance9,obstacle9).
 
stop9 :-
	nxt_stop,
	nxt_close.

Noreturn trigger

W powyższym przykładzie trigger zatrzymuje robota i wykonywane są kolejne instrukcje. W wątku, w którym utworzony został trigger, nie ma kolejnych instrukcji, więc wątek sam zostaje zakończony. Gdyby były jeszcze jakieś predykaty, które mogłyby kolidować z akcją podejmowaną przez wyzwolony trigger, efekt byłby nieporządany. W takich sytuacjach przydaje się noreturn trigger. Zabija on wątek, który go utworzył.

W przykładzie robot porusza się po kwadracie w lewo. Po klaśnięciu zaczyna się wycofywać „klaskając”.

Uwaga! W zależności od hałasu w miejscu, w którym działa robot, należy odpowiednio dobrać próg natężenia dźwięku rozpoznawanego jako klaśnięcie.

start10 :-
	nxt_open,
	trigger_create_noreturn(_,clap10,go_backward_clapping10),
	segment10.
 
segment10 :-
	nxt_rotate(300,-90),
	nxt_go_cm(300,20),
	segment10.
 
clap10 :-
	nxt_sound(Value,force),
	Value > 55.
 
go_backward_clapping10 :-
	nxt_stop,
	nxt_go_cm(-300,40),
	nxt_pincer(open,force),
	sleep(1),
	nxt_pincer(close,force),
	sleep(1),
	nxt_pincer(open,force),
	sleep(1),
	nxt_pincer(close,force),
	wait_till(nxt_is_stopped),
	nxt_close.

Trigger wielokrotny

Istnieje możliwość stworzenie triggera, który może wyzwalać akcję więcej niż raz. Należy podać jako czwarty argument liczbę razy. Można też wpisać inf (z ang. infinite), co skutkuje uruchomieniem triggera pracującego w nieskończoność (do czasu jego zabicia lub zamknięcia połączenia z robotem).

W przykładzie robot porusza się 1 metr do przodu. Na klaśnięcie otwiera i zamyka szczypce. Zamknięcie połączenia następuje po wciśnięciu sensora dotyku.

start11 :-
	nxt_open,
	trigger_create(_,nxt_touch(1,force),[nxt_stop,nxt_close]),
	trigger_create(_,clap11,pincer11,inf),
	nxt_go_cm(250,100).
 
clap11 :-
	nxt_sound(Value,force),
	Value > 55.
 
pincer11 :-
	nxt_pincer(open,force),
	sleep(1),
	nxt_pincer(close,force).

nxt_goal

Poznany w pierwszym przykładzie predykat nxt_goal bywa bardzo przydatny. Opcjonalnie trzecim jego argumentem może być opis wątku. W przykładzie robot porusza się po kwadracie wykonując 3 okrążenia. Dzięki zastosowaniu nxt_goal realizuje ten ruch w osobnym wątku, a wątek główny nie jest zajęty. Można z konsoli SWI-Prolog wydawać dodatkowe polecenia, np. odczytywać wartości sensorów. Proszę wypróbować predykat nxt_threads, który wyświetla listę wątków robota.

start12 :-
	nxt_open,
	nxt_goal(current,segment12(12),square_move).
 
segment12(0) :- nxt_close.
 
segment12(M) :-
	N is M-1,
	nxt_rotate(300,-90),
	nxt_go_cm(300,20),
	segment12(N).

Przykładowe uruchomienie:

2 ?- start12.
true.
 
3 ?- nxt_threads.
------------------------------------------------------------
Thread ID	Robot		Description
------------------------------------------------------------
main		my_robot		console_control
2		my_robot		square_move
3		my_robot		trigger(nxt_is_stopped, [current_thread(2, _G304), thread_send_message(2, resume)])
------------------------------------------------------------
true.
 
4 ?- nxt_light(Value,force).
Value = 25.
 
5 ?- nxt_threads.
------------------------------------------------------------
Thread ID	Robot		Description
------------------------------------------------------------
main		my_robot		console_control
------------------------------------------------------------
true.

Triggery na liście wątków są opisywane następująco: trigger(zdarzenie_wyzwalające,akcja). W tym przykładzie widać trigger wyzwalany w momencie zatrzymania robota. To mechanizm sekwencjonowania poleceń i będzie pojawiać się często w listach wątków.

Obsługa wielu robotów

Głównym przeznaczeniem predykatu nxt_goal jest zadawanie celu wielu robotom jednocześnie.

Do uruchomienia przykładu potrzebne są dwa roboty. Oba zdefiniowane w pliku plnxt.pl! Jeden z nich ma alias my_robot, a drugi joey. My_robot i joey jednocześnie rozpoczynają jazdę do przodu. W momencie napotkania ściany przez my_robot następuje jego zatrzymanie, ale zatrzymuje się również joey.

start13 :-
	nxt_goal(my_robot,go_to_a_wall13),
	nxt_goal(joey,go_joey13).
 
go_to_a_wall13 :-
	nxt_open,
	trigger_create(_,check_distance13,wall13),
	nxt_go(300).
 
go_joey13 :-
	nxt_open,
	nxt_go(300).
 
stop_joey13 :-
	nxt_stop,
	nxt_close.
 
check_distance13 :-
	nxt_ultrasonic(Value,force),
	Value < 20.
 
wall13 :-
	nxt_stop,
	nxt_goal(joey,stop_joey13),
	nxt_close.
pl/plnxt/pierwsze_kroki.txt · ostatnio zmienione: 2019/06/27 15:50 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0