Raspberry Pi 電子工作 10:LCDディスプレイに現在日時、温度・湿度、CPU・メモリ状態、着席状態を表示

今までの集大成。

以下の機器を接続します。

LCDディスプレイ

・温度湿度センサー

・距離センサーでの計測結果

なお、Paspberry Pi Zeroで稼働させていますが、安定しています。

 

 

1. 仕様

  いろいろな情報を1秒ごとにLCDディスプレイに表示します。

  ・現在の日時

  ・温度・湿度

  ・CPU温度、使用率・メモリの使用量、総量

  ・センサーからの距離と着席状態

 

  着席状態については、センサーをデスクに座った際に反応する位置に設置し、以下の条件で働いている、いないを判定、表示します。

  (デスクに座っているだけで働いているとみなすのが正しいとは思っていませんが、あくまで目安ということで)

  ・働いている状態

    着席している場合(距離センサーが30cm以内のものに反応した)

  ・働いていない状態

    着席していない状態が10分(600秒)以上続いた場合

 

2. 配線図

  今まで接続したものをすべて接続します。

  取り回し次第で、配線はきれいになると思います。

f:id:kobatom5278:20210211160730p:plain

LCD、温度湿度、距離センサー

3. Pythonプログラミング

  少し長めですが、難しいことはあまりしていません。

  メインループにて、定期的に関数を呼び出し、取得したものをLCDディスプレイに表示しているだけです。

 

# -*- coding: utf-8 -*-
# 日本語コメントをエラーとしないように、UTF-8で保存

# 使用するライブラリのインポート
import time
from time import sleep
import smbus
import datetime
import subprocess
import re
import psutil
import RPi.GPIO as GPIO

# 定数の定義
# LCDディスプレイのI2Cのアドレス
LCD_I2C_ADDR = 0x27
# LCDの1行目のアドレス
LCD_LINE_1 = 0x80
# LCDの2行目のアドレス
LCD_LINE_2 = 0xC0
# 1行あたりの文字数
LCD_WIDTH = 16
# バックライトのON/OFF(ON:0X08, OFF:0X00を指定)
LCD_BACKLIGHT = 0X08
LCD_CHR = 1
LCD_CMD = 0
ENABLE = 0b00000100
E_PULSE = 0.0005
E_DELAY = 0.0005
# HC-SR04のGPIOピン番号
PIN_TRIG = 24
PIN_ECHO = 25
# 音速(cm / s)
SPEED_OF_SOUND = 33145

def init_lcd():
    """
    LCDの初期化処理
    """
    lcd_byte(0x33, LCD_CMD)
    lcd_byte(0x32, LCD_CMD)
    lcd_byte(0x06, LCD_CMD)
    lcd_byte(0x0C, LCD_CMD)
    lcd_byte(0x28, LCD_CMD)
    lcd_byte(0x01, LCD_CMD)
    sleep(E_DELAY)

def lcd_byte(bits, mode):
    """
    LCDへの書き込み2
    """
    bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
    bits_low =  mode | ((bits << 4) & 0xF0) | LCD_BACKLIGHT
    bus.write_byte(LCD_I2C_ADDR, bits_high)
    lcd_toggle_enable(bits_high)
    bus.write_byte(LCD_I2C_ADDR, bits_low)
    lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
    """
    LCDへの書き込みサブ
    """
    sleep(E_DELAY)
    bus.write_byte(LCD_I2C_ADDR, (bits | ENABLE))
    sleep(E_PULSE)
    bus.write_byte(LCD_I2C_ADDR, (bits & ~ENABLE))
    sleep(E_DELAY)

def lcd_string(message: str, dispLine):
    """
    LCDへの文字の表示(半角文字列のみ)
    """
    # 表示対象行の初期化
    lcd_byte(dispLine, LCD_CMD)
    # 文字列を1行あたりの文字数に調整
    message = message.ljust(LCD_WIDTH)
    # 表示処理
    for dispIndex in range(LCD_WIDTH):
        lcd_byte(ord(message[dispIndex]), LCD_CHR)

# -*- coding: utf-8 -*-
# 日本語コメントをエラーとしないように、UTF-8で保存

# 使用するライブラリのインポート
import smbus
from time import sleep

def tempelature_humidity():
    """
    温度と湿度を取得する
    """

    ADR = 0x44
    # I2Cの取得
    ic2 = smbus.SMBus(1)

    # 測定頻度の設定
    ic2.write_byte_data(ADR, 0x23, 0x34)
    sleep(0.5)
    # 測定値の読出し
    ic2.write_byte_data(ADR, 0xe0, 0x0)
    sleep(0.5)

    # 6バイト取得
    d = ic2.read_i2c_block_data(ADR, 0x0, 6)

    # 0~1バイトが温度
    # 測定データ(生データ)を取得
    t = ((d[0]) << 8) | (d[1])
    # 換算する
    t = -45 + (175 * t / 65535)

    # 3~4バイトが湿度
    # 測定データ(生データ)を取得
    h = ((d[3]) << 8) | (d[4])
    # 換算する
    h = 100 * h / 65535

    # 結果を返却
    return t, h

