お天気時計の作成(その5:焦電センサ)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

焦電型赤外線センサ

以下の焦電型赤外線センサをamazonで購入しました。

https://amzn.to/4b4tuol

 

このセンサは、人物や物体が温度に応じて放射している赤外線を検出して出力します。

動作確認

www.amazon.co.jp

VIN端子にRaspberry Pi Zeroの 3.3V(1番pin)、

GNG端子にRaspberry Pi ZeroのGND(9番pin)に接続して、

OUT端子の波形を確認してみました。

3.3Vを供給して、信号レベルは約3.2Vでした。

 

人を検出したとき(人が動いたとき)

OUT信号はH(3.2V)になり、約2秒後にL(0V)になりました。

動き続けた場合

OUT信号はH(3.2V)の状態を続け、動作をやめるとL(0V)になりました。

動作をやめてから(Lになってから)、直ぐ動作した場合

約2秒Lを継続した後にH(3.2V)になりました。

 

LCDのLED点灯制御

人が動いたときにLEDを点灯し、一定時間経過したらLEDを消灯するようにしたいと思います。

Raspberry Pi Zeroとの接続

焦電センサ

Raspberry Pi Zero

VIN

3.3V

1

OUT

GPIO27

13

GND

GND

9

 

プログラム

GPIOの設定

GPIO27を人検知用の信号(PIR_PIN)として設定します。

変数 PIR_countは一定期間(今回は5分間)の人検知回数を保存します。

(後でどの程度の頻度で人検知しているかモニタするのに使用したいと考えています。)


#---------------------------------------------------------
#人感センサ

import RPi.GPIO as GPIO
import threading

PIR_PIN =27
GPIO.setmode(GPIO.BCM)
GPIO.setup(PIR_PIN,GPIO.IN)
PIR_count = 0

 

callback関数

先ほど設定したGPIOのPIR_PIN信号が立ち上がった時に割り込み処理として、このcallback関数を呼び出します。

この関数は、人検知の回数PIR_countを1プラスして、LEDを点灯させます。

そして、30秒後にPIR_check関数を呼び出す新しいタイマを起動します。

(古いタイマは、念のためキャンセルしてから新しいタイマをスタートしてます。)


#人感センサ入力を割り込み設定する
# 割り込みルーチンでLED点灯して30sタイマを起動する。
def callback(pin):
    global PIR_count,old_PIR_timer
    PIR_count = PIR_count + 1
    LCD_led(23,1)
    
    new_PIR_timer = threading.Timer(30.0, PIR_check)
    old_PIR_timer.cancel()
    new_PIR_timer.start()
    old_PIR_timer = new_PIR_timer
    
#GPIO.add_event_detect(PIR_PIN,GPIO.RISING,callback=callback)

PIR_PN信号の割り込み処理を有効にするには、上記のコメントアウトした箇所です。

メインプログラムで有効にしているのでここではコメントアウトしています。

 

PIR_check関数

人を検知したときの割り込み処理(callback関数)で30秒後に呼ばれます。

この関数は、LEDを消灯して、callback関数で起動したタイマを停止します。


#---------------------------------------------------------------------
# 30s後人がいない場合LEDを消灯してタイマも停止する。
def PIR_check():
    global PIR,PIR_count,old_PIR_timer
    print("人検知",PIR_count,"LED消灯")
    LCD_led(23,0)
    old_PIR_timer.cancel()

#old_PIR_timer = threading.Timer(60.0, PIR_check)
#old_PIR_timer.start()

 

PIR_count_time関数

一定期間(今回は5分間)、人検知の回数をカウントするプログラムです。

300秒(5分)間隔で自分自身を呼び出して、「今の時間、温度、湿度、人検知回数」を出力します。


def PIR_count_time():
    import datetime
    global PIR_count,humi,temp
    PIR_count_timer = threading.Timer(300.0, PIR_count_time)
    PIR_count_timer.start()
    now = datetime.datetime.now()
    time_now = now.time().strftime("%H:%M:%S")
    #humi, temp = sensor.read()
    print(time_now,f'{temp:.01f}',"℃",f'{humi:.01f}',"%","PIR=",PIR_count)
    PIR_count = 0
#--------------------------------------------------------------------

 

メインプログラム

赤字の部分が今回追加したプログラムになります。


if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from LCD_led import LCD_led
    from LCD_disp import LCD_set,setup_disp,time_disp,now_daytime
    from LCD_disp import temp_disp,weather_disp,weather_text_disp,weather_icon_disp
    from LCD_disp import touch_disp,poff_disp
    from weather_get import weather_get
    from check_wifi import check_wifi
    import time

    #---DHT22温度センサの初期設定-----
    import Adafruit_DHT
    sensor = Adafruit_DHT.DHT22
    pin = 26
    #------------------
    
    setup_main()
    touch_disp("")
    
    #人検知の割り込みを有効にする
    GPIO.add_event_detect(PIR_PIN,GPIO.RISING,callback=callback)
    #60秒後にPIR_check関数を起動するタイマを設定する
    old_PIR_timer = threading.Timer(60.0, PIR_check)
    #タイマをスタートする
    old_PIR_timer.start()
    
    #5分(300秒)間隔で人検知回数を出力する
    PIR_count = 0
    PIR_count_time()

    sel = main_01()
    
    # import osでシャットダウンできるようにする
    import os
#    import RPi.GPIO as GPIO

    time.sleep(1)
    if sel =="end":
#        GPIO.cleanup()
        sys.exit()
    if sel == "poff":
        os.system('sudo shutdown -h now')

 

