Pull to refresh

Cubieboard A10. Учимся управлять системой с пульта дистанционного управления

Reading time 7 min
Views 27K
Здравствуй, Хабр!


Решил написать статью о своем опыте работы с IR на Cubieboard.

Не так давно заказал себе это замечательное устройство (заказывал специально для будущего домашнего сервера, и, следовательно, использовать на ней только серверную ОCь) Выбор пал на Cubian (http://cubian.org/) в виду того, что строить свой велосипед пока не хочется.

Предполагалось развернуть на кубике nginx, php5, samba, mocd и несколько своих сервисов для управления блоком реле.
В ходе изучения кубика решил попробовать настроить IR на прием команд с пульта от старого TV тюнера, решение в виде lirc развернуть не получилось, стал искать другие решения, ничего доброго не нашел.

Написал свое решение, которым теперь делюсь с вами.

Изначально хотелось обойтись только bash, но перечитывать раз в секунду или чаще/реже устройство меня не привлекало, зачем в пустую гонять процессор, лишний раз нагревая эту малышку. Тогда решение пришло в сторону php, так как он все равно будет установлен.

Недавно на хабре наткнулся на статью, мое решение близко к предложенному @ntfs1984, но не такое же.
Так как комментировать мне пока не позволяется, решил поиграть в песочнице.

Приступим.
Изначально, после установки системы, устройство не доступно, так как не загружены соответствующие модули, в случае cubieboard a10 и cubian — это модуль sunxi_ir
Проверяем его загрузку командой lsmod

Обращаю ваше внимание, все команды я выполняю от root'а.
По умолчанию система этот модуль не загружает, для загрузки вручную можно воспользоваться командой:
modprobe sunxi_ir

В случае успешного запуска модуля, modprobe ничего не скажет, а вот в списке lsmod появится заветная строчка. Также важным фактором, чтобы удостовериться, что все заработало, нужно проверить, появилось ли устройство ввода:

в моем случае это было event1
самый верный способ проверить, то ли это устройство, это выполнить
cat /dev/input/event1 | hexdump
(Остановить процесс можно [Ctrl]+[C])
hexdump выводит в «красивом» формате данные, плюс узнать код нажатой кнопки можем таким же образом, но можно обойтись и без него.

На каждую нажатую кнопку получаем четыре строки, первая пара строк — кнопка на пульте была нажата(0090 0001), вторая — кнопку отпустили(0090 0000). Из этих строк можно получить много полезного, я сильно не углублялся, а лишь обращал внимание на код кнопки (в примере это 0090). Но могу сказать, что 532a -это код пульта, если кто решит привязать программу и к пульту.
Если все так и есть, переходим к следующему этапу — настройка автозагрузки
nano /etc/modules

и в конце добавить лишь одну строку
sunxi_ir
это имя нашего модуля, который мы загружали выше. Пример моего modules:

Сохранили и вышли.

Итак, приготовления работы с IR окончены, теперь перейдем к самой важной части, это написание программы, которая сможет читать устройство и выполнять необходимые действия.
Нужен демон, который будет следить за нажатиями, изначально планировалось следить как-то за устройством, а в случае нажатой кнопки — выполнять. Сразу же скажу, не хотелось создавать бесконечный цикл, который бы проверял через секунду, нажата ли кнопка на пульте или нет. Хотелось создать такую программу, которая бы срабатывала только по нажатию, в остальное время просто дремала. В результате родилась идея работы php с файлами, когда можно вычитывать побитно файл:
#!/usr/bin/php5
<?php
$dev="/dev/input/event1";
$f=fopen($dev, 'rb');
if($f){
	while (!feof($f)){
		$b=fread($f,32);  //каждая кнопка формирует данные по 64 бита, 32-нажата кнопка, еще 32 - отпустили
		$d=bin2hex($b);	//получили hex строку
		$d=substr($d,18,8);	//код кнопки пульта и её статус
		$button=substr($d,0,4); //кнопка
		$key=substr($d,4);	 //0001 или 0000
		
		switch($button){
			case "0090": echo "POWER ".($key=="0001"?"down":"up")."\n";
		}
		//Можно и так:
		/*if($key=="0001"){
			if($button=="0090"){ 
				//выполнить код по нажатию кнопки на пульте
			}
		}else{
			if($button=="0090"){ 
				//выполнить код когда кнопку на пульте отпустили
			}
		}
		*/
	}
}
?>

Сохраним файл как /tmp/tst
Позволим файлу быть исполняемым
chmod +x /tmp/ts
Запускаем, проверяем:

Теперь, усложняя switch для каждой кнопки, получим обработку каждой кнопки пульта. Ну и, собственно, не выводить на экран «пустые» слова, а назначить необходимое действие.
Немного усложнив программу, получим (/usr/sbin/mylirc):
#!/usr/bin/php5
<?php
	function AccessFile($file,$access){
		if(file_exists($file)){ //файл существует!
			if($f=@fopen($file,$access)){fclose($f);}else{exit("not have access to $file\n");}
		}else{
			return "$file not found (may not have access to the folder)\n";
		}
	}
	$dev="";
	$log=0;
	if(count($argv)==1){
		exit("Use ".dirname( __FILE__ )."/".basename($_SERVER['SCRIPT_FILENAME'])." --device=device [--log=logfile]\n");
	}else{
		for($i=1;$i<count($argv);$i++){
			$cmd=explode("=",$argv[$i]);
			switch($cmd[0]){
				case "--device": $dev=$cmd[1]; echo AccessFile($cmd[1],"rb"); break;
				case "--log":    $log=$cmd[1]; echo AccessFile($cmd[1],"w"); break;
			}
		}
	}
	if($dev==""){
		exit("Use ".dirname( __FILE__ )."/".basename($_SERVER['SCRIPT_FILENAME'])." --device=device [--log=logfile]\n");
	}
	include("/etc/mylirc/buttom_avermedia.php");
	if($log){exec("echo `date` > $log");}
	$f=fopen($dev, 'rb');
	if($f){
		while (!feof($f)){
			$b=fread($f,32);
 			$d=bin2hex($b);
			
			$d=substr($d,18,8);
			$button	=substr($d,0,4);
			$key	=substr($d,4);
			$l=0;
			for($i=0;$i<count($command);$i++){
				if(trim($button)==trim($command[$i][1]) && trim($key)==trim($command[$i][2])){
					$l=1;
					if(trim($command[$i][3])!=""){ //Назначили клавишу
						exec($command[$i][3]." > /dev/null 2>/dev/null &");
					}else{ //Если не назначили, в логах можно будет узнать:
						if($log){exec("echo ".$command[$i][0]." ".$command[$i][1]." cmd=null >> $log");}//логи
					}
				}
			}
			if(!$l){ //Если клавиша нажата, но в файле настроек не прописана:
				if($log){exec("echo not buttom $button $key >> $log");}//логи
			}
		}
	}
?>

Программа пишет логи, если был передан соответствующий параметр(--log) и ругается, если не передан параметр --device
Файл настроек (/etc/mylirc/buttom_avermedia.php):
<?php
	$command=array();
	//формат:
	//название, код, ключ, команда
	$command[]=array("POWER", "00ff","0001","led green 1");
	$command[]=array("POWER", "00ff","0000","");
	$command[]=array("NUMB 1", "0005","0001","led green 0");
	$command[]=array("NUMB 1", "0005","0000","");
	//и так далее
?>

Программа led green 1(0) (это мой скрипт на bash'е, который поместил в /usr/sbin/)
включала и выключала на кубике лампочку (в примере зеленая), но можно выполнять любую консольную команду, какую вы пожелаете. Выполнение идет от root'а, соответственно это нужно будет учитывать и понижать привилегии до нужного пользователя при необходимости.
Программу нужно запускать при старте системы в виде демона, чтобы можно было запустить/остановить/перезапустить/проверить работу программы(/usr/sbin/mylirc), за основу которого был взят /etc/init.d/ssh скрипт, переписан и сохранен как /etc/init.d/01_lirc:

#!/bin/sh
### BEGIN INIT INFO
# Provides:             01_lirc
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Default-Start:        2 3 4 5
# Default-Stop:         
# Short-Description:    LIRC on PHP server
### END INIT INFO
set -e
# /etc/init.d/01_lirc: start and stop the "LIRC on PHP" daemon
test -x /usr/sbin/mylirc || exit 0
( /usr/sbin/mylirc -\? 2>&1 | grep -q mylirc ) 2>/dev/null || exit 0
umask 022
. /lib/lsb/init-functions
run_by_init() {
    ([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ]
}
export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
case "$1" in
  start)
        log_daemon_msg "Starting LIRC on PHP deamon" "mylirc" || true
        if start-stop-daemon --quiet --oknodo --exec /usr/sbin/mylirc  --background --start -- --device=/dev/input/event1  --log=/var/log/log_lirc; then
            log_end_msg 0 || true
        else
            log_end_msg 1 || true
        fi
        ;;
  stop)
        log_daemon_msg "Stopping LIRC on PHP deamon" "mylirc" || true
        if start-stop-daemon --stop --oknodo --name mylirc --retry=KILL/1; then
            log_end_msg 0 || true
        else
            log_end_msg 1 || true
        fi
        ;;
  restart)
        log_daemon_msg "Restarting LIRC on PHP deamon" "mylirc" || true
        start-stop-daemon --stop --oknodo --name mylirc --retry=KILL/1;
        if start-stop-daemon --quiet --oknodo --exec /usr/sbin/mylirc  --background --start -- --device=/dev/input/event1  --log=/var/log/log_lirc; then
            log_end_msg 0 || true
        else
            log_end_msg 1 || true
        fi
        ;;
  status)
        ID=`(lsof | grep mylirc | grep /dev/ 2>&1) 1>/dev/null || echo 0;`
        if [ "$ID" = "0" ]; then
                echo "LIRC on PHP deamon \033[37;1;41m shutdown \033[0m";
        else
                echo "LIRC on PHP deamon \033[37;1;42m started \033[0m";
        fi
        tput sgr0
        ;;
  *)
        log_action_msg "Usage: /etc/init.d/mylirc {start|stop|restart|status}" || true
        exit 1
