第1回:ラズパイとセンサで作る「快適空間自動生成装置」
第2回:ラズパイとセンサで作る「快適空間自動生成装置」
第3回:ラズパイとセンサで作る「快適空間自動生成装置」
こんにちは、ヨシケンです!
自宅を快適に、在宅仕事を効率的にするデバイス作りもいよいよ最終回。いろいろな機能を付けて完成を目指していきます。天気の情報や状況に応じてアクションを促す機能を入れて、リモートワークでも快適にお仕事ができるようなデバイスを作りましょう!

今回の記事で必要な物一覧
Raspberry Pi 3 B+ または Raspberry Pi 4 Model B

Raspberry Pi 3 B+

Raspberry Pi 4 Model B
ラズパイ用LCDディスプレイ または タッチディスプレイ

ラズパイ用LCDディスプレイ

タッチディスプレイ
ローム・センサメダル(SensorMedal-EVK-002)

焦電型赤外線(人感)センサ (SB412A)

小型スピーカ

モバイルバッテリ

USB機器

今回の記事の流れ
- センサの値に応じてハードウェアをコントロール
- 天気やネット情報との連動
- 快適デバイスの完成
- まとめ
1. センサの値に応じてハードウェアをコントロール
第2回でセンサメダルを使って温湿度を測り、第3回で人感センサを使いました。当初は、センサの値からこのような機能を実装しようとしたので、それを実現させてみましょう。
| 番号 | 計測機能 | 計測後にあったらいい機能 |
| 1 | 部屋の温度を測る | 室温に合わせて、扇風機などをコントロール |
| 2 | 湿度など部屋の快適度を測る | 湿度が高かったら、エアコンをドライに設定 |
| 5 | 椅子に座っている時間をチェック | 人が居続ける(座りっぱなし)だったら、席を立って動くように促す |
まず温度をチェックした後、室温が一定以上だったら、扇風機を動かして涼しくなるようにします。ラズパイにUSBで使えるミニ扇風機を挿します。

室温とその状況に応じて扇風機を回すには、第2回で使ったラズパイのUSBを操作するhub-ctrlコマンドを使います。これで例えば室温が26℃を超えたら、USBの電源を入れ扇風機を回します。
また、人感センサを使ってある一定以上デスクの周りに居たら(座っていたら)、ラズパイに音声を発声させたら面白いかもしれません。ではラズパイにミニスピーカを挿します。

そして音声読み上げのAquesTalkPiという音声合成ソフトウェアを取得し、Programsの下に解凍します。
$ cd ~/Programs $ wget http://www.a-quest.com/download/package/aquestalkpi-20130827.tgz $ sudo tar zxvf aquestalkpi-20130827.tgz $ cd aquestalkpi
そうしたら、「休憩しましょう」のような音声を再生させてみましょう。
$ ./AquesTalkPi "休憩しましょう!" | aplay
これらを第3回で使ったble_lcd.pyプログラムに、以下2行目、29〜36行目の部分(温湿度コントロール)と4〜6行目、12〜17行目、38〜43行目の部分(人感センサのコントロール)を追加しています。
[ble_lcd.py]
…
import os
human_count = 0
human_check = 30
aquest_path = "/home/pi/Programs/aquestalkpi/"
scanner = btle.Scanner()
while True:
…
human = GPIO.input(human_pin)
if human == 1:
human_count+=1
else:
human_count=0
print('HCount:'+str(human_count))
...
# 受信データについてBLEデバイス毎の処理
for dev in devices:
...
'''
for key, value in sorted(sensors.items(), key=lambda x:x[0]):
print(' ',key,'=',value)
'''
temp = sensors['Temperature']
humid = sensors['Humidity']
if temp > 26 or humid > 60:
temp_msg = "Hot!"
os.system("sudo hub-ctrl -b 1 -d 2 -P 2 -p 1")
else:
temp_msg = "Not bad"
os.system("sudo hub-ctrl -b 1 -d 2 -P 2 -p 0")
human_msg = str(human_count)
if human_count > human_check:
human_msg += ' Take Rest!'
os.system(aquest_path+'AquesTalkPi "休憩しましょう!" | aplay')
else:
human_msg += ' Work Hard!'