その他修正したプログラム

PIR_count_time関数で、最新の温度と湿度を5分間隔で出力するようにしたので、

setup_main関数とmain_01関数内で使用している変数tempとhumiをグローバル変数にしています。


#--------------------------------------------------------------

def setup_main():
    global temp,humi
    LCD_set()     #LCDの初期設定
    LCD_led(23,1) #LCDのLED点灯
    setup_disp()  #起動中表示
    time.sleep(1) #1秒wait
    
    #今の日時を取得
    today_n,time_n,now_M_S,now_S = now_daytime()
    #時計表示
    time_disp(today_n,time_n)
    #温度取得
    humi, temp = Adafruit_DHT.read_retry(sensor, pin)
    if humi is not None and temp is not None:
        #温度表示
        temp_disp(temp,humi) 
    else:
        print('Failed to get reading. Try again!')
        
    while True:
        if check_wifi():
            print('Wi-Fi is available.')
            break
        else:
            print('Wi-Fi is not available.')
        
        time.sleep(1)

    #お天気を取得
    cor,weather,weather_url = weather_get()
    #お天気をLCDへ表示
    weather_disp(cor,weather,weather_url)
    #weather_text_disp(cor,weather)    
    #weather_icon_disp()    

#-------------------------------------------------

def main_01():
    global touch_x,touch_y,temp,humi
    
    now_S_old = "--"
    (touch_x,touch_y) = (0,0)
    
    while True:   
        #今の日時を取得
        today_n,time_n,now_M_S,now_S = now_daytime()
        
        if now_S != now_S_old: #1秒に1回処理するためにこの処理を追加
            #0秒になったら時計表示
            if now_S == "00" :
                time_disp(today_n,time_n)
                print(today_n,time_n)                
        
            if now_S[1] == "0": #10秒毎に処理
                #温度取得
                humi, temp = Adafruit_DHT.read_retry(sensor, pin)

                if humi is not None and temp is not None:
                    #print('Temp={0:0.1f}*C  Humidity={1:0.1f}%'.format(temp, humi))
                    #温度表示
                    temp_disp(temp,humi)
                    #weather_icon_disp()
                else:
                    print('Failed to get reading. Try again!')
                
            #天気予報表示
            if now_M_S == "00:00":            
                print(time_n)
                i = 0
                while (i < 10):
                    if check_wifi():
                        print('Wi-Fi is available.')
                        #お天気を取得
                        cor,weather,weather_url = weather_get()
                        #お天気をLCDへ表示
                        weather_disp(cor,weather,weather_url)
                        break
                    else:
                        print('Wi-Fi is not available.')        
                    time.sleep(1)

        now_S_old = now_S #1秒の切り替わりを判定するために前の秒数を保存

        #print(touch_x,touch_y,toggle)
        sel = touch_sel(touch_x,touch_y)
        if sel == "end" or sel == "poff":
            print(sel)
            time.sleep(1)
            poff_disp(sel)
            return sel    
        (touch_x,touch_y) = (0,0)

 

以上

お天気時計の作成(その5:自動起動)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

Raspberry pi zeroの電源を入れた時にお天気時計のメインプログラムを自動で実行できるようにしたいと思います。

 

自動起動方法は色々ありますが、「/etc/rc.local」にpython3プログラムの実行スクリプトを追加する方法が簡単ですが上手くできなかったので、cronを使用してみました。

 

cronを使用した自動起動

まず、PATHの設定状態を確認します。後で使用します。


$echo $PATH

そのあと


$crontab -e

と打ちます。

 

初回起動時は,どのエディタで起動するか聞かれるので、1番の「nano」を選びました。

すると,テキストエディタnanoでファイルが開きます. 

 

最後の行に以下を追加します。

PATH=の後は先ほど確認したPATHを張り付けてください。パスの指定をしないとpythonの実行ができませんでした。


LANG=ja_JP.UTF-8
PATH = /home/tomtomst/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games

@reboot cd /home/tomtomst/Documents/python ; python3 ./weather_clock_main.py > /home/tomtomst/Desktop/exec-error.log 2>&1

@rebootは、リブート時の動作を指定するもので、

まず、cdコマンドでプログラムの実行フォルダ「/home/tomtomst/Documents/python」へ移動して、プログラム「weather_clock_main.py」をpython3で実行しています。実行時のエラーは「>」を使用してデスクトップへエラーログを出力するようにしました。

 

リブートしてみてください。

自動でプログラムが起動し、LCDへお天気時計が表示するようになりました。

 

wifi接続チェックの追加

リブート時は問題ありませんでしたが、シャットダウンして電源をoffした後だと起動しない場合がありました。
エラーログを見たところwifi通信ができていない状態のようでした。

20秒程度waitを入れたところ起動できるようになりましたが、

連続動作確認中に何回かwifiの状態が悪くプログラムが停止していたことがあったので、wifiの状態をチェックするようにプログラムを変更しました。

下記の関数を作成し、「check_wifi.py」の名前でlibフォルダへ格納しました。


# WiFi Check
import subprocess

def check_wifi():
    try:
        subprocess.check_output(['ping', '-c', '1', 'www.google.com'])
        return True
    except subprocess.CalledProcessError:
        return False

 

メインプログラムは下記のプログラムを追加し、起動時は、1秒間隔でwifi状態をチェックし、通信可能になるまで待つようにしました。


    while True:
        if check_wifi():
            print('Wi-Fi is available.')
            break
        else:
            print('Wi-Fi is not available.')
        
        time.sleep(1)

