Raspberry Pi swap の無効化

Raspberry Pi は、おそらく多くの場合がSDカードで起動、動作していると思います。

SDカードは安価(単位容量あたりは決して安くはないですが)で手軽な分、読み書きが遅かったり、ある日突然使えなくなったりします。(怖いです)

今回は、Raspberry Pi のOS、Raspberry Pi OS が常時読み書きしようとする、swapを無効にしようと思います。

swapを無効にすることによって、SDカードの読み書きを減らし、SDカードへの負担を減らそうということです。

普通に使用している分にはswapは使用されずに物理メモリで動作しているようですので、容量削減、SDカードの長寿命化の効果があると思います。

swapとは、windowsなどでいう仮想メモリのことです。アプリケーションを多く動かしたりして物理メモリの残量が少なくなった場合、使用頻度の少ないメモリ領域を仮想メモリにコピーして、物理メモリの使用量を減らす仕組みです。(大まかにいうと)

 

 

1. swapの使用量の確認

  LXTerminalにて、以下のコメントを入力します。

free -h

  おそらく、2行で表示されると思います。

  1行目の「Mem:」で始まる内容は物理メモリです。

  2行目の「Swap:」で始まる内容がswapについての情報。

  初期状態では、「Swap:」に続く1つ目の数値は「total」で、「99Mi」となっていると思います。99MB使用できるという意味です。

  2つ目の数値が「used」。使用している量を示します。おそらく、普通の作業をしていたら、「0B」となっているかと思います。使用していない、ということです。

2. swapの無効化

  LXTerminalで以下のコマンドを実行していきます。

  最後の4行目を実行すると「lines 1-11/11」のような白黒反転したメッセージが表示されるので、「CTRL + C」で終了します。

sudo swapoff --all
sudo systemctl stop dphys-swapfile
sudo systemctl disable dphys-swapfile
systemctl status dphys-swapfile

3. swapが無効化されているかの確認

  もう一度、swapの使用量を確認するコマンドを実行します。

free -h

  「swap:」の1つ目の数字が「0」となっていると思います。

  これで、swapが無効化されえました。

4. swapの有効化

  メモリ使用量の大きいアプリケーションを動かすなどで、swapが必要となることもあるかもしれません。

  swapを再度有効にする方法です。

  以下のコマンドを実行します。実行後、「free -h」コマンドで確認してください。

  最後の3行目を実行すると「lines 1-11/11」のような白黒反転したメッセージが表示されるので、「CTRL + C」で終了します。

sudo systemctl start dphys-swapfile
sudo systemctl enable dphys-swapfile
systemctl status dphys-swapfile

Raspberry Pi 電子工作 12:LCDディスプレイに現在日時、温度・湿度、CPU・メモリ状態、勤怠状態(?)を表示

前々回の、距離センサーを用いた着席状態の検出から、人感センサーを使用した検出に変更しています。

距離センサーでの検出では、センサーの前に障害物を「ドン」と置いておくだけで検出されますが、人感センサーでは動いていないと検出されないので、「ジッと考えている、10分以上」ではない限り、検出精度は上がったかと思います。

 

 

1. 仕様

  前回と、基本的には同じですが、「動いている」という要素が追加されます。

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

  ・現在の日時

  ・温度・湿度

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

  ・センサーで動いているか(動作状態)

 

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

  (デスクでジッと考えているのを働いていないとみなすのが正しいとは思っていませんが、あくまで目安ということで)

  ・働いている状態

    一定間隔で動いている場合

  ・働いていない状態

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

2. 配線図

  距離センサーより端子が1つ減ったので、ちょっと簡単に。

f:id:kobatom5278:20210211225600p:plain

 

3. Pythonプログラミング

  人感センサーの検知結果がboolになりました。

# -*- 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
# 人感センサーのGPIOピン番号
ZINKAN_PIN = 23
# 音速(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)
    GPIO.setup(ZINKAN_PIN, GPIO.IN)

