できること

ラズパイとセンサで作る「快適空間自動生成装置」第4回・最終回

第1回:ラズパイとセンサで作る「快適空間自動生成装置」
第2回:ラズパイとセンサで作る「快適空間自動生成装置」
第3回:ラズパイとセンサで作る「快適空間自動生成装置」

 

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

raspberrypi-comfortable-space-device-04-1

 

今回の記事で必要な物一覧

Raspberry Pi 3 B+ または Raspberry Pi 4 Model B

raspberrypi-comfortable-space-device-03-2

Raspberry Pi 3 B+

raspberrypi-comfortable-space-device-03-3

Raspberry Pi 4 Model B

 

ラズパイ用LCDディスプレイ または タッチディスプレイ

raspberrypi-comfortable-space-device-03-6

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

raspberrypi-comfortable-space-device-04-2

タッチディスプレイ

 

ローム・センサメダル(SensorMedal-EVK-002)

raspberrypi-comfortable-space-device-03-4

 

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

raspberrypi-comfortable-space-device-03-5

 

小型スピーカ

raspberrypi-comfortable-space-device-04-3

 

モバイルバッテリ

6

 

USB機器

7-2

 

今回の記事の流れ

  1. センサの値に応じてハードウェアをコントロール
  2. 天気やネット情報との連動
  3. 快適デバイスの完成
  4. まとめ

 

1. センサの値に応じてハードウェアをコントロール

第2回でセンサメダルを使って温湿度を測り、第3回で人感センサを使いました。当初は、センサの値からこのような機能を実装しようとしたので、それを実現させてみましょう。

番号 計測機能 計測後にあったらいい機能
1 部屋の温度を測る 室温に合わせて、扇風機などをコントロール
2 湿度など部屋の快適度を測る 湿度が高かったら、エアコンをドライに設定
5 椅子に座っている時間をチェック 人が居続ける(座りっぱなし)だったら、席を立って動くように促す

 

まず温度をチェックした後、室温が一定以上だったら、扇風機を動かして涼しくなるようにします。ラズパイにUSBで使えるミニ扇風機を挿します。

raspberrypi-comfortable-space-device-04-4

 

室温とその状況に応じて扇風機を回すには、第2回で使ったラズパイのUSBを操作するhub-ctrlコマンドを使います。これで例えば室温が26℃を超えたら、USBの電源を入れ扇風機を回します。

また、人感センサを使ってある一定以上デスクの周りに居たら(座っていたら)、ラズパイに音声を発声させたら面白いかもしれません。ではラズパイにミニスピーカを挿します。

raspberrypi-comfortable-space-device-04-5

 

そして音声読み上げのAquesTalkPiという音声合成ソフトウェアを取得し、Programsの下に解凍します。

Code-Example
1
2
3
4
$ cd ~/Programs
$ wget http://www.a-quest.com/download/package/aquestalkpi-20130827.tgz
$ sudo tar zxvf aquestalkpi-20130827.tgz
$ cd aquestalkpi

 

そうしたら、「休憩しましょう」のような音声を再生させてみましょう。

Code-Example
1
$ ./AquesTalkPi "休憩しましょう!" | aplay

 

これらを第3回で使ったble_lcd.pyプログラムに、以下2行目、29〜36行目の部分(温湿度コントロール)と4〜6行目、12〜17行目、38〜43行目の部分(人感センサのコントロール)を追加しています。

[ble_lcd.py]

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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!'

 

raspberrypi-comfortable-space-device-04-6

 

2. 天気やネット情報との連動

最後に天気予報やネットからの情報も取得して、便利に使いましょう。雨が降りそうなら、それを読み上げてアラートを出すのもいいかもしれません。

番号 計測機能 計測後にあったらいい機能
6 天気をチェック 雨予報なら洗濯物を取り込むようにアラート

まず、天気予報を取得するためにOpenWeatherMapというサービスを使います。以下のように英語のサイトですが、国内の天気も簡単に取得できるので、ここで提供されるAPIを使用します。
https://openweathermap.org/api

raspberrypi-comfortable-space-device-04-7

 

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

raspberrypi-comfortable-space-device-04-8

 

それでは天気予報を取得するプログラムを作ります。まず以下のライブラリをインストールしておきます。

Code-Example
1
$ sudo pip3 install pytz requests

 

forecast.pyというサンプルプログラムを作ります。API_KEYの部分には先ほどコピーしたKeyを入れてください。また、ZIPの部分にご自分の郵便番号を入れて、JPを付けておいて下さい。さらに先ほどのAquestalkに天気を喋らせるようにもしましょう。

[forecast.py]

Code-Example
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#! /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”)

このプログラムを流すと、以下のように指定した地域の天気予報を返してくれます。そして「曇りがち」のような天気予報をラズパイが喋ってくれます。

Code-Example
1
2
$ python3 forecast.py
Date:2020-06-05 00:00:00+09:00 Weather:曇りがち Temp:23.29 C Rain:0mm

 

3. 快適デバイスの完成

では、ラズパイにLCDディスプレイ、スピーカ、人感センサ、USB機器を付けて、デバイスを完成させましょう。

センサメダルはBLEが届く範囲ならどこに置いてもいいので、机の周りや窓際などにセットして下さい。パソコンの近くや壁にかけてもいいと思います。

raspberrypi-comfortable-space-device-04-9

 

センサメダルと人感センサ、天気予報から動作するデバイスの最終的なプログラムはこちらになります。参考にしてみてください。

[ble_lcd.py]

Code-Example
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/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]

Code-Example
1
2
#!/bin/sh
sudo /usr/bin/python3 /home/pi/Programs/ble_lcd.py

 

[blelcd.service]

Code-Example
1
2
3
4
5
6
7
8
9
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

 

これで立ち上げ直せば、ラズパイがセンサの値を表示したりライトを点滅させたりし始めます。

raspberrypi-comfortable-space-device-04-10

 

4. まとめ

今回の連載では、家で仕事をする時間が増えるなか、それをチェックしたり改善したりできるようなデバイスを作りました。

第1回でどんなものを作りたいかを考え、ロームのセンサメダルがあれば様々な数値を測れることが分かりました。

第2回で、実際にラズパイとBLE経由でセンサメダルをつなぐプログラムを作っています。

第3回では、人感センサを使い、人がそこにいるか(ずっと座りっぱなしになっていないか)をチェックするようにしています。また温湿度や明るさなどをLCDディスプレイに表示できるようにしました。

そして最終回である今回では、天気予報なども追加したことで、なかなか便利なデバイスに仕上がったと思います。ラズパイの電源を入れると自動で起動し計測、アラートなどを挙げてくれます。

実際、筆者の家でも毎日稼働しており、明るさを教えてくれたり、洗濯物の取り込み忘れを警告してくれたりしています!

皆さんもご自分にあった自宅生活、リモートワークを便利にするデバイスをぜひ作ってみてください!

 

今回の連載の流れ

第1回:ラズパイとセンサで作る「快適空間自動生成装置」
第2回:ラズパイとセンサで作る「快適空間自動生成装置」
第3回:ラズパイとセンサで作る「快適空間自動生成装置」
第4回:ラズパイとセンサで作る「快適空間自動生成装置」(今回)

アバター画像

普通の会社に勤めるサラリーマンですが、モノ作りが好きな週末メイカーで、電子書籍MESHBOOKを出したり、ブログを書いたりしています!

http://blog.ktrips.net

Tello+Scratch+SQ11でプチ動画を撮る!