4.2.2021
V minulém díle jsem popsal základní hardware pásovce. Dnes je na řadě ruční ovládání základního pohybu. Ruční ovládání není dozajista to, co nás ve výsledku zajímá, ale je extrémně důležité pro sběr dat. Abychom mohli úspěšně natrénovat neuronovou síť, potřebujeme spoustu dat. V případě Pásovce budeme potřebovat spoustu obrázků z obou kamer, abychom na nich neuronové síti ukázali, jak vypadá volná cesta a jak vypadá překážka.
Omlouvám se předem, že moje popisy budou silně CLI orientované a velmi nedotažené. Navíc komplet vše dělám na Linuxu — ve Windows mohou postupy vypadat úplně jinak. Na Windows se mě neptejte, Windows mám pouze na notebooku a používám v nich jedinou pracovní aplikaci: linuxový terminál z balíku WSL.
Nvidia Jetson Nano spouští systém z µSD karty. Celá instalace systému tak spočívá v tom, že z internetu stáhnete obraz karty, překopírujete jej na µSD kartu, nastavíte síť a je to. Podrobnosti nebudu popisovat, odkážu vás přímo na hotový návod:
K návodu bych měl jen několik poznámek:
Krok 3: V tuto chvíli bych nastavil i ssh server, aby se dalo dostat na povelovou řádku vzdáleně.
Krok 4: Na displeji se vypisuje pouze IPv4 adresa. Pro většinu z vás to nic neznamená, ale já bych tam raději viděl linkovou IPv6 adresu. Poslední bod kroku 4 vás pošle na Jupyter notebook. To je dobrý odrazový můstek. Najdete tam spoustu příkladů, bobužel sem mi ty nejzajímavější z nich nikdy nepodařilo rozchodit.
Krok 5: Zkuste to alespoň ze začátku raději bez upgrade systému. Můžete samozřejmě zkusit nainstalovat novou verzi balíku jetbot, ale během experimentů jsem získal nepříjemný pocit, že upgrade celého systému zbytečně rozbil tu část, která mě především zajímá — neuronové sítě. Velkou spustu balíků jsem pak musel instalovat přes pip. A právě vytvoření funkčního prostředí nástrojem pip mi trvalo neuvěřitelnou dobu — strávil jsem s tím určitě měsíc.
Jakmile je systém nainstalovaný a nastavený, ověřte, že můžete ovládat svého robota:
root@pasovec:~/pasovec# python3 Python 3.6.9 (default, Oct 8 2020, 12:12:24) [GCC 8.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from jetbot import Robot >>>
Import na povelové řádce pythonu 3 nesmí skončit chybou. Tohle je špatně:
Python 2.7.17 (default, Sep 30 2020, 13:38:04) [GCC 7.5.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from jetbot import Robot Traceback (most recent call last): File "", line 1, in ImportError: No module named jetbot >>>
Pro ovládání pohybu Pásovce jsem zkoušel kde co. Jako první jsem vyzkoušel Jupyter notebook přibalený s balíkem jetbot. Ovládání klikáním myší na tlačítka ve webové aplikaci je opravdu chuťovka. Na tyto první krůčky připadá nejvyšší počet pokusů o proražení zdi Pásovcem.
Od dětí jsem si půjčil gamepad. Ten je pro ovládání celkem příjemný, ale gamepad velmi často posílal i v neutrální poloze nějaké malé hodnoty výchylek. Pásovec v důsledku toho místo úplného zastavení pomaličku popojížděl některým směrem, případně stál, ale tiše si bzučel v rytmu PWM řízení motorů. Navíc bezdrátový gamepad funguje jen na poměrně krátkou vzdálenost.
Nejlépe se mi pak osvědčila zavedená kombinace kláves WASD. Přidal jsem si ještě klávesu X pro úplné zastavení. Nezjistil jsem totiž (ani jsem moc nepátral), jak získat v Pythonu událost "KeyPressEvent" a "KeyReleaseEvent" odděleně, navíc v ssh relaci.
Při ovládání z klávesnice velmi oceníte vzdálený přístup přes ssh. Klávesnice připojená přímo k Pásovci se dá taky použít, ale chodit za Pásovcem v podřepu a snažit se řídit směr je nepohodlné cvičení.
Nyní už hotový kód:
import sys, termios, tty, os
from jetbot import Robot
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
maxspeed = 0.4
fwspeed = 0;
robot = Robot()
while True:
char = getch()
if (char == "w"):
fwspeed = maxspeed
robot.forward(fwspeed)
continue
if (char == "a"):
if (fwspeed > 0):
robot.set_motors(-fwspeed, fwspeed);
else:
robot.left(maxspeed)
continue
if (char == "s"):
fwspeed = -maxspeed
robot.forward(fwspeed)
continue
if (char == "d"):
if (fwspeed > 0):
robot.set_motors(fwspeed, -fwspeed);
else:
robot.right(maxspeed)
continue
if (char == "x"):
fwspeed = 0
robot.stop()
continue
if (char == "p"): # ukončení programu
break
A takhle se s tím pak jezdí:
Při experimentech se ukázalo, že někdy může být dobrý nápad zpracovat některé části (tj. video) na jiném počítači. Zajímavé to pro mě bylo především v situaci, kdy jsem ještě nedokázal zpracovat video v Nvidia Jetson Nano, ale bez problémů jsem to dokázal ve svém stolním počítači. Tehdy jsem posílal video přes gstreamer do PC a z PC se posílala jednoduchá informace překážka/volno MQTT protokolem.
MQTT je jednoduchý odlehčený protokol pro posílání zpráv mezi IOT zařízeními. Odkazy:
Abychom dokázali komunikovat protokolem MQTT, potřebujeme dvě základní komponenty: MQTT server a MQTT klienta pro Python.
Server je přímo součástí distribuce, nainstalujeme jej prostým apt-get install mosquitto.
Klienta pro Python jsem nainstaloval pomocí pip: pip3 install paho-mqtt.
Server není nutné nijak nastavovat. Po startu server automaticky poslouchá a distribuuje zprávy klientům. Samozřejmě může být vhodné nějak omezit přístupy, ale na malé lokální síti mě to příliš netrápí. Zkontrolovat server můžete programem ss:
root@pasovec:~# ss -ntpl | grep mosquitto
LISTEN 0 100 0.0.0.0:1883 0.0.0.0:* users:(("mosquitto",pid=4254,fd=4))
LISTEN 0 100 [::]:1883 [::]:* users:(("mosquitto",pid=4254,fd=5))
Program se potom rozdělí na dvě části. Jedna část obsluhuje klávesnici (může běžet i na jiném počítači), druhá část obsluhuje motory. Navzájem se domlouvají protokolem MQTT. Následující kód už je verze, kterou používám i pro komunikaci s neuronovou sítí, obsahuje proto i režim "automat". Informace z neuronové sítě ale zatím pochopitelně chybí.
import sys, termios, tty, os
import paho.mqtt.client as mqtt
class Keyboard():
def getch(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def run(self):
while True:
char = self.getch()
if char == "h": self.client.publish(self.topic, "speed up") # zrychli
if char == "n": self.client.publish(self.topic, "speed down") # zpomal
if char == "w": self.client.publish(self.topic, "forward") # dopředu
if char == "a": self.client.publish(self.topic, "left") # doleva
if char == "s": self.client.publish(self.topic, "backward") # zpátky
if char == "d": self.client.publish(self.topic, "right") # doprava
if char == "x": self.client.publish(self.topic, "stop") # zastav
if char == "l": self.client.publish(self.topic, "light") # rozsviť světlo
if char == "q": self.client.publish(self.topic, "auto") # přepni na automat
if char == "k": self.client.publish(self.topic, "XX") # překážka před námi
if char == "j": self.client.publish(self.topic, "XL") # překážka, objeď ji vlevo
if char == "l": self.client.publish(self.topic, "XR") # překážka, objeď ji vpravo
if char == "i": self.client.publish(self.topic, "OK") # volno
if char == "p": break
def __init__(self):
self.topic = "pasovec/kbd"
self.client = mqtt.Client("kbd")
self.client.connect("localhost")
self.client.loop_start()
self.run()
if __name__ == "__main__":
x = Keyboard()
from jetbot import Robot
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import random
import inspect
robot = Robot()
class Motor:
def startAutomat(self):
print("publish pasovec/motor/automat True");
publish.single("pasovec/motor/automat", "True", hostname="localhost")
self.automat = True;
def stopAutomat(self):
print("publish pasovec/motor/automat False");
publish.single("pasovec/motor/automat", "False", hostname="localhost")
self.automat = False;
def on_message(self, client, userdata, message):
payload = str(message.payload.decode('utf8'))
topic = message.topic
print(topic, " ", payload)
if topic == "pasovec/camera":
self.status = payload
if topic == "pasovec/kbd":
if payload == "forward":
self.stopAutomat()
self.fwspeed = abs(self.fwspeed)
self.robot.forward(self.fwspeed)
return
if payload == "left":
self.stopAutomat()
self.robot.set_motors(-abs(self.fwspeed), abs(self.fwspeed));
return
if payload == "right":
self.stopAutomat()
self.robot.set_motors(abs(self.fwspeed), -abs(self.fwspeed));
return
if payload == "backward":
self.stopAutomat()
self.fwspeed = -abs(self.fwspeed)
self.robot.forward(self.fwspeed)
return
if payload == "stop":
self.stopAutomat()
# self.fwspeed = 0.2
self.robot.stop()
return
if payload == "speed up":
self.fwspeed = self.fwspeed*1.1 if self.fwspeed < self.maxspeed/1.1 else self.maxspeed
self.adjustSpeed()
return
if payload == "speed down":
self.fwspeed = self.fwspeed/1.1 if self.fwspeed > 0.1 else 0.1
self.adjustSpeed()
return
if payload == "auto":
self.startAutomat()
if self.automat:
print ("automat", self.status, self.fwspeed)
if self.status == "XX":
direction = 1 if random.random() > 0.5 else -1
self.robot.set_motors(direction*self.fwspeed, -direction*self.fwspeed)
return
if self.status == "XL":
self.robot.set_motors(-self.fwspeed, self.fwspeed)
return
if self.status == "XR":
self.robot.set_motors(self.fwspeed, -self.fwspeed)
return
if self.status == "OK":
self.robot.set_motors(self.fwspeed, self.fwspeed)
return
def adjustSpeed(self):
left = self.robot.left_motor.value
right = self.robot.right_motor.value
print ("adjustSpeed ", left, right, self.fwspeed)
left = 0 if left == 0 else self.fwspeed if left > 0 else -self.fwspeed
right = 0 if right == 0 else self.fwspeed if right > 0 else -self.fwspeed
print ("adjustSpeed ", left, right)
self.robot.left_motor.value = left
self.robot.right_motor.value = right
def run(self):
self.client.loop_forever()
def __init__(self):
self.OK = False
self.status = "OK"
self.automat = True
self.stopAutomat()
self.maxspeed = 0.4
self.fwspeed = 0.3;
self.client = mqtt.Client("motor")
self.client.connect("localhost")
self.client.subscribe("pasovec/+")
self.client.on_message = self.on_message
self.robot = Robot()
self.run()
if __name__ == "__main__":
x = Motor()
V jednom terminálu si spusťe skript motor.py, v druhém terminálu skript keyboard.py a můžete jezdit!
Pásovec by už nyní měl poslouchat naše povely. Zároveň máme vytvořený základ pro další pokračování. Příště si ukážeme, jak nasbírat data z kamer. K tomu si vytvoříme další samostatný skript, který přes gstreamer získá snímek z obou kamer, poskládá snímky do jednoho obrázku a odešle jej na PC, ve kterém budeme později trénovat neuronovou síť.