def zinkan():
    """
    人感センサーの検知結果を返却
    """
    if GPIO.input(ZINKAN_PIN):
        return True
    else:
        return False

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

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

    # 無限ループ、各情報を1秒間隔で表示
    dispMode = 0
    # 最終動作時間(人感センサーが「検知」だった日時、時間)
    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)

            # 次の処理が1秒かかるので、スリープしない
            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)

            # 次の処理が1秒かかるので、スリープしない
            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)

            sleep(1)
            dispMode = 3
        elif (dispMode == 3):
            # 「人感センサー」の検知結果を取得
            isZinkan = zinkan()
            # 人感センサーの検知結果が「検知」の場合、最終動作時間を更新
            if isZinkan:
                workLastTime = datetime.datetime.now()
            # 直近の最終動作時間から10分(600秒)未満の場合は、働いているとみなす
            working = False
            if workLastTime != None and (datetime.datetime.now() - workLastTime).seconds < 600:
                working = True
            # 働いている、いないにより表示を切り替える
            line1 = ''            
            if working == False:
                if workLastTime == None:
                    # 働いておらず、最終動作時間が未設定(初期状態)
                    line1 = 'Squeeze !!'
                else:
                    # 働いておらず、最終動作時間から時間がたっている
                    line1 = f'Squeeze:{workLastTime.strftime("%H:%M:%S")}'
            else:
                # 働いている
                line1 = f'work: {workLastTime.strftime("%H:%M:%S")}'
            # 表示する
            #lcd_string(f'dist: {distance: >7} cm', LCD_LINE_1)
            lcd_string(line1, LCD_LINE_1)
            lcd_string('', LCD_LINE_2)
            
            sleep(1)
            dispMode = 0

finally:
    lcd_byte(0x01, LCD_CMD)

 

Raspberry Pi 電子工作 11:人感センサー(HW-416-B)

前回取り上げた、超音波距離センサー「HC-SR04」ですが、センサーとその前にある物体との距離を測れるものの、それが人であるかの判定はできません。

ですので、今回は人感センサーといわれるものを取り上げてみます。

 

人感センサーは、赤外線などを利用して周囲温度と温度差のあるものが検知範囲内で動いたときに、その温度変化を検知する仕組みで動作しています。

逆に言えば、検知範囲内で動かない場合は検知しません。

ですので、「検知範囲内で動く生き物(もちろん、人も含みます)を検知する」ものです。動かない状態では検知しないようです。

 

1. センサーの購入

  型番違いでいろいろなものがあるようですが、基本的には同じようです。

  端子は3つ。

  電源(+、ー)と、GPIOに接続するものの3つです。

  セットによく含まれているので、セットで購入してしまうのもよいと思います。

 

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

P Prettyia HC-SR501人体赤外線センサーモジュール焦電センサースイッチ
価格:850円(税込、送料無料) (2021/2/11時点)

楽天で購入

 


 

2. 配線

  端子が少ない分、楽です。

  手元にある機器は、どちらがvccか、gndかプリントが無いので困りましたが、どちらかです。

  基盤にプリントしてある型番 + データシート(またはdatasheet)でインターネット検索すると、たいてい出てきます。

f:id:kobatom5278:20210211212157p:plain

3. Pythonプログラミング

  今回はあまり考えず、0.5秒間隔で、「検知した(MOVE!)」、「検知していない(FREEZE)」をターミナルに時刻とともに表示します。

  正確に言えば、「動き続けない限り」、検知しません。

 

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

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

# 定数の定義
ZINKAN_PIN = 17

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

def zinkan():
    """
    人感センサーの検知結果を返却
    """
    if GPIO.input(ZINKAN_PIN):
        return True
    else:
        return False

# 初期化
init_GPIO()

