絖綛 N@i.jp  昨日:00020077
 今日:00036076
 総計:00150184
keywords
管理者専用
  Post   Add link   Control Panel 































新しいトピック
最新:04/16 19:55


新しいコメント
最新:07/28 16:47






管理人へMAIL

プライバシーポリシー

Raspberry Pi で温度センサーの値を読み取るプログラムを作ってみた

Python3でプログラムしてみたのだけど、これがなかなか難しかった


 Raspberry Pi 2に DS18B20という温度センサーを取り付けましたので、今回はセンサーから温度を取得するプログラムを作ってみようと思います。
 その前に自分の備忘録として OS(Raspbian)の設定について書いておきましょ。1-wireデバイスと通信するためには、以下のようにカーネルモジュールの読み込み設定を行います。

  1. Raspbianの最新化
     念のためRaspberry PiのOSを最新化しておきます。rootになって、
    # apt-get update
    # apt-get upgrade
  2. "/etc/modules"の編集
     "/etc/modules" に、ブート時に読み込むカーネルモジュール(赤字の部分)を追加します。
    # /etc/modules: kernel modules to load at boot time.
    #
    # This file contains the names of kernel modules that should be loaded
    # at boot time, one per line. Lines beginning with "#" are ignored.
    # Parameters can be specified after the module name.
    
    snd-bcm2835
    w1-gpio
    w1-therm
    
  3. "/boot/config.txt"の編集
     "/boot/config.txt" にデバイスの設定を追加します。
    # For more options and information see
    # http://www.raspberrypi.org/documentation/configuration/config-txt.md
    # Some settings may impact device functionality. See link above for details
    〜略〜
    dtoverlay=w1-gpio-pullup,gpiopin=4
    これで "/boot/overlays/w1-gpio-pullup-overlay.dtb" が読み込まれて GPIO 4番端子で通信できるようになるらしいです。
  4. 再起動
     カーネルモジュールを読み込ませるため、Raspberry Piを再起動します。
    # reboot

 以上でOS(Raspbian)の設定は完了。DS18B20が認識されているか確認します。"/sys/bus/w1/devices/" 配下に "28-000" で始まるディレクトリ(実際にはシンボリックリンク)が出来ていれば成功。ウチの場合は、

$ cd /sys/bus/w1/devices/
$ ls -l
lrwxrwxrwx 1 root root 0  6月 27 18:04 28-000005cf8ede -> ../../../devices/w1_bus_master1/28-000005cf8ede
lrwxrwxrwx 1 root root 0  6月 27 18:04 w1_bus_master1 -> ../../../devices/w1_bus_master1

となっていました。DS18B20は各々固有のデバイスIDを持っているそうで、"28-"の後ろの16進がデバイスIDらしいです。あとは、このディレクトリ配下にある "w1_slave" という名前の仮想ファイルを読めば DS18B20と通信されて、

$ cat /sys/bus/w1/devices/28-000005cf8ede/w1_slave
bc 01 4b 46 7f ff 04 10 d2 : crc=d2 YES
bc 01 4b 46 7f ff 04 10 d2 t=27750

という具合に読み込むことができます。2行目の t= の後の値が温度で、1,000倍された値になっています。上の場合、現在の室温は摂氏 27.75度ということになります。
 温度センサーの値を読むプログラムは、"/sys/bus/w1/devices/28-000005cf8ede/w1_slave" という名前の仮想ファイルを読み込み、t= の後ろにある数字列を数値に変換して 1,000で割った値を表示すれば良いことになります。これを Python3 で書くわけですが、やる事は簡単だし楽勝、楽勝。勉強用の課題としては物足りないかと思ったのですが、甘かった・・・

 t= の後ろの数字列を取り出すには正規表現を使えば良いんではないかい?先ずは一番最初に書いたやつがコレ。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import re

DEVICE='28-000005cf8ede'
with open('/sys/bus/w1/devices/'+DEVICE+'/w1_slave', mode='rt') as infile:
    for line in infile:
        if re.search(r't=[0-9]+', line):
            temperature=re.sub(r't=([0-9]+)', r'1', line)
            print('{0:2.3f}'.format(float(temperature)/1000))

¥ は実際には半角の\(バックスラッシュ)です。実行してみると、

Traceback (most recent call last):
  File "./ambient_temp0.py", line 12, in 
    print('{0:2.3f}'.format(float(temperature)/1000))
ValueError: could not convert string to float: 'bc 01 4b 46 7f ff 04 10 d2 27750
'

うまく動かん。"t=数字列" にマッチした行について、re.sub() で t= の後ろの数字列を後方参照(r'1')する事で取り出そうとしたのだけど、"t=" だけが取り除かれた文字列になってしまいました。何が悪いのかなぁ〜 re.sub() はマッチしない部分については、何もせずそのまま文字列を返すってことかな。(←そうです。だから正しくは re.sub(r'^.*t=([0-9]+)', r'1', line) とすれば良かったのでした。)

 原因が分からんまま書き直したプログラムが以下。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import re

