Un tutorial introduttivo alla programmazione dei robot

Pubblicato: 2022-03-11
Nota del redattore: il 16 ottobre 2018, questo articolo è stato revisionato per funzionare con le ultime tecnologie.

Ammettiamolo, i robot sono fantastici. Un giorno guideranno anche il mondo e, si spera, in quel momento avranno pietà dei loro poveri creatori carnosi e morbidi (alias sviluppatori di robotica) e ci aiuteranno a costruire un'utopia spaziale piena di abbondanza. Sto scherzando ovviamente, ma solo un po'.

Nella mia ambizione di avere una piccola influenza sulla questione, l'anno scorso ho seguito un corso sulla teoria del controllo robot autonomo, culminato nella creazione di un simulatore robotico basato su Python che mi ha permesso di esercitarmi con la teoria del controllo su un robot semplice, mobile e programmabile .

In questo articolo, mostrerò come utilizzare un framework per robot Python per sviluppare software di controllo, descriverò lo schema di controllo che ho sviluppato per il mio robot simulato, illustrerò come interagisce con il suo ambiente e raggiunge i suoi obiettivi e discuterò alcuni dei sfide fondamentali della programmazione robotica che ho incontrato lungo il percorso.

Per seguire questo tutorial sulla programmazione robotica per principianti, dovresti avere una conoscenza di base di due cose:

  • Matematica: useremo alcune funzioni e vettori trigonometrici
  • Python, poiché Python è uno dei più popolari linguaggi di programmazione di base per i robot, utilizzeremo le librerie e le funzioni di base di Python

I frammenti di codice mostrati qui sono solo una parte dell'intero simulatore, che si basa su classi e interfacce, quindi per leggere il codice direttamente, potresti aver bisogno di esperienza in Python e nella programmazione orientata agli oggetti.

Infine, gli argomenti opzionali che ti aiuteranno a seguire meglio questo tutorial sono sapere cos'è una macchina a stati e come funzionano i sensori di portata e gli encoder.

La sfida del robot programmabile: percezione vs realtà e fragilità del controllo

La sfida fondamentale di tutta la robotica è questa: è impossibile conoscere il vero stato dell'ambiente. Il software di controllo del robot può solo indovinare lo stato del mondo reale in base alle misurazioni restituite dai suoi sensori. Può solo tentare di cambiare lo stato del mondo reale attraverso la generazione di segnali di controllo.

Questo grafico mostra l'interazione tra un robot fisico e i controlli del computer quando si pratica la programmazione di robot Python.

Il software di controllo del robot può solo indovinare lo stato del mondo reale in base alle misurazioni restituite dai suoi sensori.

Pertanto, uno dei primi passi nella progettazione del controllo è elaborare un'astrazione del mondo reale, noto come modello , con cui interpretare le letture dei nostri sensori e prendere decisioni. Finché il mondo reale si comporta secondo le ipotesi del modello, possiamo fare buone ipotesi ed esercitare il controllo. Non appena il mondo reale si discosterà da questi presupposti, tuttavia, non saremo più in grado di fare buone ipotesi e il controllo andrà perso. Spesso, una volta perso il controllo, non è più possibile recuperarlo. (A meno che qualche benevola forza esterna non lo ripristini.)

Questo è uno dei motivi principali per cui la programmazione robotica è così difficile. Vediamo spesso i video dell'ultimo robot di ricerca in laboratorio, che esegue fantastiche prodezze di destrezza, navigazione o lavoro di squadra, e siamo tentati di chiederci: "Perché non viene utilizzato nel mondo reale?" Bene, la prossima volta che vedrai un video del genere, dai un'occhiata a quanto è altamente controllato l'ambiente del laboratorio. Nella maggior parte dei casi, questi robot sono in grado di svolgere questi compiti impressionanti solo fintanto che le condizioni ambientali rimangono entro i confini ristretti del suo modello interno. Pertanto, una chiave per il progresso della robotica è lo sviluppo di modelli più complessi, flessibili e robusti, e tale progresso è soggetto ai limiti delle risorse computazionali disponibili.

Una chiave per il progresso della robotica è lo sviluppo di modelli più complessi, flessibili e robusti.

[Nota a margine: Filosofi e psicologi allo stesso modo noteranno che le creature viventi soffrono anche di dipendenza dalla propria percezione interna di ciò che i loro sensi stanno dicendo loro. Molti progressi nella robotica derivano dall'osservazione delle creature viventi e dal vedere come reagiscono a stimoli inaspettati. Pensaci. Qual è il tuo modello interiore del mondo? È diverso da quello di una formica e quello di un pesce? (Si spera). Tuttavia, come la formica e il pesce, è probabile che semplifichi eccessivamente alcune realtà del mondo. Quando le tue supposizioni sul mondo non sono corrette, può metterti a rischio di perdere il controllo delle cose. A volte chiamiamo questo "pericolo". Allo stesso modo in cui il nostro piccolo robot lotta per sopravvivere contro l'universo sconosciuto, anche noi tutti. Questa è una potente intuizione per i robotici.]