2. 天気やネット情報との連動
最後に天気予報やネットからの情報も取得して、便利に使いましょう。雨が降りそうなら、それを読み上げてアラートを出すのもいいかもしれません。
| 番号 | 計測機能 | 計測後にあったらいい機能 |
| 6 | 天気をチェック | 雨予報なら洗濯物を取り込むようにアラート |
まず、天気予報を取得するためにOpenWeatherMapというサービスを使います。以下のように英語のサイトですが、国内の天気も簡単に取得できるので、ここで提供されるAPIを使用します。
https://openweathermap.org/api

こちらのページの右上の部分からアカウントを作り、ログインします。
その後API Keysという部分に行って、Keyを確認します。このKeyをコピーしておいて下さい。

それでは天気予報を取得するプログラムを作ります。まず以下のライブラリをインストールしておきます。
$ sudo pip3 install pytz requests
forecast.pyというサンプルプログラムを作ります。API_KEYの部分には先ほどコピーしたKeyを入れてください。また、ZIPの部分にご自分の郵便番号を入れて、JPを付けておいて下さい。さらに先ほどのAquestalkに天気を喋らせるようにもしましょう。
[forecast.py]
#! /usr/bin/python3
# -*- coding: utf-8 -*-
import json
import datetime
import os
import requests
import sys
from pytz import timezone
API_KEY = "XXX"
ZIP = "123-4567,JP"
API_URL = "http://api.openweathermap.org/data/2.5/forecast?zip={0}&units=metric&lang=ja&APPID={1}"
def getWeatherForecast():
url = API_URL.format(ZIP, API_KEY)
response = requests.get(url)
forecastData = json.loads(response.text)
if not ('list' in forecastData):
print('error')
return
for item in forecastData['list']:
forecastDatetime = timezone(
'Asia/Tokyo').localize(datetime.datetime.fromtimestamp(item['dt']))
weatherDescription = item['weather'][0]['description']
temperature = item['main']['temp']
rainfall = 0
if 'rain' in item and '3h' in item['rain']:
rainfall = item['rain']['3h']
break
print('Date:{0} Weather:{1} Temp:{2} C Rain:{3}mm'.format(
forecastDatetime, weatherDescription, temperature, rainfall))
return forecastDatetime, weatherDescription, temperature, rainfall
forecastDatetime, weatherDescription, temperature, rainfall = getWeatherForecast()
os.system(“/home/pi/aquestalkpi/AquesTalkPi “ + weatherDescription + “ | aplay”)
このプログラムを流すと、以下のように指定した地域の天気予報を返してくれます。そして「曇りがち」のような天気予報をラズパイが喋ってくれます。
$ python3 forecast.py Date:2020-06-05 00:00:00+09:00 Weather:曇りがち Temp:23.29 C Rain:0mm
3. 快適デバイスの完成
では、ラズパイにLCDディスプレイ、スピーカ、人感センサ、USB機器を付けて、デバイスを完成させましょう。
センサメダルはBLEが届く範囲ならどこに置いてもいいので、机の周りや窓際などにセットして下さい。パソコンの近くや壁にかけてもいいと思います。

