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íť.