赤外線リモコン

目次Raspberry Piロボット|センサー(温度・気圧・湿度人感)|赤外線リモコン読み上げMQTT連携IFTTT連携

ここでは、赤外線人感センサを試してみます。感知情報をメールやLINEで通知する方法も紹介いたします。

赤外線受信・LEDモジュールの仕様

赤外線受信モジュール
(OSRB38C9AA)の仕様

赤外線LED
(OSI5LA5113A)の仕様

トランジスタ
(2sc1815L-GR-T92-K)の仕様

接続図

GPIO1ピンあたり10mA程度、合計の電流は30mA程度が目安になります。
そのためトランジスターで増幅します。
赤外線LEDの仕様で、VF=1.35v、if=50mmA(最大100mmA)なので
(5-1.35)/ 0.005 = 73Ωですが 手持ちの抵抗51Ωで対応しております。
この場合の電流は(5-1.35)/ 51 = 72mmA となります。
トタランジスターのベース側の抵抗は3kΩとしました。
2~6kΩでも問題なしです。

事前準備

OSのアップデート

OSをアップデートして、最新の状態にします。

$ sudo apt-get update		
$ sudo apt-get upgrade		

pugioのインストール

GPIO制御を精度の高いPWM(Pulse Width Modulation)制御が出来る「pigpio」パッケージをインストールします。

$ sudo apt-get install pigpio python3-pigpio	

pigpioのデーモンを起動します。

$ sudo systemctl enable pigpiod.service
$ sudo systemctl start pigpiod

pigpioのステータスは「sudo systemctl status pigpiod」コマンドで確認が出来ます。

赤外線リモコン操作プログラムのダウンロード

Indoor Corgiさんの赤外線制御ソフトウェア「cgir」をインストールします。ターミナルを開き、以下の$に続くコマンドを実行して下さい。
ソースコードはGitHubに公開されております。

$ sudo python3 -m pip install -U cgir

赤外線リモコンボタンの学習

一般家庭で利用しているシーリングライトのリモコンで試してみます。
中(全灯)、上(保安灯)、下(消灯)、右(明)、左(暗)のボタンがあります。

学習プログラムの実行

下記のコマンドでプログラムを起動して、リモコンボタン情報を学習(取得)します。

$ cgir rec light:on -g 26

オプション 意味は下記の通りです。

認識プログラムの実行

実行コマンドを入力して、リモコンの該当ボタンを押すと赤外線データが取得出来ます。以下はプログラムの応答例です。

pi@raspberrypi:~ $ cgir rec light:on -g 26
------------------------------------
赤外線コード"light:on"を受信中...  受信機に向けて赤外線を送信して下さい.

受信コード
[9000, 4400, 640, 480, 640, 1600, 630, 490, 640, 480, 650, 480, 640, 490, 640, 480, 650, 1600, 650, 1600, 650, 480, 640, 1600, 640, 1600, 650, 480, 640, 1600, 650, 1600, 650, 480, 640, 490, 640, 1600, 650, 1600, 640, 480, 640, 490, 640, 1600, 640, 490, 640, 1600, 640, 1600, 650, 490, 640, 490, 640, 1600, 640, 1600, 640, 490, 640, 1600, 650, 480, 640]

Format NEC
Frame#1 0x82, 0x6D, 0xA6, 0x59

赤外線コード "light:on" を登録しました.

学習での取得情報

取得情報は下記の通りです。

{"light:on": [9000, 4400, 640, 490, 640, 1600, 640, 500, 640, 490, 640, 490, 640, 490, 640, 490, 640, 1600, 640,
 1600, 640, 480, 640, 1600, 640, 1600, 640, 490, 640, 1600, 640, 1600, 640, 490, 640, 490, 630, 1600, 640,
 1600, 640, 490, 630, 490, 640, 1600, 640, 490, 640, 1600, 640, 1600, 640, 490, 640, 490, 640, 1600, 640,
 1600, 640, 490, 640, 1600, 630, 490, 640]}

この数字の並びは、「赤外線がONの時間, OFFの時間, ONの時間, OFFの時間,...」という意味です。
0,2,4,6,…偶数番目の数値が、38kHzの赤外線が存在する(High状態の)マイクロ秒時間 で
1,3,5,7,…奇数番目の数値が、赤外線が存在しない(Low状態の)マイクロ秒時間です。

学習情報をliht:on(全灯)、 light:off(消灯)、 light:up(明)、 light:dn(暗)、 light:sml(保安灯)で追加した情報は(codes.json)で示します。

赤外線リモコンボタンデータ送信

コマンドで送信

下記のコマンドでプログラムを起動して、学習(取得)したデータを使って赤外線データを送信します。

