WindCar

Da raspibo.
Versione del 4 ott 2017 alle 11:08 di Oloturia (discussione | contributi)
(diff) ← Versione meno recente | Versione attuale (diff) | Versione più recente → (diff)
Jump to navigation Jump to search

La WindCar è un test per l'utilizzo di Arietta G25 di Acme Systems per ripristinare un'automobilina radiocomandata il cui controllo radio era andato perso o distrutto.

I componenti usati sono:

  • Arietta G25
  • Servo 9g
  • Ponte H
  • Powerbank
  • Webcam (di recupero da un vecchio EeePC)

Incontriamo l'Arietta G25

Arietta exposed signals.jpg

Arietta G25 come board è molto simile al Raspberry Pi. Le sue specifiche tecniche sono: CPU ARM 9 da 400 Mhz, 256 Mb RAM, lettore di micro SD, USB per alimentazione/comunicazione seriale. I livelli logici della GPIO sono a 3,3 V. Il sistema operativo consigliato è Debian Linux.

Collegarsi

Collegamento USB

Se non disponiamo di un collegamento WiFi o del modulo per collegarci, possiamo usare l'USB. Quando Arietta G25 viene collegata a un PC questa emula un'interfaccia LAN tramite USB (su Linux vedremo ad es. una porta chiamata usb0). Seguendo queste istruzioni lo farà in automatico, ma credo possa bastare assegnare 192.168.10.20 come IP a usb0 per poter avere un collegamento funzionante. Una volta stabilito il collegamento possiamo fare ssh.

Collegamento WiFI

Se la nostra Arietta G25 ha il modulo WiFi allora possiamo collegarci configurando il file wpa_supplicant.conf in modo che si colleghi alla nostra rete. Questo file si trova dentro alla partizione boot. Nel caso abbiate problemi a collegarvi, potete usare wpa_cli.

