ラズベリーパイ デバイス編 IoTCap(2)

赤外線リモコン

IoTCapに付いている赤外線が意外に使えるので、しっかりとリモコン化してみよう。 まずは家にある各種リモコンのキャプチャだ。サンプルプログラムでキャプチャが可能。
sudo ./getIR2
としてから数秒以内にIoTCapに向けてリモコンボタンを押せば、その内容を表示してくれる。

照明器具

家の天井照明は、ほぼリモコン化されている。Panasonicのシーリングライトだ。
リモコン側にはチャンネルが3つ用意されており、複数台使用しても振り分けできるようになっている。それぞれのチャンネルですべてキャプチャしてみる。

機器動作データ(CH1)データ(CH2)データ(CH3)
リビング
シーリング
ライト
点灯(単純)AEHA 40 44 82 9 45 36AEHA 40 44 82 9 53 60AEHA 40 44 82 9 61 52
消灯AEHA 40 44 82 9 47 38AEHA 40 44 82 9 55 62AEHA 40 44 82 9 63 54
全灯(中間色)AEHA 40 44 82 9 44 37AEHA 40 44 82 9 52 61AEHA 40 44 82 9 60 53
昼白色AEHA 40 44 82 57 138 179AEHA 40 44 82 57 140 181AEHA 40 44 82 57 142 183
電球色AEHA 40 44 82 57 139 178AEHA 40 44 82 57 141 180AEHA 40 44 82 57 143 182
エコナビAEHA 40 44 82 57 128 185AEHA 40 44 82 57 131 186AEHA 40 44 82 57 134 191
常夜灯AEHA 40 44 82 9 46 39AEHA 40 44 82 9 54 63AEHA 40 44 82 9 62 55
白くAEHA 40 44 82 57 144 169AEHA 40 44 82 57 148 173AEHA 40 44 82 57 152 161
赤くAEHA 40 44 82 57 145 168AEHA 40 44 82 57 149 172AEHA 40 44 82 57 153 160
明るくAEHA 40 44 82 9 42 35AEHA 40 44 82 9 50 59AEHA 40 44 82 9 58 51
暗くAEHA 40 44 82 9 43 34AEHA 40 44 82 9 51 58AEHA 40 44 82 9 59 50
環境設定AEHA 40 44 82 57 130 187AEHA 40 44 82 57 133 188AEHA 40 44 82 57 136 177
明るさ調整AEHA 40 44 82 57 129 184AEHA 40 44 82 57 132 189AEHA 40 44 82 57 135 190

テレビ

次にテレビリモコンを取得

機器動作データ動作データ
テレビ
基本ボタン
電源NEC 32 64 191 18 237決定NEC 32 64 191 61 194
地デジNEC 32 64 191 122 133NEC 32 64 191 62 193
BSNEC 32 64 191 124 131NEC 32 64 191 63 192
CSNEC 32 64 191 125 130NEC 32 64 191 95 160
入力(UP)NEC 32 64 191 58 197NEC 32 64 191 91 164
入力(DOWN)NEC 32 64 191 15 240メニューNEC 32 64 191 208 47
CH(UP)NEC 32 64 191 27 228番組表NEC 32 64 191 110 145
CH(DOWN)NEC 32 64 191 31 224戻るNEC 32 64 191 59 196
音量(UP)NEC 32 64 191 26 229終了NEC 32 64 191 60 195
音量(DOWN)NEC 32 64 191 30 225上(再生)NEC 32 64 190 32 223
画面表示NEC 32 64 191 28 227下(停止)NEC 32 64 190 33 222
ミュートNEC 32 64 191 16 239左(巻戻)NEC 32 64 190 34 221
クイックNEC 32 64 191 39 216右(早送)NEC 32 64 190 35 220

各種チャンネルボタンはこちら

動作データ(地デジ)データ(BS/CS)
CH01NEC 32 64 191 1 254NEC 32 64 191 97 158
CH02NEC 32 64 191 2 253NEC 32 64 191 98 157
CH03NEC 32 64 191 3 252NEC 32 64 191 99 156
CH04NEC 32 64 191 4 251NEC 32 64 191 100 155
CH05NEC 32 64 191 5 250NEC 32 64 191 101 154
CH06NEC 32 64 191 6 249NEC 32 64 191 102 153
CH07NEC 32 64 191 7 248NEC 32 64 191 103 152
CH08NEC 32 64 191 8 247NEC 32 64 191 104 151
CH09NEC 32 64 191 9 246NEC 32 64 191 105 150
CH10NEC 32 64 191 10 245NEC 32 64 191 106 149
CH11NEC 32 64 191 11 244NEC 32 64 191 107 148
CH12NEC 32 64 191 12 243NEC 32 64 191 108 147