Il simulatore di robot programmabile

Il simulatore che ho costruito è scritto in Python e molto abilmente soprannominato Sobot Rimulator . Puoi trovare la v1.0.0 su GitHub. Non ha molti campanelli e fischietti, ma è costruito per fare una cosa molto bene: fornire una simulazione accurata di un robot mobile e fornire a un aspirante robotico una struttura semplice per esercitarsi nella programmazione del software del robot. Anche se è sempre meglio avere un vero robot con cui giocare, un buon simulatore di robot Python è molto più accessibile ed è un ottimo punto di partenza.

Nei robot del mondo reale, il software che genera i segnali di controllo (il "controllore") deve funzionare a velocità molto elevata ed eseguire calcoli complessi. Ciò influisce sulla scelta dei linguaggi di programmazione robotica migliori da utilizzare: di solito, il C++ viene utilizzato per questo tipo di scenari, ma nelle applicazioni di robotica più semplici, Python è un ottimo compromesso tra velocità di esecuzione e facilità di sviluppo e test.

Il software che ho scritto simula un robot di ricerca nella vita reale chiamato Khepera, ma può essere adattato a una gamma di robot mobili con dimensioni e sensori diversi. Poiché ho provato a programmare il simulatore il più simile possibile alle capacità del robot reale, la logica di controllo può essere caricata in un vero robot Khepera con un refactoring minimo e funzionerà allo stesso modo del robot simulato. Le caratteristiche specifiche implementate si riferiscono al Khepera III, ma possono essere facilmente adattate al nuovo Khepera IV.

In altre parole, la programmazione di un robot simulato è analoga alla programmazione di un robot reale. Questo è fondamentale se il simulatore vuole essere di qualche utilità per sviluppare e valutare diversi approcci software di controllo.

In questo tutorial, descriverò l'architettura del software di controllo del robot fornita con la v1.0.0 di Sobot Rimulator e fornirò frammenti dal sorgente Python (con lievi modifiche per chiarezza). Tuttavia, ti incoraggio a tuffarti nella fonte e a scherzare. Il simulatore è stato biforcato e utilizzato per controllare diversi robot mobili, incluso un Roomba2 di iRobot. Allo stesso modo, sentiti libero di creare un fork del progetto e migliorarlo.

La logica di controllo del robot è vincolata a queste classi/file Python:

  • models/supervisor.py —questa classe è responsabile dell'interazione tra il mondo simulato attorno al robot e il robot stesso. Fa evolvere la nostra macchina a stati robotica e attiva i controller per calcolare il comportamento desiderato.
  • models/supervisor_state_machine.py —questa classe rappresenta i diversi stati in cui può trovarsi il robot, a seconda della sua interpretazione dei sensori.
  • I file nella directory models/controllers : queste classi implementano comportamenti diversi del robot dato uno stato noto dell'ambiente. In particolare, viene selezionato un controller specifico a seconda della macchina a stati.

L'obiettivo. il gol

I robot, come le persone, hanno bisogno di uno scopo nella vita. L'obiettivo del nostro software che controlla questo robot sarà molto semplice: tenterà di raggiungere un punto obiettivo predeterminato. Questa è solitamente la caratteristica di base che dovrebbe avere qualsiasi robot mobile, dalle auto a guida autonoma agli aspirapolvere robot. Le coordinate dell'obiettivo sono programmate nel software di controllo prima dell'attivazione del robot, ma potrebbero essere generate da un'applicazione Python aggiuntiva che supervisiona i movimenti del robot. Ad esempio, pensa alla guida attraverso più waypoint.

Tuttavia, a complicare le cose, l'ambiente del robot potrebbe essere disseminato di ostacoli. Il robot NON PUÒ entrare in collisione con un ostacolo nel suo percorso verso l'obiettivo. Pertanto, se il robot incontra un ostacolo, dovrà orientarsi in modo da poter continuare la sua strada verso l'obiettivo.

Il Robot Programmabile

Ogni robot ha capacità e problemi di controllo diversi. Acquistiamo familiarità con il nostro robot programmabile simulato.

La prima cosa da notare è che, in questa guida, il nostro robot sarà un robot mobile autonomo . Ciò significa che si muoverà liberamente nello spazio e che lo farà sotto il proprio controllo. Ciò è in contrasto, ad esempio, con un robot telecomandato (che non è autonomo) o un braccio robotico di fabbrica (che non è mobile). Il nostro robot deve capire da solo come raggiungere i suoi obiettivi e sopravvivere nel suo ambiente. Questa si rivela una sfida sorprendentemente difficile per i programmatori di robotica alle prime armi.