$ gir send light:on -g 13 -c xxx/codes.json

オプション 意味は下記の通りです。

送信しても機器が反応しない場合は以下の対応で確認してください。

学習機能で取得したデータで送信処理して、その送信データで学習データが取得出来るかを確認してください。
送信で使った学習データとその送信データで作成した学習データが一致していれば、正しく動作しております。
この状態で信号が認識出来ない場合は機器との距離や赤外線LEDの送信角度(半減角)を確認してください。
学習データが取得出来て、それぞれの学習データが一致していない場合は何かの不具合があると思われます。
今回は学習データを使って送信処理を行い、その送信データを学習した結果の値が2倍の内容であった為に
学習した送信データの値をプログラムで2分の1の値に変更して対応しました。不具合の詳細は不明です。
データ変更プログラム(cnv.py)を掲載しました。実行は「python cnv.py」のコマンドでできます。変更結果は( codes.json2)で示します。
※変更前ファイルcodes.jsonはcnv.pyプログラムと同じディレクトリに設置してください。変更後のファイルは、そのディレクトリにcodes2.jsonのファイル名で作成します。

pythonプログラムで送信

liht:on(全灯)、 light:off(消灯)、 light:up(明)、 light:dn(暗)、 light:sml(保安灯)を登録して、赤外線の自動送信を試して見ました。
学習したデータを指定の時間間隔で送信するプログラムです。赤外線送信の強さ(認識出来る距離)・送信方向の角度などが確認出来ます。
プログラム(rctl.py)は下記に示します。

import subprocess
import time

tbl =[["on",2],["dn",2],["dn",2],["dn",2],["dn",2],
     ["up",2],["up",2],["up",2],["up",2],
     ["off",4],["sml",2]]
while True:
  for i in range(len(tbl)) :
    subprocess.call(["cgir", "send", "light:"+tbl[i][0], "-g", "13"])
    time.sleep(tbl[i][1])

Webプラウザの操作で赤外線信号の送信(HTTPサーバを立てる)

Webブラウザをを使って、赤外線LEDを操作出来る様にします。その処理をするサーバを作ります。
Webブラウザより7000ポートを指定してアクセスすると、初期画面を表示して、
そのボタンをクリックすると指定した赤外線信号を出します。処理の概要は下記の通りです。

サンプルプログラム(server.py)は下記の通りです。用途に応じて変更してください。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import subprocess
import http.server as s
from urllib.parse import urlparse
from urllib.parse import parse_qs
RaspberrypiIP = '192.168.0.10'
class MyHandler(s.BaseHTTPRequestHandler):
    def do_GET(self):
        if str(self.path[1:]) != "favicon.ico" :
            self.make_data()
        else :
            self.send_response(404)
    def make_data(self):
        cmd = str(self.path[1:])
        if cmd != "" :
            subprocess.call(["cgir", "send", cmd, "-g", "13"])
            body = '<html lang="ja"><head><meta charset="UTF-8"><title></title><link rel="icon" href="data:,">'\
            '</head><body onload="history.back();"><h1>送信完了</h1></body><html>'
        else :
            body = '<html lang="ja"><head><meta charset="UTF-8"><title>リモコン</title><link rel="icon" href="data:,">'\
            '<meta name="viewport" content="width=device-width,initial-scale=1">'\
            '</head><body><h1>リモコン</h1>'\
            '<a href="http://'+RaspberrypiIP+':7000/light:on" ><button>loght:on </button></a> '\
            '<a href="http://'+RaspberrypiIP+':7000/light:off"><button>loght:off</button></a> '\
            '<a href="http://'+RaspberrypiIP+':7000/light:up" ><button>loght:up </button></a> '\
            '<a href="http://'+RaspberrypiIP+':7000/light:dn" ><button>loght:dn </button></a> '\
            '<a href="http://'+RaspberrypiIP+':7000/light:sml"><button>loght:sml</button></a> '\
            '</body></html>'
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.send_header('Content-length', len(body))
        self.end_headers()
        self.wfile.write(body.encode())

host = '0.0.0.0'
port = 7000
httpd = s.HTTPServer((host, port), MyHandler)
print('サーバを起動しました。ポート:%s' % port)
httpd.serve_forever()

プログラムの実行は「python3 server.py」コマンドで実行してください。実行完了後、Webプラウザから「RaspberryPiのIPアドレス:7000」でアクセスしてください。

赤外線リモコンボタンの認識

認識プログラムの準備