そのほかの特殊ボタン(その後増えたボタンも追加)

機器動作データ
テレビ
その他
dデータNEC 32 67 188 20 235
NEC 32 64 191 115 140
NEC 32 64 191 116 139
NEC 32 64 191 117 138
NEC 32 64 191 118 137
番組説明NEC 32 64 191 113 142
早送りNEC 32 64 190 46 209
巻き戻しNEC 32 64 190 44 211
1つ次NEC 32 64 190 38 217
1つ前NEC 32 64 190 39 216
2画面NEC 32 64 191 41 214
静止NEC 32 64 191 80 175
音多切替NEC 32 64 191 19 236
始めにジャンプNEC 32 64 190 71 184
過去番組表NEC 32 64 190 53 202
ざんまいNEC 32 64 190 76 179
録画リストNEC 32 64 190 40 215
ボイスNEC 32 64 190 69 186
字幕NEC 32 67 188 82 173
まるごとchNEC 32 64 191 38 217
シーン検索NEC 32 64 190 77 178
設定NEC 32 64 191 208 47
音声切替NEC 32 64 191 19 236
クリア音声NEC 32 64 190 70 185
次みるTVNEC 32 64 190 93 162
みるコレNEC 32 64 190 52 203
サブメニューNEC 32 64 191 39 216
4KNEC 32 64 190 124 131
スカパーNEC 32 64 190 79 176
4th-mediaNEC 32 64 190 42 213
netflixNEC 32 64 191 157 98
TUTAYA-TVNEC 32 64 191 54 201
abemaTVNEC 32 64 191 215 40
huluNEC 32 64 191 152 103
u-nextNEC 32 64 191 153 102
you tubeNEC 32 64 191 47 208
dTVNEC 32 64 191 156 99

スポットクーラー

スポットクーラーがあったので拾ってみる

動作データ
電源NEC 32 0 255 70 185
モードNEC 32 0 255 9 246
風量NEC 32 0 255 94 161
おやすみNEC 32 0 255 74 181
タイマーNEC 32 0 255 66 189
NEC 32 0 255 7 248
NEC 32 0 255 12 243

ケース

ラズパイzero+IoTCap向けケース

ケースの作成を行う。案外赤外線がしっかりと飛んで、反応が良いので、裸のラズパイZW+IoTCAPの状態から、 ケースに入れたものにして、きちんと運用したいと思う。ケースは部屋の壁に取り付ける。テレビのちょうど真向いの壁の予定。

ラズパイZEROの寸法図は公式ページからドキュメントとしてたどれるので、 こちらを参照。 トップから行くには、「HELP→ドキュメント→ハードウェア→ラズパイ→Mechanical drawings→調べたいラズパイ」でたどればよい。

これを参考にしつつ、実寸を測定しながら、位置合わせの試作品を何個か作って、最終形が完成。 まずは、ケースの底側。IoTCAPとの接続ねじとGPIOのはんだ跡の逃がし穴を用意。周りのふちは石膏ボードの壁にホッチキスで取り付けるための枠。

そして、ケースの蓋側。こちらは、LCD表示エリア、BME280接続穴、PD受信穴、赤外線LED穴そしてスイッチ穴が開けてある。 表面実装のチップLEDは、案外明るく穴開けておくとかなりまぶしいので、薄い板で覆うようにした。これでも十分光っているのが外から見えた。

そして、本体をケースに収めた状態。左側にはミニカメラも装着済み。ここまでに試作品は10個近く作っており、 位置合わせはほぼOK。嵌合は相変わらずうまく作れず、なんとなく無理やりはまっている状態だ。 ここにBME280を取り付けたら、完成。

壁取付

壁への取り付けは、ホッチキスを使って止める。あらかじめコンセントにUSB電源を用意し、電源用USBケーブルを這わして、 届く位置を決定。また、テレビと部屋のシーリングライトが主な操作対象なので、それらに赤外線が照射可能な位置の壁と高さを決める。