1時間毎のお天気表示時は下記のように10秒間チェックをして通信できない場合はお天気情報の取得を諦めるようにしました。


        #天気予報表示
        if now_M_S == "00:00":            
            print(time_n)
            i = 0
            while (i < 10):
                if check_wifi():
                    print('Wi-Fi is available.')
                    #お天気を取得
                    cor,weather,weather_url = weather_get()
                    #お天気をLCDへ表示
                    weather_disp(cor,weather,weather_url)
                    break
                else:
                    print('Wi-Fi is not available.')        
                time.sleep(1)

 

 

 

お天気時計の作成(その4:メニュー表示)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

メニューの表示

今回は、2つのメニューを表示して、タッチパネルで操作できるプログラムを作成したいと思います、

下記の黄色の部分へシャットダウン(poff)とプログラムの終了(off)のメニューを表示し、タッチしたときに実行できるようにしたいと思います。

 

 

メニュー表示プログラム

以前作成したLCD_disp.pyへ下記の関数を追加します。


def touch_disp(sel):
    if(sel == "end"):
        draw.rectangle(( 8,210, 58,239), fill=COLOR_RED)
    else:
        draw.rectangle(( 8,210, 58,239), fill=COLOR_BLUE)
    draw.text((10,210), "end"  , font=FONT_NOTO_24, fill=COLOR_WHITE)
    if(sel == "poff"):
        draw.rectangle((78,210,128,239), fill=COLOR_RED)
    else:
        draw.rectangle((78,210,128,239), fill=COLOR_BLUE)
    draw.text((80,210), "poff" , font=FONT_NOTO_24, fill=COLOR_WHITE)
        
    disp.image(image)


下図のようにLCDの座標(8,210)~(58,239)にend,(78,210)~(128,239)にpoffと表示させます。

該当の領域をタッチしたときに背景色を青から赤に変更できるようにようにしました。

endの領域をタッチしたときは、プログラムの終了、poffの領域をタッチしたときはシャットダウンを行う処理を追加したいと思います。

タッチ領域の判定プログラム

下記で記載した通り、タッチパネルの座標とLCDの座標はxとyが逆になります。

Raspberry Pi Zero 2 WでLCDタッチパネル - tomtomst【電子工作 DIY】 (hatenablog.com)

①タッチセンサを割り込みで処理できるようにするプログラム

#--------------------------------------------------
from xpt2046 import Touch
from gpiozero import Button, DigitalOutputDevice
import board
import busio
from time import sleep

# touch callback
toggle = True

def touchscreen_press(x, y):
    global touch_x,touch_y
    #print(x,y)
    (touch_x,touch_y) = (x,y)

cs = DigitalOutputDevice(7,active_high=False,initial_value=None)
clk = board.SCK
mosi = board.MOSI
miso = board.MISO
irq = Button(22)

spi = busio.SPI(clk, mosi, miso)    # auxiliary SPI
xpt = Touch(spi, cs=cs, int_pin=irq, int_handler=touchscreen_press)
②タッチ座標の判定プログラム

endの領域(210,8)~(239,58)をタッチした場合、背景を赤くして”end"を返します。

poffの領域(210,78)~(239,128)をタッチした場合、背景を赤くして"poff"を返します。


def touch_sel(x,y):
    sel = ""
    if x >= 210 and x <= 239:
        if y >=  8 and y <=  58:
            touch_disp("end")
            sel = "end"
        if y >= 78 and y <= 128:
            touch_disp("poff")
            sel = "poff"
    return sel

プログラム終了時とシャットダウン時のLCD表示

以前作成したLCD_disp.pyへ下記の関数を追加します。


def poff_disp(poff):
    #POFFと表示する
    draw.rectangle((0,0,320,240), fill=COLOR_BLACK)
    draw.text((4,20), poff , font=FONT_NOTO_64, fill=COLOR_WHITE)
    disp.image(image)

 

 

メインプログラム

メインプログラムを2つの関数に分けて作り直しました。

 

1つ目:起動したときにLCDへ表示する関数。

def setup_main():
    
    LCD_set()     #LCDの初期設定
    LCD_led(23,1) #LCDのLED点灯
    setup_disp()  #起動中表示
    time.sleep(1) #1秒wait
    
    #今の日時を取得
    today_n,time_n,now_M_S,now_S = now_daytime()
    #時計表示
    time_disp(today_n,time_n)
    #温度取得
    humi, temp = Adafruit_DHT.read_retry(sensor, pin)
    if humi is not None and temp is not None:
        #温度表示
        temp_disp(temp,humi) 
    else:
        print('Failed to get reading. Try again!')    

    #お天気を取得
    cor,weather,weather_url = weather_get()
    #お天気をLCDへ表示
    weather_disp(cor,weather,weather_url)
    #weather_text_disp(cor,weather)    
    #weather_icon_disp()    
2つ目:時計、温度と湿度、天気の更新およびタッチ処理を行う関数

時計表示は1分毎に更新)、温度と湿度の表示は10秒毎に更新、天気(アイコン、天気、降水確率)は1時間毎に更新します。

また、下部のメニューをタッチするとプログラム終了またはシャットダウンの表示を行います。