def cpu_temp():
    """
    CPU温度の取得
    """
    f = open("/sys/class/thermal/thermal_zone0/temp","r")
    cpu_temp = 0
    for t in f:
        cpu_temp = t[:2]
        # 小数点以下2桁まで表示する場合
        # cpu_temp = t[:2]+"."+t[2:3]
    f.close()
    return cpu_temp

def cpu_temp_command():
    """
    CPU温度の取得(コマンドで取得)
    """
    proc = subprocess.run(["vcgencmd", "measure_temp"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    cpu_temp = re.split("[=\']", proc.stdout.decode("utf8"))[1]
    return float(cpu_temp)

def cpu_percent():
    """
    CPU使用率の取得
    """
    cpu_percent = psutil.cpu_percent(interval=1)
    return round(cpu_percent)

def memory_info():
    """
    メモリ情報(使用量[MB]、総量[MB])の取得
    """
    mem = psutil.virtual_memory()
    mem_used = round(mem.used / 1024 / 1024)
    mem_total = round(mem.total / 1024 / 1024)
    return (mem_used, mem_total)

def init_GPIO():
    """
    GPIOの初期設定
    """
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)

    GPIO.setup(PIN_TRIG, GPIO.OUT)
    GPIO.setup(PIN_ECHO, GPIO.IN)

def calc_distance():
    """
    センサーからの距離を算出
    """
    # 2回計測して、小さいほうを返す
    distance = [0, 0]
    for count in range(0, 2):
        # 0.5秒ほど落ち着かせる
        GPIO.output(PIN_TRIG, GPIO.LOW)
        sleep(0.5)
        # 0.00001秒間、超音波を発射
        GPIO.output(PIN_TRIG, True)
        sleep(0.00001)
        GPIO.output(PIN_TRIG, False)

        # 超音波が跳ね返ってくるまでの時間を計測
        while GPIO.input(PIN_ECHO) == 0:
            signaloff = time.time()
        while GPIO.input(PIN_ECHO) == 1:
            signalon = time.time()    
        time_passed = signalon - signaloff

        # 距離を計算する(音速 * 超音波を出力、戻ってくるまでの時間 / 2)
        distance[count] = SPEED_OF_SOUND * time_passed / 2

    distance.sort()
    return distance[0]

# I2Cの取得
bus = smbus.SMBus(1)

try:
    # LCD初期化
    init_lcd()
    # GPIOの初期化
    init_GPIO()

    # 無限ループ、各情報を1秒間隔で表示
    dispMode = 0
    # 最終着席時間(最後にセンサーからの距離が30cm以下だった日時、時間(着席時間))
    workLastTime = None

    while True:
        if (dispMode == 0):
            # 「年月日と時分秒」を表示
            now = datetime.datetime.now()
            lcd_string(f'date: {now.strftime("%Y/%m/%d")}' , LCD_LINE_1)
            lcd_string(f'time: {now.strftime("%H:%M:%S")}', LCD_LINE_2)
            dispMode = 1
        elif (dispMode == 1):
            # 「温度と湿度」を表示
            templature , humidity = tempelature_humidity()
            lcd_string(f'temp: {str(templature)[:4]}\337C', LCD_LINE_1)
            lcd_string(f'hum : {str(humidity)[:4]}%', LCD_LINE_2)
            dispMode = 2
        elif (dispMode == 2):
            # 「CPU温度」「CPU使用率」と「メモリ情報」を表示
            mem_info = memory_info()
            lcd_string(f'cpu : {cpu_temp()}\337C {cpu_percent()}%', LCD_LINE_1)
            lcd_string(f'mem : {mem_info[0]}/{mem_info[1]}MB', LCD_LINE_2)
            dispMode = 3
        elif (dispMode == 3):
            # 「センサーからの距離」を取得
            distance = f'{calc_distance():.3f}'
            # 30cm以下の場合、最終着席時間を書き換える            
            if float(distance) <= 30.0:
                workLastTime = datetime.datetime.now()
            # 最終着席時間から10分(600秒)未満の場合は、働いているとみなす
            working = False
            if workLastTime != None and (datetime.datetime.now() - workLastTime).seconds < 600:
                working = True
            # 働いている、いないにより表示を切り替える
            line2 = ''            
            if working == False:
                if workLastTime == None:
                    # 働いておらず、最終着席時間が未設定(初期状態)
                    line2 = 'Squeeze !!'
                else:
                    # 働いておらず、最終着席時間から時間がたっている
                    line2 = f'Squeeze:{workLastTime.strftime("%H:%M:%S")}'
            else:
                # 働いている
                line2 = f'last: {workLastTime.strftime("%H:%M:%S")}'
            # 表示する
            lcd_string(f'dist: {distance: >7} cm', LCD_LINE_1)
            lcd_string(line2, LCD_LINE_2)
            
            sleep(1)
            dispMode = 0

finally:
    lcd_byte(0x01, LCD_CMD)