DEVICE='28-000005cf8ede'
with open('/sys/bus/w1/devices/'+DEVICE+'/w1_slave', mode='rt') as infile:
    for line in infile:
        match=re.search(r't=[0-9]+', line)
        if match:
            temperature=re.sub(r't=', '', match.group(0))
            print('{0:2.3f}'.format(float(temperature)/1000))

re.search の結果(マッチオブジェクト)から、マッチした部分(グループ0、すなはち t=数字列)を取り出し、"t=" を削除するようにしてみました。これだとちゃんと動いてくれます。

$ ./ambient_temp1.py
28.062

 もうちょっと書き換えてみます。"t=数字列" があるのは必ず2行目ですから、全部読み込んでから2行目に対してだけ処理するようにしてみました。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import re

DEVICE='28-000005cf8ede'
with open('/sys/bus/w1/devices/'+DEVICE+'/w1_slave', mode='rt') as infile:
    text = infile.read()

secondline = text.split('')[1]
match=re.search(r't=([0-9]+)', secondline)
if match:
    temperature=match.expand(r'1')
    print('{0:2.3f}'.format( float(temperature)/1000 ))

2行目に対して "t=数字列" とマッチさせ、そのマッチオブジェクトの expand() メソッドで数値列の部分を後方参照で取り出しています。これも正しく動きました。


 という具合に色々試行錯誤するまでもなく、先人達によって便利なモジュールが作成されていまして、これを使用すると実に簡単なプログラムになります。
 先ず "w1thermosensor" というモジュールをインストールします。

$ git clone https://github.com/timofurrer/w1thermsensor.git
Cloning into 'w1thermsensor'...
remote: Counting objects: 254, done.
remote: Total 254 (delta 0), reused 0 (delta 0), pack-reused 254
Receiving objects: 100% (254/254), 51.87 KiB, done.
Resolving deltas: 100% (110/110), done.
$ cd w1thermsensor/
$ sudo python3 setup.py install
running install
running build
running build_py
creating build/lib
creating build/lib/w1thermsensor
copying w1thermsensor/core.py -> build/lib/w1thermsensor
copying w1thermsensor/__init__.py -> build/lib/w1thermsensor
running install_lib
creating /usr/local/lib/python3.2/dist-packages/w1thermsensor
copying build/lib/w1thermsensor/core.py -> /usr/local/lib/python3.2/dist-packages/w1thermsensor
copying build/lib/w1thermsensor/__init__.py -> /usr/local/lib/python3.2/dist-packages/w1thermsensor
byte-compiling /usr/local/lib/python3.2/dist-packages/w1thermsensor/core.py to core.cpython-32.pyc
byte-compiling /usr/local/lib/python3.2/dist-packages/w1thermsensor/__init__.py to __init__.cpython-32.pyc
running install_egg_info
Writing /usr/local/lib/python3.2/dist-packages/w1thermsensor-0.2.2.egg-info

 このモジュールを使用すると、プログラムは以下のようになります。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
from w1thermsensor import W1ThermSensor

sensor = W1ThermSensor()
print('{0:2.3f}'.format(sensor.get_temperature()))

 今までジタバタしていたのは何だったのかと思うくらい簡単になりました。しかもデバイスIDさえ指定してません。デバイスIDを自動で探してくれるのですって。素晴しい!しかも DS18S20, DS1822, DS18B20 の三種類の1-wireデバイスをサポートしており、複数のデバイスにも対応しているとの事。何て素晴しい!
 デバイスの種類を明に指定するには、

sensor = W1ThermSensor( W1ThermSensor.THERM_SENSOR_DS18B20 )

とすれば良く、複数のデバイスの温度を取得できるようにすると、

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
from w1thermsensor import W1ThermSensor

for sensor in W1ThermSensor.get_available_sensors([W1ThermSensor.THERM_SENSOR_DS18B20]):
    print('Sensor {0} has temperature {1:2.3f}'.format(sensor.id, sensor.get_temperature()))

こうなります。これを実行させると、

$ ./ambient_temp5.py
Sensor 000005cf8ede has temperature 28.062

今はデバイスが1つしか無いので1行しか出力されませんが、デバイスが複数あるとデバイスの数だけ出力されるようになるはず。これが一番汎用性が高いプログラムですかね。

 正規表現では、他にも unicode関連で Syntax errorが出たりして色々悩んだけど、まぁ勉強にはなったかな。やはり楽にプログラムを書くには、既存のモジュール、クラスライブラリ等をどれだけ知っているかが重要ですね。


< 過去の記事 [ 4月の 全てのカテゴリ リスト ] 新しい記事 >

2024 calendar
4月
123456
78910111213
14151617181920
21222324252627
282930


掲示板
最新:08/15 17:19


GsBlog was developed by GUSTAV, Copyright(C) 2003, Web Application Factory All Rights Reserved.