認識プログラムはpigpioの作者さんが公開している赤外線信号送受信用のPythonスクリプト(IR Record and Playback(irrp.py))をそのまま流用しております。
また【ラズパイ入門】赤外線リモコンの信号受信、押されたボタンの識別【Python】 のブログを参考にさせて頂きました。
プログラム(rc.py)は下記の通りです。

#!/usr/bin/env python

import time
import os
import json
import sys

#import RPi.GPIO as GPIO
import pigpio # http://abyz.co.uk/rpi/pigpio/python.html

IR_RX_PIN  = 26
GLITCH     = 100
PRE_MS     = 200
POST_MS    = 15
FREQ       = 38.0
SHORT      = 10
TOLERANCE  = 15

POST_US    = POST_MS * 1000
PRE_US     = PRE_MS  * 1000
TOLER_MIN =  (100 - TOLERANCE) / 100.0
TOLER_MAX =  (100 + TOLERANCE) / 100.0

last_tick = 0
in_code = False
code = []
fetching_code = False

def normalise(c):
   entries = len(c)
   p = [0]*entries # Set all entries not processed.
   for i in range(entries):
      if not p[i]: # Not processed?
         v = c[i]
         tot = v
         similar = 1.0

         # Find all pulses with similar lengths to the start pulse.
         for j in range(i+2, entries, 2):
            if not p[j]: # Unprocessed.
               if (c[j]*TOLER_MIN) < v < (c[j]*TOLER_MAX): # Similar.
                  tot = tot + c[j]
                  similar += 1.0

         # Calculate the average pulse length.
         newv = round(tot / similar, 2)
         c[i] = newv

         # Set all similar pulses to the average value.
         for j in range(i+2, entries, 2):
            if not p[j]: # Unprocessed.
               if (c[j]*TOLER_MIN) < v < (c[j]*TOLER_MAX): # Similar.
                  c[j] = newv
                  p[j] = 1

def compare(p1, p2):
   if len(p1) != len(p2):
      return False

   for i in range(len(p1)):
      v = p1[i] / p2[i]
      if (v < TOLER_MIN) or (v > TOLER_MAX):
         return False

   for i in range(len(p1)):
       p1[i] = int(round((p1[i]+p2[i])/2.0))

   return True


def end_of_code():
   global code, fetching_code
   if len(code) > SHORT:
      normalise(code)
      fetching_code = False
   else:
      code = []
      print("Short code, probably a repeat, try again")

def cbf(gpio, level, tick):

   global last_tick, in_code, code, fetching_code

   if level != pigpio.TIMEOUT:

      edge = pigpio.tickDiff(last_tick, tick)
      last_tick = tick

      if fetching_code:

         if (edge > PRE_US) and (not in_code): # Start of a code.
            in_code = True
            pi.set_watchdog(IR_RX_PIN, POST_MS) # Start watchdog.

         elif (edge > POST_US) and in_code: # End of a code.
            in_code = False
            pi.set_watchdog(IR_RX_PIN, 0) # Cancel watchdog.
            end_of_code()

         elif in_code:
            code.append(edge)

   else:
      pi.set_watchdog(IR_RX_PIN, 0) # Cancel watchdog.
      if in_code:
         in_code = False
         end_of_code()

pi = pigpio.pi() # Connect to Pi.

if not pi.connected:
   exit(0)
args = sys.argv
with open(args[1]) as f:
   key_config = json.load(f)

   pi.set_mode(IR_RX_PIN, pigpio.INPUT) # IR RX connected to this IR_RX_PIN.

   pi.set_glitch_filter(IR_RX_PIN, GLITCH) # Ignore glitches.

   cb = pi.callback(IR_RX_PIN, pigpio.EITHER_EDGE, cbf)

   try:  
      while True:
         code = []
         fetching_code = True
         while fetching_code:
            time.sleep(0.1)
         time.sleep(0.5)
         key_name = "-"
         for key, val in key_config.items():
            if compare(val, code[:]):
               key_name = key
         print(key_name)
   
   except KeyboardInterrupt:
      pass
   finally:
      pi.stop() # Disconnect from Pi.

※受信した赤外線は、事前に学習したパターンと一致しているかの判定は「irrp.py」の仕様で判定しております。

認識プログラムの実行

「python3 rc.py 学習データファイル」コマンドで実行して、リモコン操作すると学習したリモコンボタンの認識が出来ます。実行結果は下記の通りです。
この認識プログラムにより、赤外線リモコンでRaspberryPiの色々な操作が可能になります。

pi@raspberrypi:~ $ python3 rc.py codes.json
light:on
light:up
light:dn
light:sml
light:off

目次Raspberry Piロボット|センサー(温度・気圧・湿度人感)|赤外線リモコン読み上げMQTT連携IFTTT連携