Ingressi di controllo: sensori

Esistono molti modi diversi in cui un robot può essere attrezzato per monitorare il suo ambiente. Questi possono includere qualsiasi cosa, da sensori di prossimità, sensori di luce, paraurti, telecamere e così via. Inoltre, i robot possono comunicare con sensori esterni che forniscono loro informazioni che loro stessi non possono osservare direttamente.

Il nostro robot di riferimento è dotato di nove sensori a infrarossi , il modello più recente ha otto sensori di prossimità a infrarossi e cinque a ultrasuoni, disposti in una "gonna" in ogni direzione. Ci sono più sensori rivolti verso la parte anteriore del robot che dietro, perché di solito è più importante che il robot sappia cosa c'è davanti a sé piuttosto che cosa c'è dietro.

Oltre ai sensori di prossimità, il robot ha una coppia di ticker delle ruote che tracciano il movimento delle ruote. Questi ti consentono di tenere traccia del numero di rotazioni effettuate da ciascuna ruota, con un giro completo in avanti di una ruota pari a 2.765 tick. Gira nella direzione opposta il conteggio all'indietro, diminuendo il conteggio dei tick invece di aumentarlo. Non devi preoccuparti di numeri specifici in questo tutorial perché il software che scriveremo utilizza la distanza percorsa espressa in metri. Più avanti ti mostrerò come calcolarlo dai tick con una semplice funzione Python.

Uscite di controllo: mobilità

Alcuni robot si muovono sulle gambe. Alcuni rotolano come una palla. Alcuni addirittura strisciano come un serpente.

Il nostro robot è un robot a trasmissione differenziale, il che significa che gira su due ruote. Quando entrambe le ruote girano alla stessa velocità, il robot si muove in linea retta. Quando le ruote si muovono a velocità diverse, il robot gira. Pertanto, il controllo del movimento di questo robot si riduce al controllo appropriato della velocità di rotazione di ciascuna di queste due ruote.

API

In Sobot Rimulator, la separazione tra il "computer" del robot e il mondo fisico (simulato) è incarnata dal file robot_supervisor_interface.py , che definisce l'intera API per interagire con i sensori e i motori del "robot reale":

  • read_proximity_sensors() restituisce una matrice di nove valori nel formato nativo dei sensori
  • read_wheel_encoders() restituisce un array di due valori che indicano i tick totali dall'inizio
  • set_wheel_drive_rates( v_l, v_r ) prende due valori (in radianti al secondo) e imposta la velocità sinistra e destra delle ruote su quei due valori

Questa interfaccia utilizza internamente un oggetto robot che fornisce i dati dei sensori e la possibilità di muovere motori o ruote. Se vuoi creare un robot diverso, devi semplicemente fornire una classe di robot Python diversa che può essere utilizzata dalla stessa interfaccia e il resto del codice (controller, supervisore e simulatore) funzionerà immediatamente!

Il simulatore

Poiché useresti un vero robot nel mondo reale senza prestare troppa attenzione alle leggi della fisica coinvolte, puoi ignorare come viene simulato il robot e saltare direttamente a come è programmato il software del controller, poiché sarà quasi lo stesso tra il mondo reale e una simulazione. Ma se sei curioso, te lo presento brevemente qui.

Il file world.py è una classe Python che rappresenta il mondo simulato, con robot e ostacoli all'interno. La funzione step all'interno di questa classe si occupa dell'evoluzione del nostro mondo semplice:

  • Applicazione di regole fisiche ai movimenti del robot
  • Considerando le collisioni con gli ostacoli
  • Fornire nuovi valori per i sensori del robot

Alla fine, chiama i supervisori del robot responsabili dell'esecuzione del software del cervello del robot.

La funzione step viene eseguita in un ciclo in modo che robot.step_motion() muova il robot utilizzando la velocità della ruota calcolata dal supervisore nella fase di simulazione precedente.

 # step the simulation through one time interval def step( self ): dt = self.dt # step all the robots for robot in self.robots: # step robot motion robot.step_motion( dt ) # apply physics interactions self.physics.apply_physics() # NOTE: The supervisors must run last to ensure they are observing the "current" world # step all of the supervisors for supervisor in self.supervisors: supervisor.step( dt ) # increment world time self.world_time += dt

La funzione apply_physics() aggiorna internamente i valori dei sensori di prossimità del robot in modo che il supervisore sia in grado di stimare l'ambiente nella fase di simulazione corrente. Gli stessi concetti si applicano agli encoder.

Un modello semplice

