LoginSignup
6
3

More than 5 years have passed since last update.

Raspberry Pi Zero Wで傘リマインダーを作る

Last updated at Posted at 2017-08-08

電子工作ど素人ですが、手始めに

  • Raspberry Pi Zero W
  • LCD
  • 人感センサー

を使って傘リマインダーを作ります。
携帯の天気アプリで「雨が降りそうなときは朝何時に通知」というものはありますが、出かけるときには結局忘れてしまっていることがあるので。。玄関に置いておいて、人感センサーの入力をトリガーにLCDに降水確率を表示する感じです。電源バッテリー化もしたいです。場合によっては消費電力低減も検討してみる。
一応一通り完成しました!技術的に高度なことは一つもありません。

  • LCDの使い方
  • 人感センサーの使い方
  • 降水確率の取得
  • 各機能の組み合わせ
  • 電源バッテリー化
  • 消費電力低減

できあがりの図

こんな感じになりました。玄関で適当に支度していると、ちゃんとお知らせしてくれます!
このバッテリーでどれくらいの時間持つのか今後検証していきたいと思います。
4AF816CA-8529-40AA-8761-2ECC9EB76447-2791-000001FB8590F7A4.jpg

準備したもの

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を逆につないで一台壊してしまいました:cry:

  • 改善点
    • 12-18hと18-24hで、requests.get()がそれぞれ一回ずつ呼ばれる。
      • 2017/08/13 追記: @shiracamusさんから教えていただいたタプル内包表記(厳密にはジェネレータ?)を使って修正済み。
    • バックライトが付いているのかを調べる方法が分からない。
      • bus.read_byte()をやってみたが、何故かバックライトの情報だけ見えない。
umbrella_reminder.py
#!/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

消費電力低減

まだやってない。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3