def main_01():
    global touch_x,touch_y
    
    now_S_old = "--"
    (touch_x,touch_y) = (0,0)
    
    while True:   
        #今の日時を取得
        today_n,time_n,now_M_S,now_S = now_daytime()
        
        if now_S != now_S_old: #1秒に1回処理するためにこの処理を追加
            #0秒になったら時計表示
            if now_S == "00" :
                time_disp(today_n,time_n)
                print(today_n,time_n)                
        
            if now_S[1] == "0": #10秒毎に処理
                #温度取得
                humi, temp = Adafruit_DHT.read_retry(sensor, pin)

                if humi is not None and temp is not None:
                    print('Temp={0:0.1f}*C  Humidity={1:0.1f}%'.format(temp, humi))
                    #温度表示
                    temp_disp(temp,humi)
                    #weather_icon_disp()
                else:
                    print('Failed to get reading. Try again!')
                
            if now_M_S == "00:00" :
                #お天気を取得
                cor,weather,weather_url = weather_get()
                #お天気をLCDへ表示
                weather_disp(cor,weather,weather_url)
                #weather_text_disp(cor,weather)
                #weather_icon_disp()
    
        now_S_old = now_S #1秒の切り替わりを判定するために前の秒数を保存

        sel = touch_sel(touch_x,touch_y)
        if sel == "end":
            print(sel)
            poff_disp("STOP")
            return sel
        if sel == "poff":
            print(sel)
            poff_disp("POFF")
            time.sleep(1)
            return sel    
        (touch_x,touch_y) = (0,0)

タッチパネルの検出座標のtouch_xとtouch_yはグローバル変数にしておかないと認識できませんでした。

プログラム終了処理とシャットダウン処理

プログラムの終了は、sys.exit()

シャットダウン処理は、os.system('sudo shutdown -h now')

で行います。


if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from LCD_led import LCD_led
    from LCD_disp import LCD_set,setup_disp,time_disp,now_daytime
    from LCD_disp import temp_disp,weather_disp,weather_text_disp,weather_icon_disp
    from LCD_disp import touch_disp,poff_disp
    from weather_get import weather_get    
    import time

    #---DHT22温度センサの初期設定-----
    import Adafruit_DHT
    sensor = Adafruit_DHT.DHT22
    pin = 26
    #------------------

    setup_main()
    touch_disp("")

    sel = main_01()
    
    # import osでシャットダウンできるようにする
    import os
    import RPi.GPIO as GPIO

    time.sleep(1)
    if sel =="end":
        GPIO.cleanup()
        sys.exit()
    if sel == "poff":
        os.system('sudo shutdown -h now')

動作確認

プログラムの終了処理

ちゃんとタッチした座標を認識してプログラムの終了処理ができました。

ただし、sys.exit()を実行するとエラーを表示します。

最後にこのようなエラーを表示しました。

RuntimeError: Please set pin numbering mode using GPIO.setmode(GPIO.BOARD) or GPIO.setmode(GPIO.BCM)

タッチパネルのプログラムでgpiozeroのライブラリを使用しています。

手動でクリーンナップする必要はないかもしれません。

 GPIO.cleanup()はコメントアウトしたら先ほどのエラーは表示しなくなりました。 LCDのLEDも消えましたので手動でクリーンナップする必要はなさそうです。

 

LCDの動作は以下のようになります。

タッチする前

タッチしたとき

すぐにSTOPを表示

1秒後に画面消灯

 

シャットダウン処理

こちらはエラーもなく正常にシャットダウンしました。

次回は、電源を入れた時にこのプログラムを自動で実行できるようにしたいと思います。

お天気時計の作成(その3:お天気表示)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

天気予報API

下記のURLで提供している情報を取得してお天気をLCDへ表示したいと思います。

天気予報 API(livedoor 天気互換) (tsukumijima.net)

 

お天気の情報は、JSONデータで取得でき、例えば「神奈川県(横浜)」の場合は、cityコード(地域ID)が「140010」なので下記のURLから取得できます。

https://weather.tsukumijima.net/api/forecast/city/140010

 

お天気情報の取得プログラム

下記は、先ほどのJSONデータから今日のお天気と今の時間の降水確率を取り出すプログラムです。

ファイル名:weather_get.py


#----weather_get-----

def weather_get():
    import requests
    import re
    from datetime import datetime

    #global cor,weather,weather_url
    city_code = "140010" # kanagawa(yokohama) のcityコード
    url = "https://weather.tsukumijima.net/api/forecast/city/" + city_code
    try:
        response = requests.get(url)
        response.raise_for_status()     # ステータスコード200番台以外は例外とする
    except requests.exceptions.RequestException as e:
        print("Error:{}".format(e))

    else:
        weather_json = response.json()
    #    print(weather_json['forecasts'][0]['telop']) # 0:今日 1:明日 2:明後日
        print(weather_json['forecasts'][0]['chanceOfRain']) # 0:今日 1:明日 2:明後日
        weather = weather_json['forecasts'][0]['telop']
        weather_code = weather_json['forecasts'][0]['image']['title']
        weather_url = weather_json['forecasts'][0]['image']['url']

        #file download
        import urllib.request
    #    url2 = "https://www.jma.go.jp/bosai/forecast/img/313.svg"
        url2 = weather_url
        urllib.request.urlretrieve(url2,'./icon/temp.svg')
        urllib.request.urlretrieve(url2,'./icon/'+weather_url[-7:])

     #--------------------------------------------
     #-- ICON change ----
        #svg -> pgn
        
        import sys
        import os
        os.environ['LC_CTYPE'] = "ja_JP.UTF-8"
        import cairosvg
        from PIL import Image

        svg = "./icon/temp.svg"
        png = "./icon/temp.png"
        cairosvg.svg2png(url=svg, write_to=png)
        img = Image.open(png)
    #    img.save('.icon/temp.png')
    #    print('Deleting temporary png file.')
    #    os.unlink(png)

        # 現在の時間の降水確率を取得していく
        now_hour = datetime.now().hour
        if 0 <= now_hour and now_hour < 6:
            cor = weather_json['forecasts'][0]['chanceOfRain']['T00_06']
        elif 6 <= now_hour and now_hour < 12:
            cor = weather_json['forecasts'][0]['chanceOfRain']['T06_12']
        elif 12 <= now_hour and now_hour < 18:
            cor = weather_json['forecasts'][0]['chanceOfRain']['T12_18']
        else:
            cor = weather_json['forecasts'][0]['chanceOfRain']['T18_24']

        print("現在の降水確率 {}".format(cor))
        print("today is  {}".format(weather))
        
    return cor,weather,weather_url