Innanzitutto, il nostro robot avrà un modello molto semplice. Farà molte ipotesi sul mondo. Alcuni di quelli importanti includono:

  • Il terreno è sempre pianeggiante e regolare
  • Gli ostacoli non sono mai rotondi
  • Le ruote non slittano mai
  • Niente potrà mai spingere il robot in giro
  • I sensori non si guastano né danno letture errate
  • Le ruote girano sempre quando gli viene detto di farlo

Sebbene la maggior parte di queste ipotesi siano ragionevoli all'interno di un ambiente simile a una casa, potrebbero essere presenti ostacoli rotondi. Il nostro software per evitare gli ostacoli ha un'implementazione semplice e segue il confine degli ostacoli per aggirarli. Indicheremo ai lettori come migliorare il quadro di controllo del nostro robot con un ulteriore controllo per evitare ostacoli circolari.

Il ciclo di controllo

Entriamo ora nel cuore del nostro software di controllo e spiegheremo i comportamenti che vogliamo programmare all'interno del robot. Ulteriori comportamenti possono essere aggiunti a questo framework e dovresti provare le tue idee dopo aver finito di leggere! Il software di robotica basato sul comportamento è stato proposto più di 20 anni fa ed è ancora un potente strumento per la robotica mobile. Ad esempio, nel 2007 una serie di comportamenti è stata utilizzata nella DARPA Urban Challenge, la prima competizione per auto a guida autonoma!

Un robot è un sistema dinamico. Lo stato del robot, le letture dei suoi sensori e gli effetti dei suoi segnali di controllo sono in continuo mutamento. Il controllo del modo in cui si svolgono gli eventi prevede i tre passaggi seguenti:

  1. Applicare segnali di controllo.
  2. Misura i risultati.
  3. Genera nuovi segnali di controllo calcolati per avvicinarci al nostro obiettivo.

Questi passaggi vengono ripetuti più e più volte fino a quando non abbiamo raggiunto il nostro obiettivo. Più volte riusciamo a farlo al secondo, maggiore sarà il controllo che avremo sul sistema. Il robot Sobot Rimulator ripete questi passaggi 20 volte al secondo (20 Hz), ma molti robot devono farlo migliaia o milioni di volte al secondo per avere un controllo adeguato. Ricorda la nostra precedente introduzione sui diversi linguaggi di programmazione dei robot per diversi sistemi robotici e requisiti di velocità.

In generale, ogni volta che il nostro robot effettua misurazioni con i suoi sensori, utilizza queste misurazioni per aggiornare la sua stima interna dello stato del mondo, ad esempio la distanza dal suo obiettivo. Confronta questo stato con un valore di riferimento di quello che vuole che sia lo stato (per la distanza, vuole che sia zero) e calcola l'errore tra lo stato desiderato e lo stato attuale. Una volta che queste informazioni sono note, la generazione di nuovi segnali di controllo può essere ridotta a un problema di minimizzazione dell'errore che alla fine sposterà il robot verso l'obiettivo.

Un trucco ingegnoso: semplificare il modello

Per controllare il robot che vogliamo programmare, dobbiamo inviare un segnale alla ruota sinistra che gli dice quanto velocemente girare e un segnale separato alla ruota destra che gli dice quanto velocemente girare. Chiamiamo questi segnali v L e v R . Tuttavia, pensare costantemente in termini di v L e v R è molto ingombrante. Invece di chiedere: "Quanto velocemente vogliamo che giri la ruota sinistra e quanto velocemente vogliamo che giri la ruota destra?" è più naturale chiedersi: "Quanto velocemente vogliamo che il robot si muova in avanti e quanto velocemente vogliamo che giri o cambi direzione?" Chiamiamo questi parametri velocità ve velocità angolare (rotazionale) ω (leggi “omega”). Si scopre che possiamo basare il nostro intero modello su v e ω invece di v L e v R , e solo una volta che abbiamo determinato come vogliamo che il nostro robot programmato si muova, trasformiamo matematicamente questi due valori in v L e v R di cui abbiamo bisogno per controllare effettivamente le ruote del robot. Questo è noto come un modello di controllo uniciclo .

Nella programmazione della robotica, è importante comprendere la differenza tra i modelli di azionamento monociclo e differenziale.

Ecco il codice Python che implementa la trasformazione finale in supervisor.py . Nota che se ω è 0, entrambe le ruote gireranno alla stessa velocità:

 # generate and send the correct commands to the robot def _send_robot_commands( self ): # ... v_l, v_r = self._uni_to_diff( v, omega ) self.robot.set_wheel_drive_rates( v_l, v_r ) def _uni_to_diff( self, v, omega ): # v = translational velocity (m/s) # omega = angular velocity (rad/s) R = self.robot_wheel_radius L = self.robot_wheel_base_length v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R) v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R) return v_l, v_r