# 無限ループ、「年月日と時分秒」と「検知結果」を0.5秒間隔で表示
while True:
    # 検知結果のメッセージ
    if (zinkan()):
        ita = "MOVE!"
    else:
        ita = "FREEZE"

    # 「年月日と時分秒」と検知結果表示
    now = datetime.datetime.now()
    print(f'{now.strftime("%Y/%m/%d %H:%M:%S")}: {ita}')
    sleep(0.5)

 

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)

Raspberry Pi 電子工作 09:距離センサー(HC-SR04)で距離を測る

「HC-SR04」という電子部品があります。

これは、超音波を発射し、跳ね返ってきた超音波を検知するものです。

イルカなどと同じ原理ですね。

今回は、この電子部品をPythonから制御して、センサーからの距離を測定してみます。

 

 

1. センサーの購入

   センサーを購入します。


 

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

超音波距離センサモジュール HC-SR04 [Arduino センサ関連]
価格:1705円(税込、送料無料) (2021/2/11時点)

楽天で購入

 

 

2. 配線

  センサーとRaspberry Pi を接続します。

f:id:kobatom5278:20210211131519p:plain

HC-SR04

 

3. Pythonプログラミング

  仕様はシンプルに、0.5秒ごとに距離を測定、20回ほど繰り返します。

  結果はターミナルに表示されます。

  なお、距離の算出は「乾燥空気」での音速、「33145 [cm/S]」を基にしています。

  正確な算出には、温度(室温)や湿度を加味する必要があるようです。

  今回は、精度はあまり気にせず、センサーを動かすことを目的とします。

  その精度についてですが、センサーのしくみ、超音波の跳ね返りを利用するためか、服を着た状態ではあまりうまく測れていないようです。

 

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

# 使用するライブラリのインポート
import time
import RPi.GPIO as GPIO

# 定数の定義
PIN_TRIG = 24
PIN_ECHO = 25
# 音速(cm / s)
SPEED_OF_SOUND = 33145

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():
    # 0.5秒ほど落ち着かせれる
    GPIO.output(PIN_TRIG, GPIO.LOW)
    time.sleep(0.5)
    # 0.00001秒間、超音波を発射
    GPIO.output(PIN_TRIG, True)
    time.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 = SPEED_OF_SOUND * time_passed / 2
    return distance

init_GPIO()

for measure in range(20):
    # 0.5 * 20 で、10秒間、0.5秒間隔で測定結果を表示
    # 測定結果が約0.5秒で取得されるので、
    # 0.5秒毎に距離が測定される
    distance = f'{calc_distance():.3f}'
    print(f'distance: {distance: >7} cm')

GPIO.cleanup()
    

Raspberry Pi Python GUI アプリケーション 06:簡易電卓

ボタンやテキストボックスを表示できるようになったので、簡易電卓を作ってみました。

あまり気の利いた機能はなく、計算自体は「eval」関数任せ。

画面下部のボタンで計算式を入力、「=」ボタンクリックで計算してくれます。

画面上部のテキストボックスに直接数式を入力しての計算もできます。

 

今回は以下の内容で処理を書いてみました。

・外部ライブラリは使用しない。標準のみ。

・GU部品のライブラリは、前回のものをそのまま利用。(使用しない関数もそのまま)

・フレームを上下2つにし、グリッドを別にした。

・ボタン配置はグリッドレイアウトを利用。ラクチン

・ボタンのクリックイベントでの処理の実行。クリックされたボタンのテキストで処理分岐。

 

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

# 使用するライブラリのインポート
import tkinter as Tk