#--------------------------------------------------------------
if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from weather_get import weather_get

    weather_get()

今日のお天気の情報

今日のお天気情報は「0」番目にあります。「1」番目は明日、「2」番目は明後日の情報になります。

「0」番目のみ取り出したのが下記になります。

 

    "forecasts": [
        {
            "date": "2024-03-30",
            "dateLabel": "今日",
            "telop": "晴のち曇",
            "detail": {
                "weather": "晴れ 夜 くもり",
                "wind": "南西の風 海上 では 南西の風 やや強く",
                "wave": "2メートル"
            },
            "temperature": {
                "min": {
                    "celsius": null,
                    "fahrenheit": null
                },
                "max": {
                    "celsius": null,
                    "fahrenheit": null
                }
            },
            "chanceOfRain": {
                "T00_06": "--%",
                "T06_12": "--%",
                "T12_18": "--%",
                "T18_24": "0%"
            },
            "image": {
                "title": "晴のち曇",
                "url": "https://www.jma.go.jp/bosai/forecast/img/110.svg",
                "width": 80,
                "height": 60
            }
        },

お天気

先ほどのプログラムでは、赤文字部分("晴のち曇")を weaterという変数へ格納しています。

降水確率

降水確率は、現在の時間から「T00_06」~「T18_24」を判定して、該当する時間の降水確率をcorという変数へ格納しています。例えば18:30の場合は「0%」(青文字の部分)です。

お天気アイコンの取得

お天気のアイコン(緑文字の部分)は、このプログラムがあるフォルダの下にiconフォルダに格納します。あらかじめiconフォルダを作成しておいてください。

 

お天気アイコンの変換

PythonのライブラリCairoSVGで、SVGファイルからPNGファイルへ変換します。

CairoSVGは、SVGファイルをPDF、EPS、PS、PNGファイルに変換することができます。
CairoSVGを使用するには、pipを使用してインストールする必要があります。

以下のコマンドを使用して、CairoSVGをインストールできます。


    $ pip install CairoSVG

 

プログラムの実行結果


LCDへのお天気の表示

下図のお天気の表示エリア(黄色の部分)に表示したいと思います。


LCD表示プログラム

下記のプログラムを以前作成したLCD_disp.pyへ追加します。


def weather_disp(cor,weather,weather_url):
    from PIL import Image
       
    #weather info
    #cor,weather,weather_url = weather_get()
                
    #天気アイコンの色を決定する
    weather_n = weather_url[-7:]
    weather_n = int(weather_n[0:3])

    icon_color = (0,0,255)
    if weather_n >= 100 and weather_n < 200 : icon_color = COLOR_RED
    if weather_n >= 200 and weather_n < 300 : icon_color = COLOR_GRAY
    if weather_n >= 300 and weather_n < 400 : icon_color = COLOR_LIGTH_BLUE
                
    # テキストを消去
    draw.rectangle((0,104,319,209), fill=COLOR_BLACK)
    draw.text((180,104), weather, font=FONT_NOTO_36, fill=COLOR_WHITE)
    draw.text((180,144), cor,     font=FONT_NOTO_48, fill=COLOR_WHITE)

    # Open image
    png = "./icon/temp.png"
    icon_image = Image.open(png)
    
    # Resize to screen size
    size = (160,120)
    icon_image = icon_image.resize(size, resample=Image.LANCZOS)
    
    # アイコンを表示
    draw.bitmap((0,96), icon_image, fill=icon_color)
    # Display image
    disp.image(image)

アイコン表示の関数「draw.bitmap」は、単色表示だったので、アイコンの種類によって色を指定するようにしました。

(アイコン名が1xx.pngは:赤、2xx.pngはグレー、3xx.pngはブルー)

 

メインプログラム

前回の温度表示のメインプログラムに赤文字部分を追加しました。


if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from LCD_led import LCD_led
    from LCD_disp import LCD_set,setup_disp,time_disp,now_daytime
    from LCD_disp import temp_disp,weather_disp
    from weather_get import weather_get    
    import time
    
    #---DHT22温度センサの初期設定-----
    import Adafruit_DHT
    sensor = Adafruit_DHT.DHT22
    pin = 26
    #------------------
    
    LCD_set()     #LCDの初期設定
    LCD_led(23,1) #LCDのLED点灯
    setup_disp()  #起動中表示
    time.sleep(1) #1秒wait
    
    #今の日時を取得
    today_n,time_n,now_M_S,now_S = now_daytime()
    #時計表示
    time_disp(today_n,time_n)
    #温度取得
    humi, temp = Adafruit_DHT.read_retry(sensor, pin)
    if humi is not None and temp is not None:
        #温度表示
        temp_disp(temp,humi) 
    else:
        print('Failed to get reading. Try again!')    

    #お天気を取得
    cor,weather,weather_url = weather_get()
    #お天気をLCDへ表示
    weather_disp(cor,weather,weather_url)
    
    now_S_old = "--"
    
    while True:   
        #今の日時を取得
        today_n,time_n,now_M_S,now_S = now_daytime()
        
        if now_S != now_S_old: #1秒に1回処理するためにこの処理を追加
            #0秒になったら時計表示
            if now_S == "00" :
                time_disp(today_n,time_n)
                print(today_n,time_n)                
        
            if now_S[1] == "0": #10秒毎に処理
                #温度取得
                humi, temp = Adafruit_DHT.read_retry(sensor, pin)

                if humi is not None and temp is not None:
                    print('Temp={0:0.1f}*C  Humidity={1:0.1f}%'.format(temp, humi))
                    #温度表示
                    temp_disp(temp,humi)
                else:
                    print('Failed to get reading. Try again!')
                
            if now_M_S == "00:00" :
                #お天気を取得
                cor,weather,weather_url = weather_get()
                #お天気をLCDへ表示
                weather_disp(cor,weather,weather_url)
    
        now_S_old = now_S #1秒の切り替わりを判定するために前の秒数を保存

起動時と1時間ごとにお天気を取得してLCDへ表示するようにしました。

 

実行結果

 

 

お天気時計の作成(その2:温度表示)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

温度センサ

DHT22(AM302)という温度センサを購入しました。

購入したのはこちらになります。

 

 

温度センサとの接続

下記のように接続しました。

DHT22のS(データ)は、4.7kΩでプルアップされてるので、電源は3.3Vを接続しました。

Raspberry pi zeroのGPIOの電圧は3.3Vのため)