Stato di stima: Robot, conosci te stesso

Utilizzando i suoi sensori, il robot deve cercare di stimare lo stato dell'ambiente e il proprio stato. Queste stime non saranno mai perfette, ma devono essere abbastanza buone perché il robot baserà tutte le sue decisioni su queste stime. Usando solo i suoi sensori di prossimità e ticker delle ruote, deve provare a indovinare quanto segue:

  • La direzione degli ostacoli
  • La distanza dagli ostacoli
  • La posizione del robot
  • La testata del robot

Le prime due proprietà sono determinate dalle letture del sensore di prossimità e sono abbastanza semplici. La funzione API read_proximity_sensors() restituisce un array di nove valori, uno per ogni sensore. Sappiamo in anticipo che la settima lettura, ad esempio, corrisponde al sensore che punta 75 gradi a destra del robot.

Quindi, se questo valore mostra una lettura corrispondente a 0,1 metri di distanza, sappiamo che c'è un ostacolo a 0,1 metri di distanza, 75 gradi a sinistra. Se non ci sono ostacoli, il sensore restituirà una lettura della sua portata massima di 0,2 metri. Pertanto, se leggiamo 0,2 metri sul sensore sette, assumeremo che in realtà non ci siano ostacoli in quella direzione.

A causa del modo in cui funzionano i sensori a infrarossi (misurazione della riflessione a infrarossi), i numeri che restituiscono sono una trasformazione non lineare della distanza effettiva rilevata. Pertanto, la funzione Python per determinare la distanza indicata deve convertire queste letture in metri. Questo viene fatto in supervisor.py come segue:

 # update the distances indicated by the proximity sensors def _update_proximity_sensor_distances( self ): self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for readval in self.robot.read_proximity_sensors() ]

Ancora una volta, abbiamo un modello di sensore specifico in questo framework di robot Python, mentre nel mondo reale, i sensori sono dotati di un software di accompagnamento che dovrebbe fornire funzioni di conversione simili da valori non lineari a metri.

Determinare la posizione e la direzione del robot (insieme noto come posa nella programmazione robotica) è un po' più impegnativo. Il nostro robot usa l'odometria per stimare la sua posa. È qui che entrano in gioco i ticker delle ruote. Misurando quanto ciascuna ruota ha girato dall'ultima iterazione del ciclo di controllo, è possibile ottenere una buona stima di come è cambiata la posa del robot, ma solo se la modifica è minima .

Questo è uno dei motivi per cui è importante ripetere il ciclo di controllo molto frequentemente in un robot del mondo reale, in cui i motori che muovono le ruote potrebbero non essere perfetti. Se abbiamo aspettato troppo a lungo per misurare i ticker delle ruote, entrambe le ruote avrebbero potuto fare molto e sarà impossibile stimare dove siamo finiti.

Dato il nostro attuale simulatore software, possiamo permetterci di eseguire il calcolo dell'odometria a 20 Hz, la stessa frequenza dei controller. Ma potrebbe essere una buona idea avere un thread Python separato che funzioni più velocemente per catturare i movimenti più piccoli dei ticker.

Di seguito è riportata la funzione di odometria completa in supervisor.py che aggiorna la stima della posa del robot. Si noti che la posa del robot è composta dalle coordinate x y dalla direzione theta , misurata in radianti dall'asse X positivo. Positivo x è a est e positivo y è a nord. Quindi un'intestazione di 0 indica che il robot è rivolto direttamente a est. Il robot presume sempre che la sua posa iniziale sia (0, 0), 0 .

 # update the estimated position of the robot using it's wheel encoder readings def _update_odometry( self ): R = self.robot_wheel_radius N = float( self.wheel_encoder_ticks_per_revolution ) # read the wheel encoder values ticks_left, ticks_right = self.robot.read_wheel_encoders() # get the difference in ticks since the last iteration d_ticks_left = ticks_left - self.prev_ticks_left d_ticks_right = ticks_right - self.prev_ticks_right # estimate the wheel movements d_left_wheel = 2*pi*R*( d_ticks_left / N ) d_right_wheel = 2*pi*R*( d_ticks_right / N ) d_center = 0.5 * ( d_left_wheel + d_right_wheel ) # calculate the new pose prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack() new_x = prev_x + ( d_center * cos( prev_theta ) ) new_y = prev_y + ( d_center * sin( prev_theta ) ) new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length ) # update the pose estimate with the new values self.estimated_pose.scalar_update( new_x, new_y, new_theta ) # save the current tick count for the next iteration self.prev_ticks_left = ticks_left self.prev_ticks_right = ticks_right