class Window(Tk.Tk):
    """
    TKクラスを継承、Windowクラスとする
    """
    
    def __init__(self, text):
        """
        ウィンドウを生成する(タイトルを指定)
        """
        Tk.Tk.__init__(self)
        self.title(text)

    def setSize(self, width, height, isCenter = True):
        """
        サイズを指定、ディスプレイ中央に表示する
        """
        # サイズを指定した文字列
        geometry = "{0}x{1}".format(width, height)

        if isCenter:    # ディスプレイ中央に表示する            
            # ディスプレイの幅と高さを取得
            s_width = self.winfo_screenwidth()
            s_height = self.winfo_screenheight()
            # 表示する左上の座標を算出        
            win_left = int((s_width - width) / 2)
            win_top = int((s_height - height) / 2)
            # 表示位置を指定した文字列を追加
            geometry += "+{0}+{1}".format(win_left, win_top)

        # サイズと表示位置を中央に指定(幅 x 高さ + 左位置 + 上位置)
        self.geometry(geometry)
    
    def disableMaximum(self):
        """
        最大化を制限する
        """
        self.resizable(False, False)

class guiParts():
    """
    GUI部品を追加表示する
    すべてグリッド、左寄せで表示
    """

    def addLabel(self, row, column, text):
        """
        ラベルを追加表示する
        """
        lbl = Tk.Label(self, text = text)
        lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.W)

        return lbl

    def addTextBox(self, row, column, width, text = None):
        """
        テキストボックスを追加表示する
        """
        textbox = Tk.Entry(self, width = width)
        textbox.grid(row = row, column = column, sticky = Tk.W)
        if text != None:
            textbox.insert(Tk.END, text)
        
        return textbox

    def addCheckBox(self, row, column, text, valiable = None):
        """
        チェックボックスを追加表示する
        """
        if valiable == None:
            valiable = Tk.BooleanVar()
        checkBox = Tk.Checkbutton(self, text = text, variable = valiable)
        checkBox.grid(row = row , column = column, sticky = Tk.W)

        return (checkBox, valiable)

    def addRadioButton(self, row, column, text, onValue, valiable = None):
        """
        ラジオボタンを追加表示する
        """
        if valiable == None:
            valiable = Tk.IntVar()
        rbn = Tk.Radiobutton(self, text = text, value = onValue, variable = valiable)
        rbn.grid(row = row, column = column, sticky = Tk.W)

        return (rbn, valiable)

    def addButton(self, row, column, text, width = None):
        """
        ボタンを追加表示する
        """
        btn = Tk.Button(self, text = text, width = width)
        btn.grid(row = row, column = column, sticky = Tk.W)

        return btn

    def addListBox(self, row, column, items):
        """
        リストボックスを追加表示する
        """
        lbx = Tk.Listbox(self, height = len(items))
        lbx.grid(row = row, column = column, sticky = Tk.W + Tk.N)
        lbx.insert(Tk.END, *items)

        return lbx

    def addMenu(self, text, items, commands, mbar = None):
        """
        メニューを追加する
        """
        if mbar == None:
            mbar = Tk.Menu(self)
        mnu = Tk.Menu(mbar)
        for index, item in enumerate(items):
            label = item
            command = commands[index]
            if label == '':
                mnu.add_separator()
            else:
                mnu.add_command(label = label, command = command)
        mbar.add_cascade(label = text, menu = mnu)
        w.config(menu = mbar)
        return mbar
 