DHT22

Raspberry pi zero

VCC

+3.3V

S

GPIO26

GND

GND

 

ライブラリのインストールと実行

ライブラリはadafruit-circuitpython-dht を使用します。

下記のURLを参考にインストールしました。

Rasperry Pi に DHT22 を接続して温度・湿度を計測する (zenn.dev)

必要なパッケージのインストール


    $ sudo apt-get install libgpiod2

adafruit-circuitpython-dhtのインストール


    $ pip3 install adafruit-circuitpython-dht

サンプルコード

サンプルコードは下記になります。

今回はGPIO26に接続していますので、

dhtDevice = adafruit_dht.DHT22(board.D18)

dhtDevice = adafruit_dht.DHT22(board.D26,use_pulseio=False)

に書き換えています。

ファイル名:dht_simpletest.py


# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import adafruit_dht

# Initial the dht device, with data pin connected to:
dhtDevice = adafruit_dht.DHT22(board.D26, use_pulseio=False)

# you can pass DHT22 use_pulseio=False if you wouldn't like to use pulseio.
# This may be necessary on a Linux single board computer like the Raspberry Pi,
# but it will not work in CircuitPython.
# dhtDevice = adafruit_dht.DHT22(board.D18, use_pulseio=False)

while True:
    try:
        # Print the values to the serial port
        temperature_c = dhtDevice.temperature
        temperature_f = temperature_c * (9 / 5) + 32
        humidity = dhtDevice.humidity
        print(
            "Temp: {:.1f} F / {:.1f} C    Humidity: {}% ".format(
                temperature_f, temperature_c, humidity
            )
        )

    except RuntimeError as error:
        # Errors happen fairly often, DHT's are hard to read, just keep going
        print(error.args[0])
        time.sleep(2.0)
        continue
    except Exception as error:
        dhtDevice.exit()
        raise error

    time.sleep(2.0)

サンプルプログラムの実行結果

実行してみました。 それらしい温度と湿度を表示しました。

が、たまにエラーを出力します。

違うライブラリのインストールと実行

Adafruit_Python_DHTを試してみることにします。

下記のURLを参考にしました。

簡単!Raspberry pi(ラズパイ)で温度・湿度(DHT22)を測る方法 | いなきたものづくりラボ (inakita-monolab.com)

ライブラリのインストール


    $ sudo git clone https://github.com/adafruit/Adafruit_Python_DHT.git
    $ cd Adafruit_Python_DHT
    $ sudo python setup.py install

サンプルプログラム


import time
import Adafruit_DHT

sensor = Adafruit_DHT.DHT22
pin = 26

while True:
    humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
    if humidity is not None and temperature is not None:
          print('Temp={0:0.1f}*C  Humidity={1:0.1f}%'.format(temperature, humidity))
    else:
          print('Failed to get reading. Try again!')
    time.sleep(2.0)

サンプルプログラムの実行結果


安定して動作してそうです。

「お天気時計」では、こちらのライブラリを使用することにします。

LCDへ表示

先日作成したLCD_disp.pyに下記の関数を追加します。


#温度表示
def temp_disp(temp,humi):
    # テキストを消去
    draw.rectangle((180 , 0   ,  319,103), fill=COLOR_BLACK)
    # 温度と湿度の表示
    draw.text((180, 0   ), f'{temp:.01f}'+"℃", font=FONT_NOTO_48, fill=COLOR_WHITE)
    draw.text((180, 48-2), f'{humi:.01f}'+"%", font=FONT_NOTO_48, fill=COLOR_WHITE)
    disp.image(image)

48ドット文字を2行表示にしたので、y座標は96ドットまでと思っていたのですが、下側にはみ出てしまったので表示するエリアを下側に8ドット増やして103ドットまでとしました。

また、2行目の開始位置を2ドット上の46ドット目にしました。

 



下記のメインプログラムを実行してみます。