Ora che il nostro robot è in grado di generare una buona stima del mondo reale, utilizziamo queste informazioni per raggiungere i nostri obiettivi.

Correlati: Esercitazione sulla fisica dei videogiochi - Rilevamento delle collisioni per oggetti solidi

Metodi di programmazione dei robot Python: comportamento da raggiungere

Lo scopo supremo dell'esistenza del nostro piccolo robot in questo tutorial di programmazione è arrivare al punto obiettivo. Allora come facciamo a far girare le ruote per arrivarci? Iniziamo semplificando un po' la nostra visione del mondo e supponiamo che non ci siano ostacoli sulla strada.

Questo diventa quindi un compito semplice e può essere facilmente programmato in Python. Se andiamo avanti mentre affrontiamo la porta, ci arriveremo. Grazie alla nostra odometria, sappiamo quali sono le nostre coordinate attuali e la direzione. Sappiamo anche quali sono le coordinate dell'obiettivo perché erano preprogrammate. Pertanto, usando un po' di algebra lineare, possiamo determinare il vettore dalla nostra posizione all'obiettivo, come in go_to_goal_controller.py :

 # return a go-to-goal heading vector in the robot's reference frame def calculate_gtg_heading_vector( self ): # get the inverse of the robot's pose robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack() # calculate the goal vector in the robot's reference frame goal = self.supervisor.goal() goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos ) return goal

Nota che stiamo portando il vettore all'obiettivo nel sistema di riferimento del robot e NON nelle coordinate mondiali. Se l'obiettivo si trova sull'asse X nel sistema di riferimento del robot, significa che si trova direttamente davanti al robot. Pertanto, l'angolo di questo vettore rispetto all'asse X è la differenza tra la nostra direzione e la direzione su cui vogliamo trovarci. In altre parole, è l' errore tra il nostro stato attuale e quello che vogliamo che sia il nostro stato attuale. Pertanto, vogliamo regolare la nostra velocità di virata ω in modo che l'angolo tra la nostra direzione e l'obiettivo cambi verso 0. Vogliamo ridurre al minimo l'errore:

 # calculate the error terms theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] ) # calculate angular velocity omega = self.kP * theta_d

self.kP nello snippet sopra del controller L'implementazione di Python è un guadagno di controllo. È un coefficiente che determina la velocità con cui giriamo in proporzione alla distanza dalla porta che stiamo affrontando. Se l'errore nella nostra intestazione è 0 , allora anche la velocità di rotazione è 0 . Nella vera funzione Python all'interno del file go_to_goal_controller.py , vedrai guadagni più simili, poiché abbiamo usato un controller PID invece di un semplice coefficiente proporzionale.

Ora che abbiamo la nostra velocità angolare ω , come determiniamo la nostra velocità in avanti v ? Una buona regola empirica generale è quella che probabilmente conosci istintivamente: se non stiamo girando, possiamo andare avanti a tutta velocità, quindi più velocemente giriamo, più dovremmo rallentare. Questo generalmente ci aiuta a mantenere il nostro sistema stabile e ad agire entro i limiti del nostro modello. Quindi, v è una funzione di ω . In go_to_goal_controller.py l'equazione è:

 # calculate translational velocity # velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5

Un suggerimento per elaborare questa formula è considerare che di solito rallentiamo quando siamo vicini alla meta per raggiungerla a velocità zero. Come cambierebbe questa formula? Deve includere in qualche modo una sostituzione di v_max() con qualcosa di proporzionale alla distanza. OK, abbiamo quasi completato un singolo ciclo di controllo. L'unica cosa rimasta da fare è trasformare questi due parametri del modello di monociclo in velocità differenziali delle ruote e inviare i segnali alle ruote. Ecco un esempio della traiettoria del robot sotto il controller go-to-goal, senza ostacoli:

Questo è un esempio della traiettoria del robot programmato.

Come possiamo vedere, il vettore verso l'obiettivo è un riferimento efficace su cui basare i nostri calcoli di controllo. È una rappresentazione interna di "dove vogliamo andare". Come vedremo, l'unica grande differenza tra andare verso l'obiettivo e altri comportamenti è che a volte andare verso l'obiettivo è una cattiva idea, quindi dobbiamo calcolare un vettore di riferimento diverso.

Metodi di programmazione dei robot Python: comportamento evita gli ostacoli

Andare verso l'obiettivo quando c'è un ostacolo in quella direzione è un esempio calzante. Invece di correre a capofitto nelle cose sulla nostra strada, proviamo a programmare una legge di controllo che faccia sì che il robot le eviti.

Per semplificare lo scenario, ora dimentichiamo completamente il punto obiettivo e facciamo solo il seguente nostro obiettivo: Quando non ci sono ostacoli davanti a noi, vai avanti. Quando si incontra un ostacolo, allontanarsi da esso finché non è più davanti a noi.