Se stiamo usando bonjour vedremo sulla nostra rete 'arietta.local' (o 'nomemacchina.local' in genere, se abbiamo cambiato nome all'Arietta G25).

Una nota per promemoria: normalmente nell'immagine distribuita da acmesystems il comando iwconfig restituisce il messaggio

wlan0      no wireless extensions.

Se vi piace usare il comando iwconfig o ne avete necessita' perche' vi servono le wireless extension basta ricompilare il kernel seguendo le indicazioni presenti sulla pagina dedicata.

In corrispondenza del comando:

make ARCH=arm menuconfig

abilitare nel menu'

Networking Support -> Wireless -> cfg80211

la voce

[*] cfg80211 wireless extensions compatibility

questo permettera' di usare anche comandi che fanno riferimento alle wireless extensions.

ssh or not ssh?

Sia tramite USB che WiFi, ad Arietta G25 è possibile collegarsi tramite ssh. Il nome utente è 'root' (per loggarvi come root) o 'acme' (per loggarvi come user) e la password 'acmesystems'. Se la tenete sempre accesa è caldamente consigliato cambiare queste impostazioni. Nel caso non vogliate usare ssh, Arietta G25 ha anche un editor via web utilizzabile via browser.

Configurare il Pinout

La particolarità dell'Arietta G25 è che i pin della GPIO sono riprogrammabili in diverse configurazioni. Tramite questo sito è possibile configurare la GPIO. Normalmente i pin sono entrate/uscite digitali, ma alcuni di loro possono essere usati come:

  • porta seriale (fino a 3, con livelli logici a 3,3 V)
  • I²C (fino a 2)
  • SPI (fino a 4 device)
  • Convertitori Analogici Digitali (fino a 4)
  • PWM (fino a 4)
  • I2S per l'audio
  • bus 1 wire

La GPIO ha anche 3 USB, fate attenzione però che la USB A non è disponibile se si sta usando la comunicazione seriale via micro USB e la USB B non lo è se si sta usando il modulo WiFi.

La procedura per l'installazione del sistema operativo è uguale a quella del Raspberry Pi: si scarica l'immagine e si flasha su una SD con dd. Ci saranno due partizioni: una conterrà /boot/ e l'altra tutto il resto del sistema operativo.

Nel sito di pinout.acmesystem.it nella tab Setup è possibile scegliere cosa attivare o disattivare. Nella tab Code examples ci sono alcuni esempi di come usare il pin in questione oppure una breve descrizione dello stesso. Purtroppo alcune pagine (relativi a I²C, 1Wire e convertitore A/D) sono mancanti e occorre cercare un po' nel sito ufficiale per trovare della documentazione. Una volta

A noi servono due pin PWM, per la gestione del servo e della velocità del motore DC, la USB C e almeno tre pin digitali per il controllo del ponte H. Dopo aver configurato i pin andiamo su Generate the DTB e clicchiamo LAUNCH. A questo punto possiamo scaricare il file e metterlo dentro a /boot/ su Arietta G25 rinominandolo acme-arietta.dtb.

Leggere e scrivere un pin GPIO

Per accendere un pin occorre modificare alcuni file di device in /sys ad esempio questo codice accende e spegne il pin PC31 (il 32 fisico)

# echo 95 > /sys/class/gpio/export
# echo out > /sys/class/gpio/pioC31/direction
# echo 1 > /sys/class/gpio/pioC31/value
# echo 0 > /sys/class/gpio/pioC31/value

Purtroppo non è molto chiaro, senza esempi, capire quale sia il numero da scrivere in /sys/class/gpio/export (è il Kernel ID, visualizzabile cliccando l'apposito checkbox), quindi è meglio sempre guardare nel sito il codice esempio. Il file value abbastanza intuitivamente, stabilisce se il pin dev'essere acceso o spento.

Per leggere il valore di un pin digitale, il codice è:

# echo in > /sys/class/gpio/pioC31/direction
# cat /sys/class/gpio/pioC31/value

Potete trovare più informazioni qui.

Scrivere un PWM

Per manovrare il servo e il ponte H ci serviranno due pin PWM. Il codice di esempio è questo:

# echo 0 > /sys/class/pwm/pwmchip0/export
# echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
# echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
# echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

Nella prima linea, il numero scritto nel file export è il numero della linea PWM (da 0 a 3). In period e duty_cycle sono scritti i tempi del periodo e di quanto questo periodo deve rimanere acceso espressi in nanosecondi. In questo caso su un periodo di 10 ms il pin sta acceso 5 ms e spento altri 5 ms. Enable accende il PWM quando è 1 e lo spegne quando è 0.

La pagina con la guida su come usare i PWM è stata rimossa e al momento l'unico documento trovabile è nella cache di Google.

Un server web che legge un joystick

L'obiettivo finale per la WindCar è guidarla con un joypad o un volante e pedaliera (o qualsiasi altro dispositivo di input!). Il server web è gestito da Node.js, che manda un JSON a uno script in Python (che a sua volta fa partire il server). Tutto il codice lo trovate qui [1]

Node.js

La soluzione più agevole per ottenere tale risultato è Node.js, una piattaforma event-driven per Javascript. Event-driven significa che il sistema rimane in ascolto di determinati eventi e lancia una funzione (callback) quando uno di questi è triggerato, rimanendo in sleep quando non accade niente.

Vediamo un po' il codice. interface.js è il file che definisce cosa fa il server.

var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

Qui sono caricati alcuni framework. Express e http sono utili per la gestione del server web, socket.io per la gestione degli eventi. Per fare un esempio:

app.get('/joystick.html',function(req,res){
	res.sendFile(__dirname+'/joystick.html');
});

nel caso sia richiesta l'indirizzo http://indirizzo_arietta/joystick.html deve restituire il file ./joystick.html

Socket.io, di cui potete trovare molta documentazione online, è un sistema di comunicazione bidirezionale basato su eventi. In questo caso l'ho usato per leggere la posizione del joystick.

io.on('connection', function(socket){
	socket.on('joyMoved',function(msg){ //output in stdout
		console.log(msg);
	});
});

L'evento joyMoved lo dobbiamo mettere anche nel file joystick.html

	<script>
		var gamepads;
		var timestamp;
		var connected = false;
		var socket = io();
		function update_gamepads_list() {			//get gamepads list
			gamepads = navigator.getGamepads();
			console.log(gamepads);
		}
		function pollGamepad() {				//polls gamepads
			if (connected) {
				if (timestamp != gamepads[0].timestamp){//if timestamps are different, then something happened
					console.log("event occurred");
					timestamp = gamepads[0].timestamp;
					json_list = {};
					json_list.gamepad_x = gamepads[0].axes[0];
					json_list.gamepad_y = gamepads[0].axes[1];
					json_list.gamepad_z = gamepads[0].axes[2];
					json_list.button0 = gamepads[0].buttons[0].pressed;
					json_list.button1 = gamepads[0].buttons[1].pressed;
					json_list.button2 = gamepads[0].buttons[2].pressed;
					json_list.button3 = gamepads[0].buttons[3].pressed;
					json_list.device = "joy";
					json_list = JSON.stringify(json_list);
					socket.emit('joyMoved',json_list);
				}
			}
			window.requestAnimationFrame(pollGamepad);
		}
		if(!!navigator.getGamepads){
			console.log("Gamepad enabled");
		} else {
			document.getElementById("statusPad").innerHTML="Joystick/joypad not supported";
		}
		window.addEventListener("gamepadconnected", function(e){update_gamepads_list();connected=true;});
		window.addEventListener("gamepaddisconnected", function(e){update_gamepads_list();connected=false;});
		window.requestAnimationFrame(pollGamepad);
	</script>

Il comando socket.emit attiva l'evento 'joyMoved' e gli manda una lista in formato JSON. A sua volta il server lo manda in stdout allo stdin dello script in python.

Python

Lo script servo_arietta.py si occupa di far partire node.js e di manovrare i pin di arietta. Si occupa anche di chiudere i server una volta usciti. Per lanciare tutto occorre quindi fare:

python servo_arietta.py

Come argomenti prende i pin da usare, altrimenti ne setta alcuni di default (che però potrebbero non essere giusti!).

Per prima cosa importiamo un po' di librerie che ci potranno servire

#!/usr/bin/env python
import json
import signal
import subprocess
import sys
import time
from acmepins import GPIO
from acmepins import PWM

json serve per leggere il JSON che ci manda node.js tramite socket.io signal necessario per intercettare CTRL+C e fare in modo che venga chiuso il server e che vengano spenti i pin prima di uscire subprocess serve per lanciare comandi da bash (in particolare node.js) sys serve per leggere gli argomenti della riga di comando time gestisce il tempo acmepins è la libreria per gestire i pin di arietta (altrimenti dovremmo manovrarli scrivendo sui file di device con echo)

Per prima cosa gestiamo l'uscita con CTRL+C:

def exit_handler(signal, frame):
	print('\nExiting...')
	node_script.terminate()
	servo.stop()
	speed.stop()
	motor1.off()
	motor2.off()
sys.exit(0)

e nella prima riga di codice del main scriveremo:

if __name__ == '__main__':
	signal.signal(signal.SIGINT, exit_handler)

Quando si preme CTRL+C non vogliamo che python chiuda semplicemente, ma vogliamo che venga chiuso il server e spenti tutti i pin. SIGINT è il segnale di interruzione, ovvero quello che viene lanciato quando si preme la combinazione CTRL+C. Invece che la normale interruzione, viene eseguito un handler (la funzione exit_handler) così da fare qualche operazione prima di interrompere lo script. Per saperne di più sulla libreria signal, potete guardare la guida di python a rigurardo.

Successivamente, controlliamo se l'utente ha scelto dei pin oppure se ha lanciato lo script senza argomenti o con un numero diverso da quelli necessari.

	if len(sys.argv) == 4:
		pwm_servo = sys.argv[1]
		enabServo = sys.argv[2]
		pin1Motor = sys.argv[3]
		pin2motor = sys.argv[4]
		motor_spd = sys.argv[5]
	else:
		pwm_servo = "J4.34"
		enabServo = "J4.35"
		motor_spd = "J4.36"
		pin1Motor = "J4.37"
		pin2Motor = "J4.39"
		print "Pin not properly set <servo> <enable servo> <pin1motor> <pin2motor> <enable>\nDefaults servo:PWM0:34 enabServo:35 pin1motor:PC1:37 pin2motor:PC0:39 enable:PWM1:36\n"
print "listening on port 3000"

L'array sys.argv contiene tutti gli argomenti dello script lanciato da riga di comando. Il primo elemento dell'array è sempre il nome dello script. In questo pezzo di codice è possibile vedere anche i nomi dei pin usati. J4.34 è il pin fisico n.34 (per pin fisico si intende la numerazione, da 1 a 40, dei pin partendo dal primo a sinistra contandoli in orizzontale).

	node_script = subprocess.Popen(["nodejs","interface.js"],stdout=subprocess.PIPE) #launch nodejs

Questa riga lancia node.js. Notate che nella funzione exit_handler l'oggetto node_script esegue il metodo terminate chiudendo così il server quando viene premuto CTRL+C.

	try:	
		servo = PWM(pwm_servo,200)
		enable_servo = GPIO(enabServo,"OUTPUT")
		speed = PWM(motor_spd,200)
		motor1 = GPIO(pin1Motor,"OUTPUT")
		motor2 = GPIO(pin2Motor,"OUTPUT")
	except KeyError:		
		print "Invalid pin name\nFormat: J4.xx, PWMs in 34 36 38 40\n"
               sys.exit(1)

Subito dopo vengono istanziati gli oggetti relativi ai pin, in due formati: PWM per la gestione del pwm (numero pin e tempo di periodo duty cycle in... ms*10???) e GPIO per i pin digitali (in questo caso, tutti usati in output). Purtroppo non c'è molta documentazione a riguardo dell'utilizzo del PWM, cercheremo di indagare più approfonditamente. Potete però vedere il sorgente della libreria acmepins

 	motor1.on() #only forward, for the moment
	servo.start(0)
	speed.start(0)

Il pin motor1 e i pwm servo e speed sono fatti partire.

	while True:
		try:
			output = json.loads(node_script.stdout.readline())             #obtain a dict from JSON
			accel = (1-output['gamepad_z'])*50
			if output['gamepad_x'] == 0 :
				wheel = 30 #servo middle position
				time_deadzone_start = time.time()
			else:
				enable_servo.on()
				accel = accel /2 #save energy for servo, delete if we could find a way to get more amps
				wheel = ((output['gamepad_x']+1)/2*40)+10
			print int(accel)
			print int(wheel)
			if (speed > 70):
				speed.ChangeDutyCycle(int(accel))
			servo.ChangeDutyCycle(int(wheel))
			if wheel == 30:
				if time.time()-time_deadzone_start > time_deadzone: 
					enable_servo.off()
		except ValueError:
			node_script.stdout.readline() #discard unmeaningful data

Questa parte di codice legge il json del joystick e assegna ai vari pin il valore giusto, con qualche accorgimento (ad es. taglia la velocità al motore quando è acceso il servo, spegne il servo quando è al centro) per far in modo di non stressare troppo la batteria, che per il momento non è adatta a fornire tutti gli ampere necessari (il motore è particolarmente affamato).

Lo streaming

Una webcam

Come già scritto, Arietta ha tre USB, ma se si sta usando l'USB on board non è possibile usare la A, se si usa il modulo wi fi non si può usare la B, rimane la C. Guardate sul pinout ufficiale dov'è. Ricordate che è a 3.3v quindi fate molta attenzione a cosa attaccate! Nel nostro caso è stata usata una webcam recuperata da un EeePC.

Webcamwindcar.jpg

Per vedere se tutto funziona come dovrebbe, una volta attaccato il vostro dispositivo scrivete su terminale

lsusb

e dovreste vedere il vostro device nella lista, nel mio caso

Bus 002 Device 002: ID eb1a:2761 eMPIA Technology, Inc. EeePC 701 integrated Webcam

(è abbastanza strano trovare la webcam integrata dell'EeePC fuori da un EeePC, non trovate?)

mjpg-streamer

Per lo streaming, uno dei più facili e versatili software a disposizione è mjpg-streamer. Il programmino tira su un serverino con varie pagine, ognuna con un tipo diverso di streaming (java, javascript, diretto, link per VLC). Trovate il codice nella pagina su Sourceforge ma trovate i binari già compilati per Arietta sul sito ufficiale.

Per far partire il server:

./mjpg_streamer -i "./input_uvc.so -d /dev/video0 -n -r 320x240" -o "./output_http.so -w ./www"

input_uvc.so è il plugin di input, potete mandare in input anche un file ad es. allo stesso modo, output_http è il plugin dell'output, potendo anche streamare su un file, su una porta udp, ecc. /dev/video0 è il device file della webcam, se ne avete più di una potreste avere video1 o successivi -r è la risoluzione -n specifica che non devono esserci i comandi per il pan-tilt -w è il percorso dei file del server web, se volete personalizzare il look, è in quella cartella che dovete andare a metter le mani

Ci sono anche altre opzioni, ad es. per cambiare il numero della porta o il formato. Se è andato tutto come dovrebbe, allora andate sulla stessa rete di Arietta e aprite un browser. Collegatevi all'indirizzo di Arietta (di solito arietta.local) e aggiungete :8080, la porta di default.

http://arietta.local:8080/

Se tutto è andato come dovrebbe (e due) dovreste vedere la pagina di mjpg-streamer e il vostro stream! Ora potete metterlo in grande e tenerlo a fianco dell'altra pagina, quella che legge i controlli.

Risultato

Ecco la WindCar in tutto il suo splendore!

Windcar finale.jpg