センサメダルと人感センサ、天気予報から動作するデバイスの最終的なプログラムはこちらになります。参考にしてみてください。
[ble_lcd.py]
#!/usr/bin/env python3
# coding: utf-8
import dothat
import dothat.backlight as backlight
import dothat.lcd as lcd
interval = 10 # 動作間隔
from datetime import datetime
from bluepy import btle
from sys import argv
import getpass
from time import sleep
import os
import RPi.GPIO as GPIO
human_pin = 13
GPIO.setmode(GPIO.BCM)
GPIO.setup(human_pin, GPIO.IN)
human_count = 0
human_check = 3
import json
import requests
import sys
from pytz import timezone
API_KEY = "xxx" #WeatherMap API Key
ZIP = "123-4567,JP" #Your address
API_URL = "http://api.openweathermap.org/data/2.5/forecast?zip={0}&units=metric&lang=ja&APPID={1}"
aquest_path = "/home/pi/Programs/aquestalkpi/" #AquesTalkPi path
def getWeatherForecast():
url = API_URL.format(ZIP, API_KEY)
response = requests.get(url)
forecastData = json.loads(response.text)
if not ('list' in forecastData):
print('error')
return
#print(forecastData)
for item in forecastData['list']:
forecastDatetime = timezone('Asia/Tokyo').localize(datetime.fromtimestamp(item['dt']))
weatherDescription = item['weather'][0]['description']
temperature = item['main']['temp']
rainfall = 0
if 'rain' in item and '3h' in item['rain']:
rainfall = item['rain']['3h']
break
print('Date:{0} Weather:{1} Temp:{2} C Rain:{3}mm'.format(forecastDatetime, weatherDescription, temperature, rainfall))
return forecastDatetime, weatherDescription, temperature, rainfall
def payval(num, bytes=1, sign=False):
global val
a = 0
for i in range(0, bytes):
a += (256 ** i) * int(val[(num - 2 + i) * 2 : (num - 1 + i) * 2],16)
if sign:
if a >= 2 ** (bytes * 8 - 1):
a -= 2 ** (bytes * 8)
return a
scanner = btle.Scanner()
while True:
now = datetime.now()
d = '{0:0>4d}/{1:0>2d}/{2:0>2d}({3})'.format(now.year, now.month, now.day, now.strftime('%a'))
t = '{0:0>2d}:{1:0>2d}:{2:0>2d}'.format(now.hour, now.minute, now.second)
forecastDatetime, weatherDescription, temperature, rainfall = getWeatherForecast()
lcd.clear()
lcd.set_cursor_position(0, 0)
lcd.write('{}'.format(d))
lcd.set_cursor_position(2, 1)
lcd.write('{}'.format(t))
lcd.set_cursor_position(0, 2)
lcd.write('W:{0}C {1}mm'.format(round(temperature,0), rainfall))
if rainfall > 0:
print(weatherDescription, rainfall)
os.system(aquest_path+'AquesTalkPi '+weatherDescription+' | aplay')
human = GPIO.input(human_pin)
if human == 1:
human_count+=1
else:
human_count=0
print('HCount:'+str(human_count))
try:
devices = scanner.scan(interval)
except Exception as e:
print("ERROR",e)
if getpass.getuser() != 'root':
print('使用方法: sudo', argv[0])
exit()
sleep(interval)
continue
# 受信データについてBLEデバイス毎の処理
for dev in devices:
print("\nDevice %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi))
isRohmMedal = False
sensors = dict()
for (adtype, desc, val) in dev.getScanData():
print(" %s = %s" % (desc, val))
if desc == 'Short Local Name' and val[0:10] == 'ROHMMedal2':
isRohmMedal = True
if isRohmMedal and desc == 'Manufacturer':
# センサ値を辞書型変数sensorsへ代入
sensors['ID'] = hex(payval(2,2))
sensors['Temperature'] = -45 + 175 * payval(4,2) / 65536
sensors['Humidity'] = 100 * payval(6,2) / 65536
sensors['Pressure'] = payval(22,3) / 2048
sensors['Illuminance'] = payval(25,2) / 1.2
sensors['Battery Level'] = payval(30)
sensors['RSSI'] = dev.rssi
# 画面へ表示
print(' ID =',sensors['ID'])
print(' Temperature =',round(sensors['Temperature'],2),'℃')
print(' Humidity =',round(sensors['Humidity'],2),'%')
print(' Pressure =',round(sensors['Pressure'],3),'hPa')
print(' Illuminance =',round(sensors['Illuminance'],1),'lx')
print(' Battery Level =',sensors['Battery Level'],'%')
print(' RSSI =',sensors['RSSI'],'dB')
'''
for key, value in sorted(sensors.items(), key=lambda x:x[0]):
print(' ',key,'=',value)
'''
temp = sensors['Temperature']
humid = sensors['Humidity']
lcd.clear()
dothat.backlight.set_graph(0.5) # 50%
backlight.rgb(0, 0, 0)
if temp > 28 or humid > 80:
temp_msg = "Hot!"
backlight.rgb(255, 0, 0) #Red
else:
temp_msg = "Not bad"
illum = sensors['Illuminance']
if illum < 200:
illum_msg = "Dark!"
os.system("sudo hub-ctrl -b 1 -d 2 -P 2 -p 1")
backlight.rgb(255, 255, 255)
else:
illum_msg = "Bright"
os.system("sudo hub-ctrl -b 1 -d 2 -P 2 -p 0")
backlight.rgb(0, 0, 255) #Blue
human_msg = str(human_count)
dothat.backlight.off()
for led in range(human_count):
backlight.graph_set_led_state(led, 0.2)
if human_count > human_check:
human_msg += ' Take Rest!'
backlight.rgb(0, 255, 0) #Green
os.system(aquest_path+'AquesTalkPi "休憩しましょう!" | aplay')
else:
human_msg += ' Work Hard!'
backlight.rgb(0, 255, 255) #Lightblue
lcd.clear()
lcd.set_cursor_position(0, 0)
lcd.write('T:{0:1.0f}C {1:1.0f}% {2}'.format(temp,humid,temp_msg))
lcd.set_cursor_position(0, 1)
lcd.write('I:{0:1.0f} Lx {1}'.format(illum,illum_msg))
lcd.set_cursor_position(0, 2)
lcd.write('H:{}'.format(human_msg))
sleep(interval)
最後にこのプログラムを自動起動させるようにします。まずPythonプログラムを実行するためのシェルプログラムを作ります。そのシェルを起動時に実行させるためのサービス設定をおこないます。
[blelcd.sh]
#!/bin/sh sudo /usr/bin/python3 /home/pi/Programs/ble_lcd.py
[blelcd.service]
Description=ROHM MEDAL BLE to LCD [Service] ExecStart=/bin/bash /home/pi/Programs/blelcd.sh WorkingDirectory=/home/pi/Programs User=pi [Install] WantedBy=multi-user.target
これで立ち上げ直せば、ラズパイがセンサの値を表示したりライトを点滅させたりし始めます。

4. まとめ
今回の連載では、家で仕事をする時間が増えるなか、それをチェックしたり改善したりできるようなデバイスを作りました。
第1回でどんなものを作りたいかを考え、ロームのセンサメダルがあれば様々な数値を測れることが分かりました。
第2回で、実際にラズパイとBLE経由でセンサメダルをつなぐプログラムを作っています。
第3回では、人感センサを使い、人がそこにいるか(ずっと座りっぱなしになっていないか)をチェックするようにしています。また温湿度や明るさなどをLCDディスプレイに表示できるようにしました。
そして最終回である今回では、天気予報なども追加したことで、なかなか便利なデバイスに仕上がったと思います。ラズパイの電源を入れると自動で起動し計測、アラートなどを挙げてくれます。
実際、筆者の家でも毎日稼働しており、明るさを教えてくれたり、洗濯物の取り込み忘れを警告してくれたりしています!
皆さんもご自分にあった自宅生活、リモートワークを便利にするデバイスをぜひ作ってみてください!
今回の連載の流れ
第1回:ラズパイとセンサで作る「快適空間自動生成装置」
第2回:ラズパイとセンサで作る「快適空間自動生成装置」
第3回:ラズパイとセンサで作る「快適空間自動生成装置」
第4回:ラズパイとセンサで作る「快適空間自動生成装置」(今回)



