Хабрахабр

Катаемся на Xiaomi Vacuum Cleaner

Вот и пришли новогодние праздники, а с ними и куча свободного времени, да еще и умный пылесос угодил ко мне в руки. Как только я увидел в приложении MiHome ручное управление, я сразу понял, что хочу сделать: будем управлять пылесосом с помощью геймпада Dualshock v4!

Шаг 1, тащим токен, прошиваем (опционально)

Пылесос отчаянно отказывался обновляться, после нескольких часов гугления я попробовал посмотреть отладочный вывод mirobo и о чудо! Ставим пропатченное приложение MiHome, которое будет показывать нам токен, далее выбираем рутованную прошивку, скачиваем, ставим python-miio (pip install python-miio), пробуем установить прошивку с помощью mirobo --ip %ip% --token %token% update-firmware %filename% и на этом моменте у меня все сломалось. Далее я просто поднял файловый сервер и выполнил эту команду: mirobo --ip=%ip% --token=%token% raw-command miIO.ota ''. Из-за того, что у меня на ноутбуке установлено несколько адаптеров, он пытался раздать прошивку в сети адаптера VirtualBox Host-Only. Прошивка встала где-то за 10 минут, доступ по ssh работал

Шаг 2, пытаемся покататься на роботе

import miio
ip = ''
token = ''
bot = miio.vacuum.Vacuum(ip, token)
bot.manual_start()
bot.manual_control(0, 0.3, 2000) # move forward with max speed for 2 seconds
bot.manual_control(90, 0, 1000) # rotate
bot.manual_stop()

На этом этапе пылесос должен сказать Using remote controls (или что-то подобное в зависимости от прошивки), подергаться и остановиться

Шаг 3, подключаем Dualshock

После небольшого исследования было решено использовать pygame
Смотрим, какие кнопки/стикеры за что отвечают


BUTTON_SQUARE = 0
BUTTON_X = 1
BUTTON_CIRCLE = 2
BUTTON_TRIANGLE = 3 def init_joystick(): pygame.init() pygame.joystick.init() controller = pygame.joystick.Joystick(0) controller.init() return controller def main(): controller = init_joystick() bot = miio.vacuum.Vacuum(ip, token) modes = ['manual', 'home', 'spot', 'cleaning', 'unk'] mode = 'unk' axis = [0.00 for _ in range(6)] flag = True button = [False for _ in range(14)] print('Press start to start!') while flag: for event in pygame.event.get(): if event.type == pygame.JOYAXISMOTION: axis[event.axis] = round(event.value,2) elif event.type == pygame.JOYBUTTONDOWN: button[event.button] = True # Touchpad to exit if event.button == 13: flag = False elif event.type == pygame.JOYBUTTONUP: if mode == 'unk': print('Ready to go! Press X to start manual mode') if event.button == BUTTON_X: mode = 'manual' bot.manual_start() elif mode == 'manual': if event.button == BUTTON_TRIANGLE: bot.manual_stop() mode = 'unk' elif event.button == BUTTON_X: play_sound('http://192.168.1.43:8080/dejavu.mp3') # see ya later elif event.button == BUTTON_CIRCLE: # stop sound play_sound(';') if mode == 'manual': try: move_robot(bot, button, axis) # see ya in the next step except: bot.manual_start() pass time.sleep(0.01)

Пока в move_robot можно сделать просто print(axis) и проверить, что джойстик работает.
Далее нам нужно сделать так, чтобы робот ездил при нажатии на кнопки/стики, я выбрал левый стик по оси Y (вверх -1, вниз 1) для скорости и правый стик по оси X для угла, получилась примерно такая функция


def translate(value, leftMin, leftMax, rightMin, rightMax): leftSpan = leftMax - leftMin rightSpan = rightMax - rightMin valueScaled = float(value - leftMin) / float(leftSpan) return rightMin + (valueScaled * rightSpan) def move_robot(bot, buttons, axis): rot = 0 val = 0 to_min, to_max = -0.3, 0.3 # Right stick X if axis[2] != 0: rot = -translate(axis[2], -1, 1, -90, 90) if abs(rot) < 8: rot = 0 # Left stick Y, -1 up, 1 down if axis[1] != 0: val = -translate(axis[1], -1, 1, to_min, to_max) if abs(val) < 0.07: val = 0 if rot or val: bot.manual_control(rot, val, 150)

29, 0. Запускаем скрипт, жмем Х на контроллере и робот должен ездить и поворачивать
На этом этапе у меня возникла проблема: почему-то если нажать левый стик вперед до конца и попытаться повернуть, он не будет поворачивать, придется сначала сбросить скорость, если попытаться уменьшить значения маппинга, например поставить -0. 29, он начнет ездить по кругу, пока не изменится положение левого стикера, я так и не разобрался, в чем тут проблема

Шаг 4, добавим музыки

Заходим по ssh на нашего робота и смотрим, какие скриптовые языки тут есть.
Питона не было, а устанавливать его я смысла не видел, зато нашел перл, для нашей небольшой задачки подойдет
Далее устанавливаем sox:

sudo apt-get install sox, libsox-fmt-mp3

и пишем небольшой сервер на перле:

#!/usr/bin/perl use IO::Socket::INET; $| = 1; my $socket = new IO::Socket::INET ( LocalHost => '0.0.0.0', LocalPort => '7777', Proto => 'tcp', Listen => 2, Reuse => 1
); die "cannot create socket $!\n" unless $socket;
print "server waiting for client connection on port 7777\n"; while(1)
{ my $client_socket = $socket->accept(); my $client_address = $client_socket->peerhost(); my $client_port = $client_socket->peerport(); print "connection from $client_address:$client_port\n"; my $data = ""; $client_socket->recv($data, 256); print "received data: $data\n"; my @urls = split /;/, $data; system("killall play > /dev/null"); $data = "ok"; $client_socket->send($data); shutdown($client_socket, 1); if ( $urls[0] ne "") { system("play -q -v 0.4 " . $urls[0] . " &"); }
} $socket->close();

sudo perl sound_server.pl

у себя в консольке делаем что-то вроде

import socket
ip = ''
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ip, 7777))
s.sendall(b'http://%local_ip%:%local_port%/test.mp3;')
s.close()

И через пылесос должен заиграть наш test.mp3 (соответственно, нужно поднять файловый сервер на нашей локальной машине)
Наша функция play_sound() будет делать практически то же самое, только будет sendall(url+';'), url — аргумент функции

Результат

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»