赤字部分が前回から変更(追加)した箇所になります。


if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from LCD_led import LCD_led
    from LCD_disp import LCD_set,setup_disp,time_disp,now_daytime
    from LCD_disp import temp_disp
    import time
    
    #---DHT22温度センサの初期設定-----
    import Adafruit_DHT
    sensor = Adafruit_DHT.DHT22
    pin = 26
    #------------------
    
    LCD_set()     #LCDの初期設定
    LCD_led(23,1) #LCDのLED点灯
    setup_disp()  #起動中表示
    time.sleep(1) #1秒wait
    
    #今の日時を取得
    today_n,time_n,now_M_S,now_S = now_daytime()
    #時計表示
    time_disp(today_n,time_n)
    #温度取得
    humi, temp = Adafruit_DHT.read_retry(sensor, pin)
    if humi is not None and temp is not None:
        #温度表示
        temp_disp(temp,humi) 
    else:
        print('Failed to get reading. Try again!')    

    now_S_old = "--"
    
    while True:   
        #今の日時を取得
        today_n,time_n,now_M_S,now_S = now_daytime()
        
        if now_S != now_S_old: #1秒に1回処理するためにこの処理を追加
            #0秒になったら時計表示
            if now_S == "00" :
                time_disp(today_n,time_n)
                print(today_n,time_n)                
        
            if now_S[1] == "0": #10秒毎に処理
                #温度取得
                humi, temp = Adafruit_DHT.read_retry(sensor, pin)

                if humi is not None and temp is not None:
                    print('Temp={0:0.1f}*C  Humidity={1:0.1f}%'.format(temp, humi))
                    #温度表示
                    temp_disp(temp,humi)
                else:
                    print('Failed to get reading. Try again!')

        now_S_old = now_S #1秒の切り替わりを判定するために前の秒数を保存

実行結果

10秒毎に温度と湿度を右側のエリアに表示できました。

次回はお天気の部分を表示できるようにしたいと思います。

 

お天気時計の作成(その1:時計表示)

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

表示イメージ

Raspberry pi zero 2Wを使用して下図のような表示を行う「お天気時計」を作成しようと考えています。

LCDは前回紹介した3.2インチのタッチパネル付きLCDになります。

(前回のLCD接続確認はこちらです)

 


表示エリア

先ほどの表示イメージを下図のように5つのエリアに区切って、1つ1つ作っていきたいと思います。今回は、「日付と時計表示エリア」になります。

 

プログラム

作成したプログラムはこちらになります。

ファイル名:LCD_disp.py

#LCDの初期設定
def LCD_set():
    global draw,disp,image
    global COLOR_BLACK,COLOR_WHITE,COLOR_PURPLE,COLOR_RED
    global COLOR_BLUE,COLOR_GRAY,COLOR_LIGTH_BLUE
    global FONT_NOTO_12,FONT_NOTO_18,FONT_NOTO_20,FONT_NOTO_24
    global FONT_NOTO_36,FONT_NOTO_48,FONT_NOTO_64,FONT_ROBOTO
    
    from adafruit_rgb_display.rgb import color565
    from adafruit_rgb_display.ili9341 import ILI9341

    from busio import SPI
    from digitalio import DigitalInOut
    import board

    from PIL import Image, ImageDraw, ImageFont

    # Pin Configuration
    cs_pin = DigitalInOut(board.D8)
    dc_pin = DigitalInOut(board.D25)
    rst_pin = DigitalInOut(board.D24)

    # Set up SPI bus
    spi = SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO)

    # Create the ILI9341 display:
    disp = ILI9341(
        spi,
        cs=cs_pin, dc=dc_pin, rst=rst_pin,
        width=240, height=320,
        rotation=90,
        baudrate=24000000
    )

    # Define image size (320x240, rotated)
    IMAGE_SIZE = (disp.height, disp.width)

    # Define fonts
    FONT_ROBOTO = ImageFont.truetype("Roboto-Medium.ttf", 12)
    FONT_NOTO_12 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 12)
    FONT_NOTO_18 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 18)
    FONT_NOTO_20 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 20)
    FONT_NOTO_24 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 24)
    FONT_NOTO_36 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 36)
    FONT_NOTO_48 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 48)
    FONT_NOTO_64 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 64)

    # Define colors
    COLOR_WHITE      = (236, 239, 241)
    COLOR_BLACK      = (  0,   0,   0)
    COLOR_PURPLE     = (239, 154, 154)
    COLOR_RED        = (239,   0,   0)
    COLOR_BLUE       = (  0,   0, 239)
    COLOR_GRAY       = (128, 128, 128)
    COLOR_LIGTH_BLUE = (  0, 239, 239)

    # Create an image with black background
    image = Image.new("RGB", IMAGE_SIZE, (0, 0, 0))

    # Draw some text
    draw = ImageDraw.Draw(image)

#今の日時を取得
def now_daytime():
    import datetime
    now = datetime.datetime.now()
    today_n = now.date().strftime("%Y/%m/%d(%a)")
    time_n = now.time().strftime("%H:%M")
    now_M_S = now.time().strftime("%M:%S")
    now_S = now.time().strftime("%S")
    return today_n,time_n,now_M_S,now_S

#時計表示
def time_disp(today_n,time_n):
    # テキストを消去
    draw.rectangle((0 , 0   ,  179, 95), fill=COLOR_BLACK)
    #時計表示
    draw.text((0 , 0 ), today_n, font=FONT_NOTO_20, fill=COLOR_WHITE)
    draw.text((4 , 20), time_n , font=FONT_NOTO_64, fill=COLOR_WHITE)
    disp.image(image)