そして、取り付け実施。ケースの蓋をした状態だと、ホッチキスの頭が当たって、きちんと取り付けできないことがわかり、 いったんケースは蓋や本体を取り外して、台座部分だけで壁への取り付けを行う。 本体は軽いので、ホッチキス止めでも十分支えてくれる。しかし何かぶつかったりなどで簡単に外れても困るので、 ある程度ホッチキスはたくさん打ち込んでおく。

壁に台座が取り付けられたら、本体と蓋を取り付ければ完了。本体と台座は両面テープで止めている。 ねじ止めしたいところだが、うまい機構が考え付かなかった。蓋がそこそこしっかりはまっているので、 台座を残して本体がポロリは無いはずだ。

将来機能として、カメラを装備。でもラズパイzeroには処理が重いので取り外した。

主目的のテレビと部屋の照明は、うまく反応してくれる。いろいろ活用できそうだ。

シャットダウンボタン

ボタンを活用して、シャットダウン機能を装備する。 とおもったが、サンプルに既に「poweroff」が存在。シェル版とpython版が用意されていた。

#! /usr/bin/python
# for IoTCAP
# (C)Copyright 2018 All rights reserved by Y.Onodera
 
### global
GPIO_SW1=22
import RPi.GPIO as GPIO
import time
import sys
import os
 
### function
def callbackSW1(pin):
  i=0
  while True:
    time.sleep(0.1)
    i+=1
    if i > 50:
      # sys.exit(0)
      os.system('sudo poweroff')
      break
    if GPIO.input(GPIO_SW1) == 1:
      break
 
 
### main routine
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_SW1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(GPIO_SW1, GPIO.FALLING)
GPIO.add_event_callback(GPIO_SW1, callbackSW1)
 
try:
  while True:
    time.sleep(1)
 
except:
  GPIO.cleanup() 

5秒長押しするとシャットダウンが動作するというもの。 これを見ると、ボタン押下はイベント登録して、コールバックで動かしている。 pythonをあまり知らないので、こんな書き方があるのを知らなかった。

このままだと、5秒経過でのシャットダウンの開始が見えないので、 途中LEDを光らせて、シャットダウン受付中のレスポンスを返そうと思う。

#!/usr/bin/python
# coding:utf-8
import RPi.GPIO as GPIO
import time
import sys
import os
import smbus
 
#GPIO初期化
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
 