Di conseguenza, quando non ci sono ostacoli davanti a noi, vogliamo che il nostro vettore di riferimento punti semplicemente in avanti. Allora ω sarà zero e v sarà la velocità massima. Tuttavia, non appena rileviamo un ostacolo con i nostri sensori di prossimità, vogliamo che il vettore di riferimento punti in qualsiasi direzione sia lontana dall'ostacolo. Ciò farà sì che ω si alzi per allontanarci dall'ostacolo e farà cadere v per assicurarci di non imbatterci accidentalmente nell'ostacolo nel processo.

Un modo accurato per generare il vettore di riferimento desiderato è trasformare le nostre nove letture di prossimità in vettori e prendere una somma ponderata. Quando non vengono rilevati ostacoli, i vettori si sommano simmetricamente, risultando in un vettore di riferimento che punta dritto come desiderato. Ma se un sensore, ad esempio, sul lato destro rileva un ostacolo, contribuirà alla somma con un vettore più piccolo e il risultato sarà un vettore di riferimento che viene spostato verso sinistra.

Per un robot generico con una diversa posizione dei sensori, è possibile applicare la stessa idea ma potrebbe richiedere modifiche ai pesi e/o cure aggiuntive quando i sensori sono simmetrici davanti e dietro al robot, poiché la somma pesata potrebbe diventare zero .

Se programmato correttamente, il robot può evitare questi complessi ostacoli.

Ecco il codice che fa questo in avoid_obstacles_controller.py :

 # sensor gains (weights) self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi ) for p in supervisor.proximity_sensor_placements() ] # ... # return an obstacle avoidance vector in the robot's reference frame # also returns vectors to detected obstacles in the robot's reference frame def calculate_ao_heading_vector( self ): # initialize vector obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements ) ao_heading_vector = [ 0.0, 0.0 ] # get the distances indicated by the robot's sensor readings sensor_distances = self.supervisor.proximity_sensor_distances() # calculate the position of detected obstacles and find an avoidance vector robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack() for i in range( len( sensor_distances ) ): # calculate the position of the obstacle sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack() vector = [ sensor_distances[i], 0.0 ] vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos ) obstacle_vectors[i] = vector # store the obstacle vectors in the robot's reference frame # accumulate the heading vector within the robot's reference frame ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) ) return ao_heading_vector, obstacle_vectors

Utilizzando il risultante ao_heading_vector come riferimento per il robot da cercare di abbinare, ecco i risultati dell'esecuzione del software del robot in simulazione utilizzando solo il controller evita gli ostacoli, ignorando completamente il punto obiettivo. Il robot rimbalza senza meta, ma non si scontra mai con un ostacolo e riesce persino a navigare in spazi molto ristretti:

Questo robot sta evitando con successo gli ostacoli all'interno del simulatore di robot Python.

Metodi di programmazione del robot Python: automi ibridi (Behavior State Machine)

Finora abbiamo descritto due comportamenti, andare all'obiettivo ed evitare gli ostacoli, in isolamento. Entrambi svolgono egregiamente la loro funzione, ma per raggiungere con successo l'obiettivo in un ambiente pieno di ostacoli, è necessario combinarli.

La soluzione che svilupperemo risiede in una classe di macchine che ha la designazione estremamente interessante degli automi ibridi . Un automa ibrido è programmato con diversi comportamenti o modalità, nonché una macchina a stati di supervisione. La macchina a stati di supervisione passa da una modalità all'altra in tempi discreti (quando gli obiettivi vengono raggiunti o l'ambiente cambia improvvisamente troppo), mentre ogni comportamento utilizza sensori e ruote per reagire continuamente ai cambiamenti dell'ambiente. La soluzione è stata chiamata ibrida perché si evolve in modo sia discreto che continuo.

Il nostro framework per robot Python implementa la macchina a stati nel file supervisor_state_machine.py .

Dotato dei nostri due comportamenti pratici, si suggerisce una semplice logica: quando non viene rilevato alcun ostacolo, utilizzare il comportamento vai all'obiettivo. Quando viene rilevato un ostacolo, passare al comportamento evita ostacoli fino a quando l'ostacolo non viene più rilevato.

A quanto pare, tuttavia, questa logica produrrà molti problemi. Ciò che questo sistema tende a fare quando incontra un ostacolo è allontanarsi da esso, quindi non appena si è allontanato da esso, girarsi indietro e incontrarlo di nuovo. Il risultato è un ciclo infinito di commutazione rapida che rende il robot inutilizzabile. In the worst case, the robot may switch between behaviors with every iteration of the control loop—a state known as a Zeno condition .

