電子工作ど素人ですが、手始めに
- Raspberry Pi Zero W
- LCD
- 人感センサー
を使って傘リマインダーを作ります。
携帯の天気アプリで「雨が降りそうなときは朝何時に通知」というものはありますが、出かけるときには結局忘れてしまっていることがあるので。。玄関に置いておいて、人感センサーの入力をトリガーにLCDに降水確率を表示する感じです。電源バッテリー化もしたいです。場合によっては消費電力低減も検討してみる。
一応一通り完成しました!技術的に高度なことは一つもありません。
- LCDの使い方
- 人感センサーの使い方
- 降水確率の取得
- 各機能の組み合わせ
- 電源バッテリー化
- 消費電力低減
できあがりの図
こんな感じになりました。玄関で適当に支度していると、ちゃんとお知らせしてくれます!
このバッテリーでどれくらいの時間持つのか今後検証していきたいと思います。
準備したもの
GPIO Hammer Headerははんだ付けに自信がないので買いました。
OSOYOO 電子部品セットは、電子工作経験ゼロの人にはおすすめです。安いしチュートリアルも簡単だし満足です。
LCDの使い方
以下を参照。
http://osoyoo.com/ja/2016/04/10/drive-16x2-lcd-with-raspberry-pi/
http://osoyoo.com/ja/2016/06/01/drive-i2c-lcd-screen-with-raspberry-pi/
人感センサーの使い方
以下を参照。
http://osoyoo.com/ja/2016/07/14/motionsensor-pi/
降水確率の取得
次の子記事を作成しました。
PythonでXMLから降水確率を取得する
各機能の組み合わせ
一通り出来上がりましたが、ここに至るまでにLCDのVCCとGNDを逆につないで一台壊してしまいました
- 改善点
-
12-18hと18-24hで、requests.get()がそれぞれ一回ずつ呼ばれる。- 2017/08/13 追記: @shiracamusさんから教えていただいたタプル内包表記(厳密にはジェネレータ?)を使って修正済み。
- バックライトが付いているのかを調べる方法が分からない。
- bus.read_byte()をやってみたが、何故かバックライトの情報だけ見えない。
-
#!/usr/bin/env python2
# coding: utf-8
# If motion sensor detected a person, get and show today's rain fall chance
# of Yokohama (East of Kanagawa) on LCD for 30 seconds.
# If not, wait 1 second and check again.
# for general use
import time
# for motion sensor (referred to as "MS")
import RPi.GPIO as GPIO
# for LCD
import smbus
# for rain fall chance
import datetime
import requests
import xml.etree.ElementTree as ET
# constants for motion sensor
MS_GPIO_INPUT_PIN = 14
# constants for LCD
LCD_I2C_ADDR = 0x3F
LCD_CHR_DISP_WIDTH = 16
LCD_CHR_DRAM_WIDTH = 0x28
LCD_NUM_LINES = 2
LCD_PULSE_WAIT = 0.0005
LCD_DELAY_WAIT = 0.0005
I2C_CMD_MODE = 0x00
I2C_CHR_MODE = 0x01
I2C_ENABLE_BIT = 0x04
I2C_BACKLIGHT_OFF = 0x00
I2C_BACKLIGHT_ON = 0x08
bus = smbus.SMBus(1)
def initialize_gpio_for_ms():
GPIO.setwarnings(False)
GPIO.cleanup()
GPIO.setwarnings(True)
GPIO.setmode(GPIO.BCM)
GPIO.setup(MS_GPIO_INPUT_PIN, GPIO.IN, pull_up_down = GPIO.PUD_UP)
def initialize_lcd():
send_byte_to_lcd(0x33, I2C_CMD_MODE) # establish 4 bits mode
send_byte_to_lcd(0x32, I2C_CMD_MODE) # establish 4 bits mode
send_byte_to_lcd(0x01, I2C_CMD_MODE) # 000001 clear display, return cursor to the home position
# send_byte_to_lcd(0x02, I2C_CMD_MODE) # 000010 return cursor to the home position
send_byte_to_lcd(0x06, I2C_CMD_MODE) # 000110 cursor move direction: increment, display shift: disabled
send_byte_to_lcd(0x0C, I2C_CMD_MODE) # 001100 display on, cursor off, blink off
# send_byte_to_lcd(0x10, I2C_CMD_MODE) # 010000 move cursor without writing, not used for initialization
send_byte_to_lcd(0x28, I2C_CMD_MODE) # 101000 4 bits mode, use 2 lines, use 5 dots font
time.sleep(LCD_DELAY_WAIT)
def send_byte_to_lcd(lcd_data, i2c_mode):
first_data = ((lcd_data & 0xF0) << 0) | i2c_mode
second_data = ((lcd_data & 0x0F) << 4) | i2c_mode
write_and_toggle_lcd_enable(first_data)
write_and_toggle_lcd_enable(second_data)
def write_and_toggle_lcd_enable(data):
time.sleep(LCD_DELAY_WAIT)
bus.write_byte(LCD_I2C_ADDR, (data | I2C_ENABLE_BIT))
time.sleep(LCD_PULSE_WAIT)
bus.write_byte(LCD_I2C_ADDR, (data & ~I2C_ENABLE_BIT))
time.sleep(LCD_DELAY_WAIT)
def turn_off_lcd():
send_byte_to_lcd(0x08, (I2C_CMD_MODE | I2C_BACKLIGHT_OFF))
def turn_on_lcd():
send_byte_to_lcd(0x0C, (I2C_CMD_MODE | I2C_BACKLIGHT_ON))
def move_cursor(line, offset):
if line < 0 or LCD_NUM_LINES <= line:
print('invalid line')
return False
if offset < 0 or LCD_CHR_DRAM_WIDTH <= offset:
print('invalid offset')
return False
if line == 0:
line_data = 0x00
elif line == 1:
line_data = 0x40
data = 0x80 + line_data + offset
send_byte_to_lcd(data, (I2C_CMD_MODE | I2C_BACKLIGHT_ON))
return True
def show_text_to_line(message, line):
if move_cursor(line, 0) == False:
return
message = message.ljust(LCD_CHR_DISP_WIDTH)
for i in range(LCD_CHR_DISP_WIDTH):
send_byte_to_lcd(ord(message[i]), (I2C_CHR_MODE | I2C_BACKLIGHT_ON))
def get_yokohama_rain_fall_chances():
today = datetime.datetime.today().strftime("%Y/%m/%d")
url = 'http://www.drk7.jp/weather/xml/14.xml'
response = requests.get(url)
root = ET.fromstring(response.content)
return ((period.get('hour'), period.text)
for area in root.iter('area')
if area.get('id').encode('utf-8') == '東部'
for info in area.findall('info')
if info.get('date') == today
for period in info.find('rainfallchance').findall('period'))
def main():
initialize_gpio_for_ms()
initialize_lcd()
target_periods = '12-18', '18-24'
while True:
motion_detected = True if GPIO.input(MS_GPIO_INPUT_PIN) != 0 else False
if motion_detected:
print('motion detected')
turn_on_lcd()
curr_line = 0
for period, rain_fall_chance in get_yokohama_rain_fall_chances():
if period in target_periods:
text = period + 'h ' + rain_fall_chance + '%'
show_text_to_line(text, curr_line)
print(text)
curr_line += 1
if curr_line >= LCD_NUM_LINES:
break
time.sleep(30)
else:
print('motion not detected')
turn_off_lcd()
time.sleep(1)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass
finally:
send_byte_to_lcd(0x01, I2C_CMD_MODE)
turn_on_lcd()
show_text_to_line('Goodbye!', 0)
GPIO.cleanup()
電源バッテリー化
いろいろ見ていると「最近のモバイルバッテリーは微電流の場合に保護回路が働いて出力が止まる」というようなことが書いてあって心配していましたが、普通に手持ちのバッテリーが使えました。
安物だから保護回路が動いていないのかしら?それともそこそこ電流が流れている?消費電力の測定もしてみたいです。
ANKER Astro Slim2
消費電力低減
まだやってない。