# 継承した場合など、実行されないようにする(現状、意味は特にない)
if __name__ == "__main__":
    # ウィンドウを生成、各設定
    w = Window('超簡易電卓')
    w.setSize(500, 160)
    w.disableMaximum()

    # フレームを生成(入力内容、回答表示用)
    f1 = Tk.Frame(w)
    txtAns = guiParts.addTextBox(f1, 0, 0, 40, '')
    f1.pack()

    # フレームを生成(電卓ボタン部)
    f = Tk.Frame(w)

    def clickButton(event):
        """
        入力内容を制御
        """

        # 計算不能エラーメッセージ
        ERROR_MESSAGE = 'ERROR : '
        
        # クリックされたボタンのテキストを取得
        inputVal = event.widget['text']
        # テキストボックスの内容を取得
        resultVal = txtAns.get()

        # 直近の操作が計算不能エラーの場合はクリアする
        if str(resultVal).startswith(ERROR_MESSAGE):
            txtAns.delete(0, Tk.END)

        # ボタンごとに分岐
        if inputVal == '=':
            # 計算、表示
            try:
                result = eval(resultVal)
                txtAns.delete(0, Tk.END)
                txtAns.insert(Tk.END, result)
            except:
                # 計算できなかった場合は先頭に計算不能エラーメッセージを表示
                txtAns.insert(0, ERROR_MESSAGE)

        elif inputVal == 'CL':
            # 入力内容クリア
            txtAns.delete(0, Tk.END)

        elif (inputVal in ['+', '-', '*', '/']):
            # 算術記号の場合は前後に半角スペースを開ける
            txtAns.insert(Tk.END, f' {inputVal} ')

        else:
            # 以外の場合(数字ボタンが押された)
            txtAns.insert(Tk.END, inputVal)

    # 部品を追加、表示、左クリックイベントをバインド
    guiParts.addButton(f, 3, 0, '0', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 2, 0, '1', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 2, 1, '2', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 2, 2, '3', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 1, 0, '4', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 1, 1, '5', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 1, 2, '6', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 0, 0, '7', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 0, 1, '8', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 0, 2, '9', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 0, 3, '+', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 1, 3, '-', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 2, 3, '*', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 3, 3, '/', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 3, 2, '=', 10).bind('<1>', clickButton)
    guiParts.addButton(f, 3, 1, 'CL', 10).bind('<1>', clickButton)

    f.pack()
    w.mainloop()

Raspberry Pi Python GUI アプリケーション 05:まとめ

前回と前々回の内容をまとめて、一度書き直しました。

特に新しいものはありません。

 

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

# 使用するライブラリのインポート
import tkinter as Tk

# 表示するウィンドウの幅と高さ
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 220

class Window(Tk.Tk):
    """
    TKクラスを継承、Windowクラスとする
    """
    
    def __init__(self, text):
        """
        ウィンドウを生成する(タイトルを指定)
        """
        Tk.Tk.__init__(self)
        self.title(text)

    def setSize(self, width, height, isCenter = True):
        """
        サイズを指定、ディスプレイ中央に表示する
        """
        # サイズを指定した文字列
        geometry = "{0}x{1}".format(width, height)

        if isCenter:    # ディスプレイ中央に表示する            
            # ディスプレイの幅と高さを取得
            s_width = self.winfo_screenwidth()
            s_height = self.winfo_screenheight()
            # 表示する左上の座標を算出        
            win_left = int((s_width - width) / 2)
            win_top = int((s_height - height) / 2)
            # 表示位置を指定した文字列を追加
            geometry += "+{0}+{1}".format(win_left, win_top)

        # サイズと表示位置を中央に指定(幅 x 高さ + 左位置 + 上位置)
        self.geometry(geometry)
    
    def disableMaximum(self):
        """
        最大化を制限する
        """
        self.resizable(False, False)