#GPIO23pinを入力モードとし、pull up設定とします
GPIO_SW2 = 23
GPIO.setup(GPIO_SW2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#GPIO05(Blue)を出力モードとします
led_port=5
GPIO.setup(led_port, GPIO.OUT)
GPIO.output(led_port, GPIO.LOW)
#GPIO06(White)を出力モードとします
wh_port=6
GPIO.setup(wh_port, GPIO.OUT)
GPIO.output(wh_port, GPIO.LOW)
 
### function 長押し検知してシャットダウン処理
def callbackSW2(pin):
  i=0
  while True:
    time.sleep(0.1)
    i+=1
    if i == 1:
      GPIO.output(led_port, GPIO.LOW)
    if i == 30:
      GPIO.output(led_port, GPIO.HIGH)
    if i == 35:
      GPIO.output(led_port, GPIO.LOW)
    if i == 40:
      GPIO.output(led_port, GPIO.HIGH)
    if i == 45:
      GPIO.output(led_port, GPIO.LOW)
    if i > 50:
      GPIO.output(led_port, GPIO.HIGH)
      # sys.exit(0)
      os.system('sudo poweroff')
      break
    if GPIO.input(GPIO_SW2) == 1:
      break
 
#GPIO23pinのボタン押下をイベント登録
GPIO.add_event_detect(GPIO_SW2, GPIO.FALLING)
GPIO.add_event_callback(GPIO_SW2, callbackSW2)
 
try:
  while True:
    time.sleep(0.2)
 
except:
  GPIO.cleanup()

これで、長押し3秒でLEDが点灯、3.5秒で消灯、4秒で点灯、4.5秒で消灯、そして5秒で点灯してシャットダウン動作に入る。 3回目の点滅が来たらシャットダウン開始だ。それ以前に指を離せば、セーフ。

時計表示

LCDがあるので、時計を表示させたい。時計表示もサンプルは用意されているが、「clock.sh」のシェルのみ。 内部ではシェルとCの処理を呼び出して、動作している。なので、これをpython化してみる。

python版の時計表示サンプルは無いが、初期化「initLCD.py」、カーソル移動「locateLCD.py」、表示「printLCD.py」は、 用意されているので、これらを組み合わせて、時計表示を作ってみよう。 また、上記シャットダウン処理に組み合わせることとする。

ということで、組み合わせて作ってみた。

#!/usr/bin/python
# coding:utf-8
import RPi.GPIO as GPIO
import time
import sys
import os
import smbus
import datetime
 
#LCD接続設定
bus_number=1
addr=0x3e
lcd_cmd=0x00
lcd_dat=0x40
 
#GPIO初期化
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
 
#GPIO23pinを入力モードとし、pull up設定とします
GPIO_SW2 = 23
GPIO.setup(GPIO_SW2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
#GPIO05(Blue)を出力モードとします
led_port=5
GPIO.setup(led_port, GPIO.OUT)
GPIO.output(led_port, GPIO.LOW)
#GPIO06(White)を出力モードとします
wh_port=6
GPIO.setup(wh_port, GPIO.OUT)
GPIO.output(wh_port, GPIO.LOW)
 
#LCD初期化(I2C使用)
bus=smbus.SMBus(bus_number)
time.sleep(0.1)
bus.write_i2c_block_data(addr, lcd_cmd, [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c])
time.sleep(0.3)
bus.write_i2c_block_data(addr, lcd_cmd, [0x38, 0x0c, 0x01])
bus.write_i2c_block_data(addr, lcd_cmd, [0x05, 0x01])
 
### function 長押し検知してシャットダウン処理
def callbackSW2(pin):
  i=0
  while True:
    time.sleep(0.1)
    i+=1
    if i == 1:
      GPIO.output(led_port, GPIO.LOW)
    if i == 30:
      GPIO.output(led_port, GPIO.HIGH)
    if i == 35:
      GPIO.output(led_port, GPIO.LOW)
    if i == 40:
      GPIO.output(led_port, GPIO.HIGH)
    if i == 45:
      GPIO.output(led_port, GPIO.LOW)
    if i > 50:
      GPIO.output(led_port, GPIO.HIGH)
      # sys.exit(0)
      os.system('sudo poweroff')
      break
    if GPIO.input(GPIO_SW2) == 1:
      break
 
#GPIO23pinのボタン押下をイベント登録
GPIO.add_event_detect(GPIO_SW2, GPIO.FALLING)
GPIO.add_event_callback(GPIO_SW2, callbackSW2)
 
try:
  while True:
    time.sleep(0.2)
    # LCDカーソルをHomeポジションへ
    bus.write_i2c_block_data(addr, lcd_cmd, [0x80])
    # 現在日時
    now = datetime.datetime.today().isoformat()
    # LCD表示(時刻部)
    for ix in range(11,19):
      bus.write_i2c_block_data(addr, lcd_dat, [ord(now[ix])])
    #LED点滅
    if (ord(now[18]) % 2) == 0:
      GPIO.output(wh_port, GPIO.LOW)
    else:
      GPIO.output(wh_port, GPIO.HIGH)
 
except:
  GPIO.cleanup()

1秒ごとにLEDも点滅させてみた。これで動いている感じが出る。本当は0.5秒点滅にしてみたかったが、 検知が難しそうなのと、pythonがそんなに早く動かないので、1秒単位での点滅として秒が奇数/偶数かで点灯/消灯させている。

常駐化

起動時に動作するように常駐化させてみる。まずはサービスファイルを作成。「etc下のsystemd/systemに作成する」
sudo nano iotcap.service
内容は、以下の感じとする。

[Unit]
Description =ShutdownButton and TimeDisplay by IoTCAP
  
[Service]
ExecStart=/usr/bin/python3 /home/{ユーザ名など}/IoTCAP/iotcap.py
Restart=no
Type=simple
  
[Install]
WantedBy=multi-user.target

保存したら、まずはデーモン再読み込み。
sudo systemctl daemon-reload
認識確認。
systemctl list-unit-files –type=service | grep iotcap

iotcap.service                         disabled

では、一旦動かしてみる。
sudo systemctl start iotcap
うまく起動したかステータスを見てみよう。
systemctl status iotcap

● iotcap.service - ShutdownButton and TimeDisplay by IoTCAP
Loaded: loaded (iotcap.service; disabled; vendor preset: enabled)
Active: active (running) since Sun 2018-10-** **:**:** JST; 27s ago
Main PID: 1087 (python3)
CGroup: /system.slice/iotcap.service
mq1087 /usr/bin/python3 IoTCAP/iotcap.py

10月 ** **:**:** raspberrypi systemd[1]: Started ShutdownButton and TimeDisplay by IoTCAP.

IoTCAP側は時計が表示されて、LEDも点滅開始。systemctlのステータスも問題なしだ。 では、自動起動を設定しておこう。
sudo systemctl enable iotcap

Webサーバー化

このラズパイでWebサーバーを立てて、各種処理を起動出来れば、離れたクライアントからいろいろ操作ができる。 Nodejs使ってWebサーバを立てて、スマホからのリモート操作できるようにしてみよう。

APIサーバの準備

サーバ処理はexpressを使う。Expressのinit用にgeneratorをインストールする。
sudo npm install -g express-generator
インストールしたらプロジェクト作成。
express –ejs smarthome
作成したら、ミドルのインストールをしておこう
cd smarthome
npm install

基本的な準備完了。

では、app.jsを少し変更。apiへのルーティング作成。

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
 
var indexRouter = require('./routes/index');
var apiRouter = require('./routes/restapi');
 
var app = express();
 
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
 
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// cors
app.use(function(req, res, next){                          // add
  res.header("Access-Control-Allow-Origin","*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
  
app.use('/', indexRouter);
app.use('/api', apiRouter);
 
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});
 
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
 
  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
 
module.exports = app;

8行目で「apiRouter」を作成
21~26行目はCORS設定
29行目で「/api」をapiRouterに接続

併せて、route/users.jsファイルをroute/restapi.jsにリネームして内容を下記に変更。

var express = require('express');
var router = express.Router();
  
/* GET API */
router.get('/', function(req, res, next) {
  var param = {"値":"サンプルAPI返却"};
  res.header('Content-Type', 'application/json; charset=utf-8');
  res.send(param);
});
  
module.exports = router;

では、初回起動を行うが、その前にnodemonを入れておく。
npm install nodemon –save
これを入れて起動すれば、いちいち再起動しなくてもExpressを再起動してくれて便利だ。
npx nodemon ./bin/www
では、起動する。 まずは、ブラウザでアクセスしてみる。
http://raspberrypi.local:3000/
Expressの初期画面が出れば、とりあえずOK。index.js処理はできている様子(デフォルト版) では、API側も見てみよう。ブラウザでurlを少し変更
http://raspberrypi.local:3000/api
こんな結果が返ってくればOKだ。

{"値":"サンプルAPI返却"}

では、apiが呼び出されると、リモコン操作が動作するようにしてみよう。 まずは、送信処理を「/usr/local/bin」に置いておく。
sudo cp setIR2 /usr/local/bin/setIR2
apiで「/light/on」で部屋の明かり操作が呼び出されるようにする。 外部処理の呼び出しには、「child_process」を使用する。

var express = require('express');
var exec = require('child_process').exec;
var router = express.Router();
  
/* GET API */
router.get('/', function(req, res, next) {
  var param = {"値":"サンプルAPI返却"};
  res.header('Content-Type', 'application/json; charset=utf-8');
  res.send(param);
});
 
router.get('/light/:id', function(req, res, next) {
  if (req.params.id == 'on') {
    // 照明点灯
    exec('sudo /usr/local/bin/setIR2 AEHA 40 44 82 9 45 36', function (err, stdout, stderr) {
      if (err) { console.log(err); }
    });
  } else if (req.params.id == 'off') {
    // 照明消灯
    exec('sudo /usr/local/bin/setIR2 AEHA 40 44 82 9 47 38', function (err, stdout, stderr) {
      if (err) { console.log(err); }
    });
  }
  res.header('Content-Type', 'application/json; charset=utf-8');
  res.send({ status: '200', msg: 'OK' });
});
 
module.exports = router;

2行目で「child_process」を準備
12行目で「/light/on」にアクセスがあったら「setIR2」をchild_processで呼び出す。onかoffを区別して点灯か消灯かを呼び出している。

これで、サーバにアクセスしてみると
http://raspberrypihome.local:3000/api/light/on
無事赤外線LEDから信号が飛んで、部屋の明かりが点灯した。これで問題なさそうだ。 あとは、各種コマンドに応じたapiを用意すればOK。

遠い昔は、寝るとき部屋の明かりを消すために長い紐を用意して、布団の中から明かりを消していた。そして今はリモコン化されているから、リモコンを手元に置いて消灯。そして今回はスマホから指示を出すことで消灯が可能に。

クライアント画面の準備

クライアント画面はvueで作ってみる。