There are multiple solutions to this problem, and readers that are looking for deeper knowledge should check, for example, the DAMN software architecture.

What we need for our simple simulated robot is an easier solution: One more behavior specialized with the task of getting around an obstacle and reaching the other side.

Python Robot Programming Methods: Follow-Wall Behavior

Here's the idea: When we encounter an obstacle, take the two sensor readings that are closest to the obstacle and use them to estimate the surface of the obstacle. Then, simply set our reference vector to be parallel to this surface. Keep following this wall until A) the obstacle is no longer between us and the goal, and B) we are closer to the goal than we were when we started. Then we can be certain we have navigated the obstacle properly.

With our limited information, we can't say for certain whether it will be faster to go around the obstacle to the left or to the right. To make up our minds, we select the direction that will move us closer to the goal immediately. To figure out which way that is, we need to know the reference vectors of the go-to-goal behavior and the avoid-obstacle behavior, as well as both of the possible follow-wall reference vectors. Here is an illustration of how the final decision is made (in this case, the robot will choose to go left):

Utilizing a few types of behaviors, the programmed robot avoids obstacles and continues onward.

Determining the follow-wall reference vectors turns out to be a bit more involved than either the avoid-obstacle or go-to-goal reference vectors. Take a look at the Python code in follow_wall_controller.py to see how it's done.

Final Control Design

The final control design uses the follow-wall behavior for almost all encounters with obstacles. However, if the robot finds itself in a tight spot, dangerously close to a collision, it will switch to pure avoid-obstacles mode until it is a safer distance away, and then return to follow-wall. Once obstacles have been successfully negotiated, the robot switches to go-to-goal. Here is the final state diagram, which is programmed inside the supervisor_state_machine.py :

This diagram illustrates the switching between robotics programming behaviors to achieve a goal and avoid obstacles.

Here is the robot successfully navigating a crowded environment using this control scheme:

The robot simulator has successfully allowed the robot software to avoid obstacles and achieve its original purpose.

An additional feature of the state machine that you can try to implement is a way to avoid circular obstacles by switching to go-to-goal as soon as possible instead of following the obstacle border until the end (which does not exist for circular objects!)

Tweak, Tweak, Tweak: Trial and Error

The control scheme that comes with Sobot Rimulator is very finely tuned. It took many hours of tweaking one little variable here, and another equation there, to get it to work in a way I was satisfied with. Robotics programming often involves a great deal of plain old trial-and-error. Robots are very complex and there are few shortcuts to getting them to behave optimally in a robot simulator environment…at least, not much short of outright machine learning, but that's a whole other can of worms.

Robotics often involves a great deal of plain old trial-and-error.

I encourage you to play with the control variables in Sobot Rimulator and observe and attempt to interpret the results. Changes to the following all have profound effects on the simulated robot's behavior:

  • The error gain kP in each controller
  • The sensor gains used by the avoid-obstacles controller
  • The calculation of v as a function of ω in each controller
  • The obstacle standoff distance used by the follow-wall controller
  • The switching conditions used by supervisor_state_machine.py
  • Pretty much anything else

When Programmable Robots Fail

We've done a lot of work to get to this point, and this robot seems pretty clever. Yet, if you run Sobot Rimulator through several randomized maps, it won't be long before you find one that this robot can't deal with. Sometimes it drives itself directly into tight corners and collides. Sometimes it just oscillates back and forth endlessly on the wrong side of an obstacle. Occasionally it is legitimately imprisoned with no possible path to the goal. After all of our testing and tweaking, sometimes we must come to the conclusion that the model we are working with just isn't up to the job, and we have to change the design or add functionality.

In the mobile robot universe, our little robot's “brain” is on the simpler end of the spectrum. Many of the failure cases it encounters could be overcome by adding some more advanced software to the mix. More advanced robots make use of techniques such as mapping , to remember where it's been and avoid trying the same things over and over; heuristics , to generate acceptable decisions when there is no perfect decision to be found; and machine learning , to more perfectly tune the various control parameters governing the robot's behavior.

A Sample of What's to Come

Robots are already doing so much for us, and they are only going to be doing more in the future. While even basic robotics programming is a tough field of study requiring great patience, it is also a fascinating and immensely rewarding one.

In this tutorial, we learned how to develop reactive control software for a robot using the high-level programming language Python. But there are many more advanced concepts that can be learned and tested quickly with a Python robot framework similar to the one we prototyped here. I hope you will consider getting involved in the shaping of things to come!


Acknowledgement: I would like to thank Dr. Magnus Egerstedt and Jean-Pierre de la Croix of the Georgia Institute of Technology for teaching me all this stuff, and for their enthusiasm for my work on Sobot Rimulator.

Related: OpenCV Tutorial: Real-time Object Detection Using MSER in iOS