樹莓派與紅外線遙控器

利用樹莓派接收紅外線訊息、發射紅外線訊息是相當常用的遠端控制技巧

一、利用remote-ssh extension打造遠端開發環境

先前有介紹使用VS Code遠端開發Django,當時使用的方法有點土法練鋼,現在微軟提供非常簡易的remote-ssh擴充套件,可以讓VS Code很方便的與遠端樹莓派整合在一起,利用前端電腦的強大處理能力去協同開發後端樹莓派的Python應用。

在介紹紅外線的接收與發射前,讓我們先來打造一個Python遠端開發的樹莓派環境。

二、打開支援核心

現今的Linux核心已可直接支援紅外線遙控器的收發,只要將它打開即可,非常方便使用。

三、萬碼奔騰,自製編碼對應表

紅外線使用的編碼表按鍵名稱列表:https://peppe8o.com/download/txt/ir-keytable%20available%20keycodes.txt

由於遙控器的廠牌太多種類,甚至是同廠牌也有不同型號的遙控器,其編碼也不一定會完全一致,因此,學習自製編碼對應表,是常遇到的課題。

四、控制VLC播放音樂及LED閃爍

有了前面的基礎後,就可以利用python-evdev模組,來讀取遙控器資料並判斷是哪個按鈕被按下,進行相對應的程式處理。同時,由於VLC於背景播放,可使用psutil模組,來處理結束播放事宜。這二個模組功能是相當強大的,值得好好研究與學習。官網說明文件如下:

# ir_read_nomessage.py
from evdev import InputDevice, list_devices, categorize, ecodes

def get_ir_device():
    devices = [InputDevice(path) for path in list_devices()]
    for device in devices:
        print(device.path, device.name, device.phys)
        if 'ir_recv' in device.name:
            return device
        else:
            device.close()

dev = get_ir_device()
try:
    for event in dev.read_loop():
        if event.type == ecodes.EV_KEY:
            if event.value == 1:
                if ecodes.KEY[event.code] == 'KEY_1':
                    print('press 1')
                elif ecodes.KEY[event.code] == 'KEY_2':
                    print('press 2')
                elif ecodes.KEY[event.code] == 'KEY_3':
                    print('press 3')
                    dev.close()
                    quit()
except KeyboardInterrupt:
    print('press ctrl-c')
    dev.close()
    quit()
# vlc_id.py
import psutil
# 方法一:先取得所有的process id (PID),再找出vlc的PID,找到就kill離開
# process_ids = psutil.pids()
# for pid in process_ids:
#     p = psutil.Process(pid)
#     if 'vlc' in p.name():
#         print(p.name())
#         print(p.pid)
#         p.kill()
#         break

# 方法二:透過iter的方式,一個個找,找到就kill離開
for proc in psutil.process_iter(['pid', 'name']):
    if 'vlc' in proc.info['name']:
        print(proc.info['pid'])
        p = psutil.Process(proc.info['pid'])
        p.kill()
        break
from evdev import InputDevice, list_devices, categorize, ecodes
from gpiozero import LED
import subprocess
import psutil
led = LED(27)
led_on = False
vlc_on = False

def get_ir_device():
    devices = [InputDevice(path) for path in list_devices()]
    for device in devices:
        print(device.path, device.name, device.phys)
        if 'ir_recv' in device.name:
            return device
        else:
            device.close()

def stop_vlc():
    for proc in psutil.process_iter(['pid', 'name']):
        if 'vlc' in proc.info['name']:
            print(proc.info['pid'])
            p = psutil.Process(proc.info['pid'])
            p.kill()
            return

dev = get_ir_device()
try:
    for event in dev.read_loop():
        if event.type == ecodes.EV_KEY and event.value == 1:
            if ecodes.KEY[event.code] == 'KEY_1':
                # 控制VLC播放音樂
                if vlc_on:
                    # 利用PID停止播放
                    print('停止播放音樂')
                    stop_vlc()
                else:
                    print('開始播放音樂')
                    subprocess.run(["cvlc", "-LZ", "/home/pi/Music"])
                vlc_on = not vlc_on
            elif ecodes.KEY[event.code] == 'KEY_2':
                # 控制LED亮與暗
                if led_on:
                    led.off()
                else:
                    led.blink()
                led_on = not led_on
            elif ecodes.KEY[event.code] == 'KEY_3':
                print('離開程式')
                dev.close()
                quit()
except KeyboardInterrupt:
    print('按鍵Ctrl+c中斷程式')
    dev.close()
    quit()

五、發射紅外線訊號

先利用evtest來檢視鍵盤訊號值,再利用ir_ctl測試,依據scancode發射紅外線訊號給接收器,最後撰寫程式來完成工作。

from evdev import InputDevice, list_devices, categorize, ecodes
from configparser import ConfigParser
from collections import defaultdict
from time import sleep
import os

config = ConfigParser()

config.read(os.path.expanduser('~/lien.toml'))
# 列出所有的設定section
# print(keymap.sections())
scancodes = config['protocols.scancodes']
keymaps = defaultdict()
# 原先格式是
# [protocols.scancodes]
# 0x41ba09 = "KEY_1"
# 轉成 "KEY_1" = 0x41ba09
# 並且將 " 字符除去(利用replace),如此才可以使用keymaps['KEY_1']
# 如果不去除,就必須要用keymaps['"KEY_1"'],此用法容易混淆
for s in scancodes:
    keymaps[scancodes[s].replace('"','')] = s

dev = InputDevice('/dev/input/event0')
try:
    for event in dev.read_loop():
        if event.type == ecodes.EV_KEY and event.value == 1:
            if event.code == ecodes.KEY_A:
                print('按鍵 A')
                command = f"ir-ctl -S necx:{keymaps['KEY_1']} -d /dev/lirc0"
                os.system(command)
            elif event.code == ecodes.KEY_B:
                print('按鍵 B')
                command = f"ir-ctl -S necx:{keymaps['KEY_2']} -d /dev/lirc0"
                os.system(command)
            elif event.code == ecodes.KEY_C:
                print('結束程式')
                dev.close()
                break
except KeyboardInterrupt:
    dev.close()
    quit()

Last updated