#起動中表示
def setup_disp():
    #起動中と表示する
    draw.rectangle((0,0,319,95), fill=COLOR_BLACK)
    draw.text((4,20), "起動中" , font=FONT_NOTO_48, fill=COLOR_WHITE)
    disp.image(image)

if __name__ == '__main__':

    # libフォルダのライブラリを参照できるようにする。
    import sys
    sys.path.append('./lib')
    from LCD_led import LCD_led
    
    import time
    
    LCD_set()     #LCDの初期設定
    LCD_led(23,1) #LCDのLED点灯
    setup_disp()  #起動中表示
    time.sleep(1) #1秒wait
    
    #今の日時を取得
    today_n,time_n,now_M_S,now_S = now_daytime()
    #時計表示
    time_disp(today_n,time_n)
    
    now_S_old = "--"
    
    while True:   
        #今の日時を取得
        today_n,time_n,now_M_S,now_S = now_daytime()
        
        if now_S != now_S_old: #1秒に1回処理するためにこの処理を追加
            #0秒になったら時計表示
            if now_S == "00" :
                time_disp(today_n,time_n)
                print(today_n,time_n)                
        
        now_S_old = now_S #1秒の切り替わりを判定するために前の秒数を保存
        

プログラムの説明

   LCDの初期設定を行います。LCDを制御するためのSPIやGPIOの設定

   および、使用するフォントやフォントの色を定義しています。

  • now_daytime()

   datetime関数使用して現在の日時を取得します。

   実行すると today_n,time_n,now_M_S,now_Sを返します。

    today_n  :現在の「年月日(曜日)」

    time_n    :現在の「時:分」

    now_M_S:現在の「分:秒」

    now_S     :現在の「秒」

  • time_disp(today_n,time_n)

   現在の「年月日(曜日)」と「時:分」をLCDへ表示します。

  • setup_disp()

   日付と時間の表示エリアに「起動中」と表示します。

  • if __name__ == '__main__':以下のプログラム

    上記作成した関数の確認用のプログラムです。

   プログラム中にある「LCD_led(23,1)」関数は、前回作成した関数で、

   LCDのLEDを点灯させるための関数です。「23」はGPIO23を示し、

   「1」でLED点灯、「0」でLED消灯します。

 

  実行すると1秒間「起動中」と表示し、日付と時間を表示します。

  その後は1分毎(0秒時)に時計表示を更新します。

実行結果

下の写真が実行した結果です。

保護用のフィルムを付けたままなので画面が汚いですが...


以上です。

Raspberry Pi Zero 2 WでLCDタッチパネル

Raspberry pi zero 2Wを入手したので備忘録として書きます。

 

LCDタッチパネルの接続

LCDタッチパネルは前回紹介した3.2インチLCDを使用します。

接続内容やLCDの表示方法の詳細はこちらを参照してください。

 

サンプルプログラム

GITHUBにXPT2046-Pythonというサンプルプログラムがあったので動作するか確認してみました。

URLは下記です。

https://github.com/Luca8991/XPT2046-Python/tree/main

xpt2046.py と 
touch-test.py をダウンロードし同じフォルダに格納します。
 

サンプルプログラムtouch-test.pyは、SPI_1を使用するようになっていて、
割り込み用のGPIOはGPIO26で、チップセレクト用のGPIOはGPIO17でした。

なので、下記の個所を今回の接続に合わせて修正しました。

cs = DigitalOutputDevice(17)
clk = board.SCLK_1		# same as writing 21
mosi = board.MOSI_1	# same as writing 20
miso = board.MISO_1	# same as writing 19
irq = Button(26)
  • 注)チップセレクトcsの設定ですが、GPIOの番号を17から7へ変更しただけでは動作しなかったので、active_high=False,initial_value=Noneを追加して動作するようになりました。

以下が修正後のサンプルプログラムtouch-test.pyになります。

from xpt2046 import Touch
from gpiozero import Button, DigitalOutputDevice
import board
import busio
from time import sleep

# touch callback
def touchscreen_press(x, y):
    print(x,y)

cs = DigitalOutputDevice(7,active_high=False,initial_value=None) #
clk = board.SCLK    # same as writing 11
mosi = board.MOSI    # same as writing 10
miso = board.MISO    # same as writing  9
irq = Button(22)    #

spi = busio.SPI(clk, mosi, miso)
# auxiliary SPI

xpt = Touch(spi, cs=cs, int_pin=irq, int_handler=touchscreen_press)

while True:
    #print(xpt.get_touch()) # to get the (x, y) coords when you desire
    sleep(.01)

実行結果

実行結果は下記になります。

左上、右上、左下、右下の順にタッチしました。

240x340のLCDなのでほぼあっています。

が、今回横長で使用する予定なのでxとyが逆になっています。

検出するときは20ドット程度は間隔をあけておいたほうがよさそうです。

位置

x

y

位置

x

y

左上:

3

13

右上:

2

319

左下:

222

15

右下:

224

318

 

LCDの座標は見た目通りですが、タッチパネルはxとyが逆になります。

図にすると下記のようになります。



ライブラリの参照方法

今回使用したxpt2046.pyは、後に使用するのでlibフォルダへ入れておくと便利です。

libフォルダ内のプログラムをライブラリとして参照するには、実行するプログラムの先頭で下記のように参照フォルダを指定します。

# libフォルダのライブラリを参照できるようにする。
import sys
sys.path.append('./lib')

以上