Сижу я дома, никого не трогаю, передо мной лежит свеженькая arduino. Что делать? Надо к ней что-то присобачить!
Немного поразмыслив, поняв, что у меня нет ИК-фотодиода под рукой, вспомнил, что у меня есть лишние геймпады от денди.
Хорошо. Посматриваем мануальчики к ардуино, и, параллельно ищем спеки к геймпадам. Быстро узнаём, что это дело питается от +5В, и уровни у него - 0В и +5В. Это очень хорошо, ибо у ардуинки - так же, и не надо ничего ставить дополнительно.
Довольно быстро удаётся найти следующий ман:http://bit16.ru/index.php?gl=jpad&act=1, откуда мы берём распиновочку.
Распиновочка есть, а вот спеки найти было сложнее. Но, через полчаса где-то , находим следующий ман: http://www.parallax.com/Portals/0/Downloads/docs/prod/acc/32368-NesGamepadCtrlrAdapter-v1.0.pdf
Этот ман описывает контроллер для подключения двух геймпадов NES. Нам это, конечно, нафиг не надо, но там есть этот прекрасный параграф:
Возможно, это будет читать кто-то, кто не знает инглиш, так что расскажу, как это работает. Заодно, добавлю больше букв в бложек. Когда геймпад принимает импульс на LATCH, он "запоминает" комбинацию нажатых клавиш, и шлёт нам бит, показывающий, нажата ли кнопка "A", или нет. Затем, мы шлём семь импульсов на CLK и, соответсвенно получаем ещё семь бит, для семи оставшихся кнопок. В общем-то, всё крайне понятно при первом взгляде на график.
Всё подцепил. Написал простейшую программку. Не работает. Полчаса мучений. Потом понимаю, что подрубил нерабочий геймпад. Подрубаю нужный - всё ок!
Моя программка для arduino
#define CLKDELAY 1
#define FREQ 30
int data = 13;
int clock = 12;
int latch = 11;
//выбираем пины, куда втыкать наши проводки
int code;
int result;
boolean a;
boolean b;
boolean ta;
boolean tb;
boolean select;
boolean start;
boolean up;
boolean down;
boolean left;
boolean right;
void setup() {
//выполняется один раз.
pinMode(data, INPUT);//включаем пин данных на вход
pinMode(clock, OUTPUT);//на эти пины мы шлём импульсы,
pinMode(latch, OUTPUT);//так что это - выходы
digitalWrite(latch, LOW);//для начала на них
digitalWrite(clock, LOW);//пустим низкий уровень
Serial.begin(9600);//частота, на которой работает USB. Нам пока хватит.
}
void loop() {
//шлём импульс на LATCH, снимаем A
digitalWrite(latch, HIGH);
delay(CLKDELAY);
a = digitalRead(data);
digitalWrite(latch, LOW);
//Дальше шлём импульсы на CLK и снимаем остальные данные
//1
delay(CLKDELAY);
digitalWrite(clock, HIGH);
delay(CLKDELAY);
b = digitalRead(data);
digitalWrite(clock, LOW);
delay(CLKDELAY);
//2
digitalWrite(clock, HIGH);
delay(CLKDELAY);
select = digitalRead(data);
digitalWrite(clock, LOW);
//3
delay(CLKDELAY);
digitalWrite(clock, HIGH);
start = digitalRead(data);
delay(CLKDELAY);
digitalWrite(clock, LOW);
//4
delay(CLKDELAY);
digitalWrite(clock, HIGH);
delay(CLKDELAY);
up = digitalRead(data);
digitalWrite(clock, LOW);
//5
delay(CLKDELAY);
digitalWrite(clock, HIGH);
delay(CLKDELAY);
down = digitalRead(data);
digitalWrite(clock, LOW);
//6
delay(CLKDELAY);
digitalWrite(clock, HIGH);
delay(CLKDELAY);
left = digitalRead(data);
digitalWrite(clock, LOW);
//7
delay(CLKDELAY);
digitalWrite(clock, HIGH);
delay(CLKDELAY);
right = digitalRead(data);
digitalWrite(clock, LOW);
delay(FREQ); //задержка между опросами
result = 0;
result << 7; //на будущее
if (!a) result++; result=result<<1;
if (!b) result++; result=result<<1;
if(!select) result++; result=result<<1;
if(!start) result++; result=result<<1;
if(!up) result++; result=result<<1;
if(!down) result++; result=result<<1;
if(!left) result++; result=result<<1;
if(!right) result++;
Serial.println(result, DEC);//ебашим результат в универсальный последовательный автобус
//В десятичной системе - потому, что нам - похуй. Потом, когда посмотрете код на bash, поймёте.
}
Пара слов о программке. Она выводит 16битное число, где первые 7 бит - нули (потом собираюсь к ардуинке ещё много чего присобачить, так что с запасом). Затем идут последовательно биты для каждого пина. Так как выход с геймпада - инверсный, добавляем инверсию в if. Ну а так, всё понятно любому школьнику.
Итак, на тот момент я мог сделать cat /dev/ttyACM0 и увидеть кодыА теперь начинается самое ужасное! Один день я истратил на гугление, как же получить данные из последовательного устройства. На следующий день, решил спросить на.. лоре. И правильно сделал! Мне подсказали реально рабочий способ. Выглядит он вот так вот:
while read value do #Чозахочешь done < deviceНу либо пайпом.
Но... не рабоатет. Тот же няшный чувак с лора, предположил, что мой говнодевайс шлёт данные не в формате вроде 128\n, а в формате 128\r\n. Смотрим в hexdump - он прав!
Далее идут попытки заткнуть весь поток в dos2unix, в hd| xxd -r |dos2unix, у кого-то работает, у меня - нет. Пробовал я ненужные символы порезать сдвигами, но у нас ведь bash! Тут всё, не как у людей. Сдвиги тут работают только для чисел. Пробовал убрать последние символы стандартными средствами работы со строками - но и они нихрена не работают.
В конечном итоге решил попробовать отдельно каждую строку совать в dos2unix. И это - заработало. Ещё попробовал отдельно каждую строку совать в xxd -p - получаем hex вместе с символами перевода. Засунуть поток в xxd -p не вышло, ибо после второй строки подряд у меня получается ошибка xxd: cannot seek backwarkds. Так и не понял, к чему это. Если у вас - заработает, вы - молодец.
Вариант с xxd -p и последующим сравнением полученных кодов с кодами, которые мы запомнили работал субъективно быстрее (да, моё говно на bash жрёт ~2% моего ебаного проца), так что я оставил его.
Получается что-то вроде
while read number
do
hex=`echo $number | xxd -p`
case $hex in
320d0a) #влево
action;;
310d0a) #вправо
action;;
33320d0a) #вправо+select
action;;
33340d0a) #влево+select
action;;
esac
done < /dev/ttyACM0
Затем понимаем, что часть команд должна повторяться при длительном нажатии на кнопку, например, перемотка, или задержка субтитров, а часть - нет, например play/pause.Заводим переменную, отслеживающую, сколько опросов подряд кнопка остаётся нажатой.
Смотрим коды, сравниваем, выполняем нужные команды.
Ну, все пользователи exaile давно осилили qdbus, у mplayer'а - сложнее, но гуглится на раз (http://ubuntuforums.org/showthread.php?t=1629000).
mkfifo /tmp/mplayer-control mplayer -slave -input file=/tmp/mplayer-control /path/to/some/file/to/play
Затем в эту очередь кидаем команды, например,
echo "pause" > /tmp/mplayer-control echo "quit" > /tmp/mplayer-control
Список команд есть здесь: http://www.mplayerhq.hu/DOCS/tech/slave.txt
Также, неплохо бы управлять громкостью. Тут нам поможет вот этот ОТЛИЧНЫЙ СКРИПТ: http://pastebin.com/F1tM6R2J.
Небольшое лирическое отступление. Думаю, всем понятно, что если ничего не нажато, то делать всякие там сравнения нафиг не надо. Поэтому в начало цикла чтения надо добавить вот такое:
Ещё, у меня есть функция, которая смотрит, запущен ли mplayer, чтобы понять, управлять им, или exaile. Приводить отдельно не буду, потом увидите.
Если ардуинка пропала - ждём секунду, и снова проверяем, есть ли она. Ну, а так, вроде всё.Также, неплохо бы управлять громкостью. Тут нам поможет вот этот ОТЛИЧНЫЙ СКРИПТ: http://pastebin.com/F1tM6R2J.
Небольшое лирическое отступление. Думаю, всем понятно, что если ничего не нажато, то делать всякие там сравнения нафиг не надо. Поэтому в начало цикла чтения надо добавить вот такое:
if [ $hex = 300d0a ]
then prevkey=300d0a
continue
fi
Также я отметил, что при многократном включении/выключении arduino, /dev/ttyACM0 может остаться, а ардуинка получит себе /dev/ttyACM1. Так что я решил подстраховаться и добваить следующее: b=0
for ((a=5; a >=0 ; a--))
do
if [ -e /dev/ttyACM$a ]
then
b=$a
break
fi
done
И, соответственно, везде обращаемся к девайсу, как /dev/ttyACM$bЕщё, у меня есть функция, которая смотрит, запущен ли mplayer, чтобы понять, управлять им, или exaile. Приводить отдельно не буду, потом увидите.
Конечный скрипт - держите.
#!/bin/bash
mkfifo /tmp/mplayer-control
whatruns() #Производит опрос, какие из нужных нам программ, запущены.
#Делаю это, ибо хочу управлять разными программами, с помощью одинаковых комбинаций клавиш
#Пока тут только mplayer - exaile у меня запущен всегда.
{
if ps ax |grep mplayer |grep -q slave ;
then mplayer=1
else mplayer=0
fi
}
while true
do
b=0
for ((a=5; a >=0 ; a--))
do
if [ -e /dev/ttyACM$a ]
then
b=$a
break
fi
done
while [ -e /dev/ttyACM$b ]
#работаем, пока есть геймпад
do
whatruns #перед работой проверили, что запущено
readcount=0 #Отсчитывает количество опросов геймпада
readspressed=0 #Отсчитывает, сколько опросв подряд комбинация остаётся нажатой
prevkey=0 #Код предыдущей комбинации
while read number
do
let "readcount+=1"
if [ $readcount -ge 50 ] #Каждые 20 опросов
then
whatruns #проверяем, что запущено
readcount=0
fi
hex=`echo $number | xxd -p`
if [ $hex = 300d0a ]
then prevkey=300d0a
continue
fi
if [[ $hex = $prevkey ]]
then let "readspressed+=1"
else
readspressed=0
fi
prevkey=$hex
#Для тех команд, которые работают один раз при длительном нажатии
if [ "$readspressed" -eq 0 ]
then
case $hex in
31360d0a) #start
if [ $mplayer -eq 1 ]
then echo "pause" > /tmp/mplayer-control
else qdbus org.exaile.Exaile /org/exaile/Exaile org.exaile.Exaile.PlayPause
fi ;;
320d0a) #влево
if [ $mplayer -eq 0 ]
then qdbus org.exaile.Exaile /org/exaile/Exaile org.exaile.Exaile.Prev
fi ;;
310d0a) #вправо
if [ $mplayer -eq 0 ]
then qdbus org.exaile.Exaile /org/exaile/Exaile org.exaile.Exaile.Next
fi ;;
esac
fi
#для команд, которые будт срабатывать каждые десять опросов
if (( $readspressed != 0 || `expr $readspressed % 10` == 9 ))
then
case $hex in
320d0a) #влево
if [ $mplayer -eq 1 ]
then echo "seek -5" > /tmp/mplayer-control
fi ;;
310d0a) #вправо
if [ $mplayer -eq 1 ]
then echo "seek +5" > /tmp/mplayer-control
fi ;;
33320d0a) #вправо+select
if [ $mplayer -eq 1]
then echo "sub_delay +0.1" > /tmp/mplayer-control
else
r=`qdbus org.exaile.Exaile /Player org.freedesktop.MediaPlayer.PositionGet`
let "r+=5000"
qdbus org.exaile.Exaile /Player org.freedesktop.MediaPlayer.PositionSet $r
fi ;;
33340d0a) #влево+select
if [ $mplayer -eq 1 ]
then echo "sub_delay -0.1" > /tmp/mplayer-control
else
r=`qdbus org.exaile.Exaile /Player org.freedesktop.MediaPlayer.PositionGet`
let "r-=5000"
qdbus org.exaile.Exaile /Player org.freedesktop.MediaPlayer.PositionSet $r
fi ;;
380d0a) #вверх
volume up;;
340d0a) #вниз
volume down ;;
esac
fi
done < /dev/ttyACM$b
done
sleep 1
done
P.S. Прошу прощения за такую ебаную тему подсветки кода - включил дефолт на скорую руку. Как-нибудь исправлю.