class guiParts():
    """
    GUI部品を追加表示する
    すべてグリッド、左寄せで表示
    """

    def addLabel(self, row, column, text):
        """
        ラベルを追加表示する
        """
        lbl = Tk.Label(self, text = text)
        lbl.grid(row = row, column = column, pady = 5, padx = 20, sticky = Tk.W)

        return lbl

    def addTextBox(self, row, column, width, text = None):
        """
        テキストボックスを追加表示する
        """
        textbox = Tk.Entry(self, width = width)
        textbox.grid(row = row, column = column, sticky = Tk.W)
        if text != None:
            textbox.insert(Tk.END, text)
        
        return textbox

    def addCheckBox(self, row, column, text, valiable = None):
        """
        チェックボックスを追加表示する
        """
        if valiable == None:
            valiable = Tk.BooleanVar()
        checkBox = Tk.Checkbutton(self, text = text, variable = valiable)
        checkBox.grid(row = row , column = column, sticky = Tk.W)

        return (checkBox, valiable)

    def addRadioButton(self, row, column, text, onValue, valiable = None):
        """
        ラジオボタンを追加表示する
        """
        if valiable == None:
            valiable = Tk.IntVar()
        rbn = Tk.Radiobutton(self, text = text, value = onValue, variable = valiable)
        rbn.grid(row = row, column = column, sticky = Tk.W)

        return (rbn, valiable)

    def addButton(self, row, column, text, width = None):
        """
        ボタンを追加表示する
        """
        btn = Tk.Button(self, text = text, width = width)
        btn.grid(row = row, column = column, sticky = Tk.W)

        return btn

    def addListBox(self, row, column, items):
        """
        リストボックスを追加表示する
        """
        lbx = Tk.Listbox(self, height = len(items))
        lbx.grid(row = row, column = column, sticky = Tk.W + Tk.N)
        lbx.insert(Tk.END, *items)

        return lbx

    def addMenu(self, text, items, commands, mbar = None):
        """
        メニューを追加する
        """
        if mbar == None:
            mbar = Tk.Menu(self)
        mnu = Tk.Menu(mbar)
        for index, item in enumerate(items):
            label = item
            command = commands[index]
            if label == '':
                mnu.add_separator()
            else:
                mnu.add_command(label = label, command = command)
        mbar.add_cascade(label = text, menu = mnu)
        w.config(menu = mbar)
        return mbar
 
def printSelectMenu1():
    print('メニューが選択されました1')
def printSelectMenu2():
    print('メニューが選択されました2')

# 継承した場合など、実行されないようにする(現状、意味は特にない)
if __name__ == "__main__":
    # ウィンドウを生成、各設定
    w = Window('メインウィンドウ')
    w.setSize(WINDOW_WIDTH, WINDOW_HEIGHT)
    w.disableMaximum()

    # メニューを設定
    mbar = guiParts.addMenu(w, 'Menu1', ['ABC', '', 'DEF', 'GHI'], [None, None, printSelectMenu1, None])
    mbar = guiParts.addMenu(w, 'Menu2', ['AAA', 'BBB', '', 'DDD'], [printSelectMenu2, None, None, None], mbar)

    # フレームを生成
    f = Tk.Frame(w)

    # 部品を追加、表示
    # ラベル + テキストボックス
    guiParts.addLabel(f, 1, 0, 'テキストボックス:')
    guiParts.addTextBox(f, 1, 1, 20, 'ABC')

    # ラベル + チェックボックス
    guiParts.addLabel(f, 2, 0, 'チェックボックス:')
    chk1 = guiParts.addCheckBox(f, 2, 1, 'チェックボックス1')
    chk1[1].set(False)
    chk2 = guiParts.addCheckBox(f, 2, 2, 'チェックボックス2')
    chk2[1].set(True)
   
    # ラベル + ラジオボタン
    guiParts.addLabel(f, 3, 0, 'ラジオボタン:')
    # ラジオボタン初期値オブジェクト(このオブジェクトを共有するラジオボタンがトグルとなる)
    valRadio = Tk.IntVar()
    guiParts.addRadioButton(f, 3, 1, 'ラジオボタン1', 1, valRadio)
    guiParts.addRadioButton(f, 3, 2, 'ラジオボタン2', 2, valRadio)
    valRadio.set(1)
    
    guiParts.addLabel(f, 4, 0, 'ボタン:')
    guiParts.addButton(f, 4, 1, 'ボタン', 10)

    guiParts.addLabel(f, 5, 0, 'リストボックス:')
    lbc = guiParts.addListBox(f, 5, 1, ['ABC', 'DEF', 'GHI'])
    lbc.select_set(1, 2)        # index = 1 ~ 2 のアイテムを選択状態に
    print(lbc.curselection())   # 選択されているアイテムを取得

    f.pack()
    w.mainloop()