Продолжение. Начало — Часть1 и Часть2
Приступим к написанию скриптов для ROS
1. Запуск узла преобразования речи в текст ardrone1_julius_talker и выдача результатов в тему ardrone1_textspeech
Создадим файл ros_ardrone1_julius_init.py
Скрипт запускает в цикле программу julius с выдачей результата преобразования скрипту ros_ardrone1_julius_to_text.py
Т.к. система должна понимать не только полные команды, но и укороченные, создадим несколько параметров (данные, хранящиеся на сервере ROS), где будем хранить текущие значения для текущего робота, текущего действия, текущего значения действия — ardrone1_name, ardrone1_do, ardrone1_digit.
Вот содержимое файла ros_ardrone1_julius_init.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # Запуск программы julius (распознование речи) # входящие данные - микрофон # перенаправление вывода результатов в скрипт ros_ardrone1_julius_to_text.py # import roslib; roslib.load_manifest('vp_ardrone1') import rospy from std_msgs.msg import String import os import subprocess def run(): rospy.set_param("ardrone1_name",1); rospy.set_param("ardrone1_do",[0,0]); rospy.set_param("ardrone1_digit",[[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]]); while not rospy.is_shutdown(): a = os.system('padsp julius -quiet -input mic -C julian.jconf 2>/dev/null | ./ros_ardrone1_julius_to_text.py') rospy.sleep(1.0) if __name__ == '__main__': try: run() except rospy.ROSInterruptException: pass except KeyboardInterrupt: sys.exit(1)
Результаты передаются скрипту ros_ardrone1_julius_to_text.py, который только создает узел и публикует значение sentence1 в тему ardrone1_textspeech
Вот вывод результата
Содержимое файла ros_ardrone1_julius_to_text.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # Узел ardrone1_julius_talker, получающий результат программы julius(распознование речи) из stdin # (скрипт ros_ardrone1_julius_init.py) и публикующий строку в тему ardrone1_textspeech # import roslib; roslib.load_manifest('vp_ardrone1') import rospy from std_msgs.msg import String import os import subprocess import sys def Control(file_object): pub = rospy.Publisher('ardrone1_textspeech', String) rospy.init_node('ardrone1_julius_talker') startstring = 'sentence1:' endstring = '' while not rospy.is_shutdown(): line = file_object.readline() if not line: break end = len(line)-6 if line[0:9] == 'sentence1': word = line[15:end] print word str = word rospy.loginfo(str) pub.publish(String(str)) rospy.sleep(0.001) if __name__ == '__main__': try: Control(sys.stdin) except rospy.ROSInterruptException: pass except KeyboardInterrupt: sys.exit(1)
2. Запуск слушателя (subscriber) ardrone1_textspeech, преобразование текста в цифровую команду и выдача результатов в тему ardrone1_command
Скрипт ros_ardrone1_text_to_command_julius.py создает промежуточный узел ros_ardrone1_sub_voice, который является слушателем (subscriber) темы ardrone_textspeech. Его задача:
получив текст, дополнить его до полной команды (из данных rosparam), например forward slow ->ardrone forward slow, seven -> ardrone dance seven
сформировать цифровую форму кманды, отсечь неверные команды, например ardrone hi slow — неверно, ardrone off seven — неверно
выдать цифровую форму команды в тему ardrone1_command
Все слова из словаря разбиты на 6 групп, каждая из которых имеет свой коэффициент k
«DRONE»,»ROBERT» — k=100 000
«HI»,»BUY»,»THANKS» — k=10 000
«ON»,»OFF»,»SLEEP»,»BASE» — k=1000
«FORWARD»,»BACK»,»LEFT»,»RIGHT»,»TURN»,»UP»,»DOWN»,»HANG»,»DANCE» — k=100
«ONE»,»TWO»,»THREE»,»FOUR»,»FIVE»,»SIX»,»SEVEN»,»EIGHT»,»NINE» — k=10
«FAST»,»SLOW» — k=1
Цифровая форма команды формируется так
count2=слово1*k*(индекс слова в группе+1)+слово2*k*(индекс слова в группе+1)+…..
Одновременно формируется и число
count1=слово1*k*1+слово2*k*1+…..
Например
DRONE THANKS RIGHT FAST
count2=100000*(0+1)+10000*(2+1)+100*(3+1)+1*(0+1)=130401
count1=100000*1+10000*1+100*1+1*1=110101
Список возможных комбинаций для count1 находится в массиве ok_fraza
ok_fraza=[
«110000»,»010000″,
«101000»,»001000″,
«100100»,»000100″,»100110″,»000110″
«100001»,»000001″,»100101″,»000101″]
Если count1 для нашей фразы присутствует в массиве ok_fraza, в тему ardrone1_command публикуем значение count2
Кроме этого в скрипте устанавливаются значения параметров (ardrone1_name, ardrone1_do, ardrone1_digit) на сервере параметров
Содержимое файла ros_ardrone1_text_to_command_julius.py #!/usr/bin/env python #-*-coding:utf-8 -*- # # Слушатель ros_ardrone1_sub_voice # чтение сообщений из темы ardrone1_textspeech (строка голосовой команды) # и отправка команд и установка параметров # для управления ardrone 2.0 # import roslib; roslib.load_manifest('vp_ardrone1') import rospy import subprocess import shlex from ardrone_autonomy.msg import * from ardrone_autonomy.srv import * from std_msgs.msg import String from std_msgs.msg import Int32 from std_msgs.msg import Empty from geometry_msgs.msg import Twist arr_commands=[["DRONE","ROBERT"], ["HI","BUY","THANKS"], ["ON","OFF","SLEEP","BASE"], ["FORWARD","BACK","LEFT","RIGHT","TURN","UP","DOWN","HANG","DANCE"], ["ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"], ["FAST","SLOW"]] digits=[100000,10000,1000,100,10,1] ok_fraza=[ "110000","010000", "101000","001000", "100100","000100","100110","000110" "100001","000001","100101","000101"] def controller(data): if(data.data>0): str = data.data rospy.loginfo(str) arr_str=str.split(); count1=0;count2=0; # разбор предложения #str1="";str2=""; for word in arr_str: for ind1,frazes in enumerate(arr_commands): for ind2,fraza in enumerate(frazes): if fraza==word: count1=count1+digits[ind1] count2=count2+digits[ind1]*(ind2+1) ## добавить из параметров умолчания # персонаж по умолчанию if(count2/100000<1): count2=rospy.get_param("ardrone1_name")*100000+count2; else: rospy.set_param("ardrone1_name",count2/100000); # действие по умолчанию if((count2%1000)/100<1): params2=rospy.get_param("ardrone1_do") count2=params2[rospy.get_param("ardrone1_name")-1]*100+count2 else: params2=rospy.get_param("ardrone1_do") params2[rospy.get_param("ardrone1_name")-1]=(count2%1000)/100 rospy.set_param("ardrone1_do",params2) params3=rospy.get_param("ardrone1_digit") params3[rospy.get_param("ardrone1_name")-1]=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] params3[rospy.get_param("ardrone1_name")-1][(count2%1000)/100-1]=0.1 rospy.set_param("ardrone1_digit",params3) if((count2%1000)/100==8): params3=rospy.get_param("ardrone1_digit") params3[rospy.get_param("ardrone1_name")-1]=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] rospy.set_param("ardrone1_digit",params3) # скорость текущая if((count2%100)/10>0): params3=rospy.get_param("ardrone1_do") tdo=params3[rospy.get_param("ardrone1_name")-1] params4=rospy.get_param("ardrone1_digit") params4[rospy.get_param("ardrone1_name")-1]=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] params4[rospy.get_param("ardrone1_name")-1][tdo-1]=((count2%100)/10*1.0)/10 rospy.set_param("ardrone1_digit",params4) if(tdo==8): params4=rospy.get_param("ardrone1_digit") params4[rospy.get_param("ardrone1_name")-1]=[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0] rospy.set_param("ardrone1_digit",params4); # быстрее, медленнее if((count2%10)==1): params3=rospy.get_param("ardrone1_do") tdo=params3[rospy.get_param("ardrone1_name")-1] params4=rospy.get_param("ardrone1_digit") tdigit=params4[rospy.get_param("ardrone1_name")-1][tdo-1] params4[rospy.get_param("ardrone1_name")-1][tdo-1]=min(1.0,tdigit*10.0/10+0.1) rospy.set_param("ardrone1_digit",params4) elif((count2%10)==2): params3=rospy.get_param("ardrone1_do") tdo=params3[rospy.get_param("ardrone1_name")-1] params4=rospy.get_param("ardrone1_digit") tdigit=params4[rospy.get_param("ardrone1_name")-1][tdo-1] params4[rospy.get_param("ardrone1_name")-1][tdo-1]=max(0.0,tdigit*10.0/10-0.1) rospy.set_param("ardrone1_digit",params4) # лог rospy.loginfo(String(count2)) # публиковать в тему ardrone1_move pub1=rospy.Publisher('ardrone1_command', Int32) pub1.publish(count2) #rospy.loginfo(str) def listener(): rospy.init_node('ros_ardrone1_sub_voice') sub = rospy.Subscriber("ardrone1_textspeech",String,controller) rospy.spin() if __name__ == '__main__': listener()
3. Преобразование цифровой формы команды из темы ardrone1_command в сообщения для слушателей и сервисов пакета ardrone_autonomy
Обычно для управления Ardrone посылаются единичные команды сервисам или в тему. Исключения составляют команды для управления движениям arDrone, для движения Ardron в каком-нибудь направлении необходимо посылать ПОСТОЯННО сообщения в тему cmd_vel. Поэтому я создал два узла-слушателя subscriber из темы ardrone1_command. Один отправляет единичные команды, другой при необходимости ПОСТОЯННЫЕ в тему cmd_vel.
Скрипт ros_ardrone1_command_to_move.py — для одиночных сообщений
#!/usr/bin/env python #-*-coding:utf-8 -*- # # Слушатель ros_ardrone1_command # чтение сообщений из темы ardrone1_command (строка голосовой команды) # и отправка команд и установка параметров # для управления ardrone 2.0 # или iRobotCreate import roslib; roslib.load_manifest('vp_ardrone1') import rospy import subprocess import shlex from ardrone_autonomy.msg import * from ardrone_autonomy.srv import * from std_msgs.msg import String from std_msgs.msg import Empty from std_msgs.msg import Int32 from geometry_msgs.msg import Twist def controller(data): if(data.data>0): command = data.data if(command/100000==1): # ArDrone 2.0 ### приветствие rospy.loginfo("ArDrone 2.0") if((command%100000)/10000==1): # привет - анимация rospy.loginfo("привет") serv1=rospy.ServiceProxy('ardrone/setledanimation', LedAnim) res1=serv1(1,4,3); rospy.loginfo(res1) # параметры elif((command%100000)/10000==2): # пока - анимация rospy.loginfo("пока") serv1=rospy.ServiceProxy('ardrone/setledanimation', LedAnim) res1=serv1(2,4,3); rospy.loginfo(res1) elif((command%100000)/10000==3): # молодец - анимация rospy.loginfo("молодец") serv1=rospy.ServiceProxy('ardrone/setledanimation', LedAnim) res1=serv1(3,4,5); rospy.loginfo(res1) ### взлет, посадка elif((command%10000)/1000==1): # взлет rospy.loginfo("взлет") pub1=rospy.Publisher('ardrone/takeoff', Empty) pub1.publish() # параметры elif((command%10000)/1000==2): # посадка rospy.loginfo("посадка") pub2=rospy.Publisher('ardrone/land', Empty) pub2.publish() elif((command%10000)/1000==3): # заснуть, сброс rospy.loginfo("заснуть") pub2=rospy.Publisher('ardrone/reset', Empty) pub2.publish() ### dance анимация elif((command%1000)/100==9): # полетная анимация rospy.loginfo("flight анимации - па !!!!") serv1=rospy.ServiceProxy('ardrone/setflightanimation',FlightAnim) res1=serv1((command%100)/10,0); rospy.loginfo(res1) elif(command/100000==2): # iRobot rospy.loginfo("iRorot Robert") def listener(): rospy.init_node('ros_ardrone1_command') sub = rospy.Subscriber("ardrone1_command",Int32,controller) rospy.spin() if __name__ == '__main__': listener()
Скрипт ros_ardrone1_command_to_move_odom.py — для одиночных сообщений — проверяет состояние голосовой команда для ArDron (по ненулевым значениям ardrone_digit) и запускает посылку сообщений в тему cmd_vel. При выдаче новой голосовой команды изменяются значения параметра ardrone1_digit, и при переходе их в нулевые — посылка команд в тему cmd_vel прекращается.
Скрипт ros_ardrone1_command_to_move_odom.py
#!/usr/bin/env python #-*-coding:utf-8 -*- # # Слушатель ros_ardrone1_command # чтение сообщений из темы ardrone1_command (строка голосовой команды) # и отправка команд в тему cmd_vel # для управления ardrone 2.0 # или iRobotCreate import roslib; roslib.load_manifest('vp_ardrone1') import rospy import subprocess import shlex from ardrone_autonomy.msg import * from ardrone_autonomy.srv import * from std_msgs.msg import String from std_msgs.msg import Empty from std_msgs.msg import Int32 from geometry_msgs.msg import Twist def controller(data): if(data.data>0): command = data.data ### вперед, назад, влево, вправо, поворот, вверх, вниз, медленнее, быстрее if(command/100000==1): # ArDrone 2.0 if((command%1000)/100>0): # go=True while go==True: params1=rospy.get_param("ardrone1_name") params2=rospy.get_param("ardrone1_do") params3=rospy.get_param("ardrone1_digit") fb=params3[params1-1][0]-params3[params1-1][1] rl=params3[params1-1][2]-params3 [params1-1][3] ud=params3[params1-1][4]-params3[params1-1][5] tt=params3[params1-1][6] if(abs(fb)>0.0 or abs(rl)>0.0 or abs(ud)>0.0 or abs(tt)>0.0): go=True else: go=False pub3=rospy.Publisher('cmd_vel', Twist) odom=Twist() odom.linear.x=fb odom.linear.y=rl odom.linear.z=ud odom.angular.x=0.0 odom.angular.y=0.0 odom.angular.z=tt pub3.publish(odom) rospy.loginfo("движение!!!!") rospy.loginfo("конец движения!!!!") elif(command/100000==2): # iRobot rospy.loginfo("iRorot Robert") def listener(): rospy.init_node('ros_ardrone1_command_odom') sub = rospy.Subscriber("ardrone1_command",Int32,controller) rospy.spin() if __name__ == '__main__': listener()
В проект включаем для подстраховки скрипты для управления квадрокоптера ArDrone с помощью джойстика,
$ rosparam set joy_node/dev "/dev/input/jsX0" $ rosrun joy joy_node $ rosrun vp_ardrone1 ros_ardrone1_joystick_to_move.py
где при нажатии кнопки RB6 происходит обнуление данных в параметре ardrone_digit и соответственно останов Ardron-а.
Теперь можно подключаться к Ardronб запускать драйвер ardrone_autonomy и проверять работу пакета.
Далее — Часть 4 — пошаговая инструкция по запуску системы и видео.