esac
exit 0


Теперь проверить работу демона можно командами, запуск:
/etc/init.d/01_lirc start
Остановка:
/etc/init.d/01_lirc stop
Статус:
/etc/init.d/01_lirc status

и аналогично restart
Для добавление в автозагрузку демона необходимо выполнить:
cd /etc/init.d/ 
update-rc.d 01_lirc defaults

Убрать из автозагрузки:
cd /etc/init.d/
update-rc.d 01_lirc remove


P.S. Программу откатал, уже две недели работает стабильно. Пожелания и комментарии приветствуются.

P.S.S.
edwardoid предложил составить скрипт, который бы генерировал файл настроек (похожий на /etc/mylirc/buttom_avermedia.php ),
вот результат
#!/usr/bin/php5
<?php
	declare(ticks = 1);
	pcntl_signal(SIGINT,  "signal_handler");
	function signal_handler($signal){
		global $f,$out;
		if($signal==SIGINT){
			fclose($f); 
			exec('echo ?\> >> '.$out);
			exit("\n$out create!\n");
		}
	}
    function AccessFile($file,$access){
        if(file_exists($file)){ //файл существует!
            if($f=@fopen($file,$access)){fclose($f);}else{exit("not have access to $file\n");}
        }else{
            return "$file not found (may not have access to the folder)\n";
        }
    }
    $dev="";
    $out="";
    if(count($argv)==1){
        exit("Use ".dirname( __FILE__ )."/".basename($_SERVER['SCRIPT_FILENAME'])." --device=device --out=config.php\n");
    }else{
        for($i=1;$i<count($argv);$i++){
            $cmd=explode("=",$argv[$i]);
            switch($cmd[0]){
                case "--device": $dev=$cmd[1]; echo AccessFile($cmd[1],"rb"); break;
                case "--out":    $out=$cmd[1]; echo AccessFile($cmd[1],"w"); break;
            }
        }
    }
    if(($dev=="")||($out=="")){
        exit("Use ".dirname( __FILE__ )."/".basename($_SERVER['SCRIPT_FILENAME'])." --device=device --out=config.php\n");
    }
	echo "press [Ctrl]+[C] to stop and press any button on the remote\n";
    $f=fopen($dev, 'rb');
    if($f){
	exec('echo \<?php > '.$out);
	exec('echo "	\$command=array();" >> '.$out);
	$i=0;
	$j=1;
        while (!feof($f)){
            $b=fread($f,32);
			$b=bin2hex($b);
            $b=substr($b,18,8);
            $button	=substr($b,0,4);
            $key	=substr($b,4);
            $i=$i+($j++)%2; //Нумерация кнопок
			$but='$command[]=array("'.$i.'", "'.$button.'","'.$key.'",""); // #'.$i;
			exec('echo \'	'.$but.'\' >> '.$out);
			echo $but."\n"; //Выводим и на экран, чтобы понимать, что кнопки нажимаются и все работает
        }
    }
?>

Спасибо за идею!
Tags:
Hubs:
+22
Comments 17
Comments Comments 17

Articles