RaspberryPiとDS18B20で温度を計ってdygraphsでグラフ化

少し前ですが、あまりにも暑かったので、暑さ自慢(不幸自慢)するために部屋の温度を計ることにしました。Raspberry Piで。
(あの日以降、北海道は割と寒いですが・・・)

RaspberryPiとDS18B20を使って温度を計る

1. 電子回路はよくわからないので、色々と参考にしながらDS18B20をRasbperryPiにつなぎます。

Raspberry Piブログ : [コラム] 第9回『1-wire温度センサーで部屋の温度を測定しよう』

2. 接続したら/etc/modulesに以下を追加して保存します。::

[code]
w1-gpio
w1-therm
[/code]

3. 再起動します。

[bash]
sudo shutdown -r now
[/bash]

4. /sys/bus/w1/devicesの中に接続したデバイスのIDでリンクができているので、その中のw1_slaveをcatします。

[bash]
cat /sys/bus/w1/devices/28-*****/w1_slave
[/bash]

5. 温度が表示されます。

[bash]
ef 01 4b 46 7f ff 01 10 f5 : crc=f5 YES
ef 01 4b 46 7f ff 01 10 f5 t=30937
[/bash]

t=30937のところが温度で、1/1000にして摂氏30.937となります。

データを定期的に記録する

温度が取得できることがわかったので、定期的に温度を記録します。
t=30937の部分を取り出してファイルに書くだけの簡単なお仕事です。

t=30937の部分を取り出す処理
[python]
with open(‘/sys/bus/w1/devices/28-000**********/w1_slave’) as f:
data = f.read()
temp = float(data[data.index(‘t=’)+2:])/1000

print temp
[/python]

これで温度だけを記録できるようになりました。
あとは時刻も取得して、CSVの1行として出力します。

temp.py
[python]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import datetime

class TZ(datetime.tzinfo):
def __init__(self, name, offset):
self.name = name
self.offset = offset
def utcoffset(self, dt):
return datetime.timedelta(hours=self.offset)
def tzname(self, dt):
return self.name
def dst(self, dt):
return datetime.timedelta(0)

def main():
path = ‘/sys/bus/w1/devices/28-000*********/w1_slave’
with open(path) as f:
data = f.read()
temp = float(data[data.index(‘t=’)+2:])/1000

FMT = ‘{},{:.1f}’
JST = TZ(‘JST’, 9)
now = datetime.datetime.now(JST)

print(FMT.format(now.strftime("%Y/%m/%d %H:%M:%S"), temp))

if __name__ == ‘__main__’:
main()
[/python]

これは、次のように実行すると時刻と温度がカンマ区切りで出力されます。
[bash]
$ python temp.py
2014/06/15 23:05:06,23.2
[/bash]

これをcronを使って定期的に実行します。

[bash]
crontab -e
[/bash]

cronの設定を編集します。
[bash]
*/10 * * * * python /path/to/temp.py >> /path/to/temp.csv
[/bash]

これで10分おきに温度を記録できるようになりました。

記録した温度のデータをグラフにする

取得したデータはtemp.csvとして残っていますが、エディタでしか見れませんので、ウェブページからグラフで確認できるようにします。

D3.js(C3.js)を使って描画してはみたものの、
そんなにすごいことができなくてもいいので、dygraphs.com を使うことにしました。

dygraphsとは

dygraphsは高速かつ簡単にグラフを作成できるライブラリです。
さくっとグラフを描画するだけなら、D3.jsよりも簡単です。

1. dygraphs.com/download.htmlからdygraph-combined.jsをダウンロードします。

2. 次のようなhtmlを作成します(dygraph-combined.jsとtemp.csvはstatic配下に保存してあります)。

[html]
<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>

<div id="chart"></div>

<script src="/static/dygraph-combined.js’"></script>
<script>
var chart_element = document.getElementById(‘chart’);
var chart = new Dygraph(
chart_element,
"/static/temp.csv",
{}
);
</script>

</body>
</html>
[/html]

4. これをブラウザで確認すると次のようなグラフが表示されます。

Temperature at my work space

なんとなく部屋の温度がわかっていいですね。

しかし思ってたより暑くはなく、不幸自慢できませんでした。

まとめ

raspberry piとDS18B20ですごく簡単に温度を計ることが、また温度のデータをグラフ化することができました。
しかし、このままではCSVがどんどん大きなサイズになり、読み込みが遅くなりそうです。
遅くなったらsqlite3あたりにつっこんで、日付で抽出できるようにすればよいですね。

温度がわかるようになると今度は湿度もほしいなぁ・・・
あとこれをpebbleにも表示できるようにしたいなぁ・・・

python3でのstdin,out,errのencodingの変更

暑くてとろけております。
涼しいはずの北海道で全国最高気温(と最低気温)とは・・・。

python3でのopen

python3からopenの仕様が変更となり、text modeの場合(mode=rやwだけで開いた場合)には、encoding付きでopenするようになっています。

返されるオブジェクトもfileオブジェクトではなくTextIOWrapper, BufferedReaderまたはBufferedWriterとなっています。


[code]
 >>> f = open(‘sample.csv’, ‘r’)
>>> f
<_io.TextIOWrapper name=’sample.csv’ mode=’r’ encoding=’UTF-8′>
>>> f.close()

>>> f = open(‘sample.csv’, ‘w’)
>>> f
<_io.TextIOWrapper name=’sample.csv’ mode=’w’ encoding=’UTF-8′>
>>> f.close()

>>> f = open(‘sample.csv’, ‘a’)
>>> f
<_io.TextIOWrapper name=’sample.csv’ mode=’a’ encoding=’UTF-8′>
>>> f.close()

>>> f = open(‘sample.csv’, ‘r+’)
>>> f
<_io.TextIOWrapper name=’sample.csv’ mode=’r+’ encoding=’UTF-8′>
>>> f.close()

>>> f = open(‘sample.csv’, ‘w+’)
>>> f
<_io.TextIOWrapper name=’sample.csv’ mode=’w+’ encoding=’UTF-8′>
>>> f.close()

>>> f = open(‘sample.csv’, ‘rb’)
>>> f
<_io.BufferedReader name=’sample.csv’>
>>> f.close()

>>> f = open(‘sample.csv’, ‘wb’)
>>> f
<_io.BufferedWriter name=’sample.csv’>
>>> f.close()
[/code]

python3の標準入出力のencodingを切り替える

上述のとおりstdin, stdoutにencodingが付いているわけですが、これのデフォルトのencodingは環境に依存しています。

私の環境ではUTF-8がデフォルトのencodingとして設定しているので、何も指定しない状態ではUTF-8でstdinを読もうとします。

例えば、SJISのファイルをcatしてstdinから読み込むとどうなるかというと、

sample1.py

[python]
 from __future__ import print_function
import sys
print(sys.stdin.read())
[/python]

実行

[code]
 cat sample.csv | python3 sample1.py

Traceback (most recent call last):
File "sample1.py", line 2, in <module>
print(sys.stdin.read())
File "/usr/local/Cellar/python3/3.4.0_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/codecs.py", line 313, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0x82 in position 3: invalid start byte
[/code]

SJISの文字列を渡した場合、Decodeできなかったと怒られます。

ちなみにpython2で同じことをすると文字化けしました。

[code]

1, �ق��ق�
2, �ӂ��ӂ�

[/code]

python2の場合、この問題を解決するために次のようにしていました。

sample2.py::

[python]
 from __future__ import print_function
import sys
import codecs

_stdin = codecs.getreader(‘sjis’)(sys.stdin)
print(_stdin.read())
[/python]

これを実行すると::

[code]
 cat sample.csv | python sample2.py
1, ほげほげ
2, ふがふが
[/code]

となります。
SJISをデコードして読み取ることができています。

これをpython3で実行すると::

[code]
 cat sample.csv | python3 sample2.py
Traceback (most recent call last):
File "sample2.py", line 6, in <module>
print(_stdin.read())
File "/usr/local/Cellar/python3/3.4.0_1/Frameworks/Python.framework/Versions/3.4/lib/python3.4/codecs.py", line 313, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0x82 in position 3: invalid start byte
[/code]

残念ながら使えません・・・。

そこで、思い出すのが、冒頭に書いていたfileが_io.TextIOWrapperに変わったということです。
text modeで開いた時はすでにTextIOWrapperオブジェクトになっているので、.bufferにアクセスして、それをcodecs.getreaderします。

sample3.py::

[python]
 import sys
import codecs

_stdin = codecs.getreader(‘sjis’)(sys.stdin.buffer)
print(_stdin.read())
[/python]

これで文字化けせずに読むことができるようになります。
しかし、調べて見ると他のやり方がもあるようです。

sample4.py::

[python]
 import sys
import io

_stdin = io.TextIOWrapper(sys.stdin.buffer, encoding=’sjis’)
print(_stdin.read())

[/python]

sample5.py::

[python]
 import sys
_stdin = open(sys.stdin.fileno(), ‘r’, encoding=’sjis’)
print(_stdin.read())
[/python]

どれも正常に動作しています。

となると、今度は結局どれを使えばいいの?となります。
私はgetreaderやらTextIOWrapperという単語を見るよりも、openでstdinをsjisで開き直す、という方がさっぱりしていて良いなと思ってます。

・・・ということで寝られるかと思ったら、何やら怪しい挙動が・・・

sample6.py::

[python]
 import sys
import codecs

_stdin = codecs.getreader(‘sjis’)(sys.stdin.buffer)
_stdout = codecs.getwriter(‘utf-8’)(sys.stdout.buffer)

for line in _stdin:
print(‘!!!’, file=sys.__stdout__)
print(‘???’, file=sys.__stderr__)
_stdout.write(line)
[/python]

実行すると::

[code]
 !!!
???
1, ほげほげ
!!!
???
2, ふがふが
[/code]

sample7.py::

[python]
 import sys
import io

_stdin = io.TextIOWrapper(sys.stdin.buffer, encoding=’sjis’)
_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding=’utf-8′)

for line in _stdin:
print(‘!!!’, file=sys.__stdout__)
print(‘???’, file=sys.__stderr__)
_stdout.write(line)
[/python]

実行すると::

[code]
 !!!
???
!!!
???
1, ほげほげ
2, ふがふが
[/code]

あれ???

sample8.py::

[python]
 import sys
_stdin = open(sys.stdin.fileno(), ‘r’, encoding=’sjis’)
_stdout = open(sys.stdout.fileno(), ‘w’, encoding=’utf-8′)

for line in _stdin:
print(‘!!!’, file=sys.__stdout__)
print(‘???’, file=sys.__stderr__)
_stdout.write(line)
[/python]

実行すると::

[code]
 !!!
???
1, ほげほげ
!!!
???
2, ふがふが
[/code]

なぜかTextIOWrapperのとき、出力順序が違っています。
なんとなくですが、TextIOWrapperを作りなおしている時に、元のバッファとは違うところを使っている、とかでしょうか?

sample7a.py::

[python]
 import sys
import io

_stdin = io.TextIOWrapper(sys.stdin.buffer, encoding=’sjis’)
_stdout = io.TextIOWrapper(sys.stdout.buffer, encoding=’utf-8′)

for line in _stdin:
print(‘!!!’, file=sys.__stdout__)
print(‘???’, file=sys.__stderr__)
_stdout.write(line)
_stdout.flush()
[/python]

実行すると::

!!!
???
1, ほげほげ
!!!
???
2, ふがふが

それっぽい結果がでました。これ以上の深追いはやめておきましょう・・・。

まとめ

どうしてもpython側でstd(in|out|err)の文字コードを吸収(変換)したいなら、今回書いた方法のどれかで変換すればよさそうです。

ただ、pythonでの文字コード変換が必須ではなく、コマンドラインで処理するスクリプトの場合には、iconvやnkfを使った方がpythonのコードもシンプルになります。

sample_simple.py::

[python]
import sys
print(sys.stdin.read())
[/python]

これをこう::

[code] cat sample.csv | iconv -f SJIS -t UTF-8 | python3 sample_simple.py
[/code]

というわけで、頑張った割に、最終的にはpythonで文字コード変換しない方が良い気がしてきました。
お疲れ様でした。。。

追記

 ずっと見てなかったcomp.lang.pythonのメールに何故か目を通してみたら、ちょうど関連する話がありました。

Reading from sys.stdin reads the whole file in – Google Groups

  • バッファリングしているのでflushする必要がある。
  • python -u sample.py のように-uをしていするとバッファリングされない
  • 環境変数 PYTHONUNBUFFERED を設定するとバッファリングされない
  • python3.3から print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False) のflushをTrueにすればすぐflushするみたい
  • python2でバッファリングなしでstdinの行ごとの処理をすると遅いし変なことしないとだめなのでオススメしない。python3を使おう。

参考

アプリケーション開始前の初期化段階では呼び出すことができません。

.Net MVC で認証をちょっと変更しようとMembershipProviderを作ってたらエラー。

アプリケーション開始前の初期化段階では呼び出すことができません。

ふむふむ・・・・・・・・・・・

結局わからず、日本語を適当に英語にして検索してstackoverflow様。

ASP.NET: This method cannot be called during the application's pre-start initialization stage – Stack Overflow

[code language=”xml”]
<add key="enableSimpleMembership" value="false"/>
<add key="autoFormsAuthentication" value="false"/>
[/code]

PDFから取得した時刻表テキストから、使いやすいデータを抽出する

気温の変化の激しさに負け、風邪をひいて喉が痛いです。
週末を風邪でぐったり過ごすと損した気分です!

さて、地道に作成しているPebbleの札幌東豊線の時刻表アプリ、を動かすために必要なデータを作るためにチマチマ進めている PDFからテキストを取得する PyPDF2 の続きです。

ソース

planset/tohosendata です。

やはりこういった電車の時刻表はオープンデータ化してほしいですね。
どこかにあるのかなー?

テキストから時刻表を抽出する

前回、PDFからテキストを取得することができました。

[code]
WeekdaysSaturdays/Sundays/Holidays6 01020293846546 01020293847557 2 91419232731353943475155597 311192632384450568 3 711151923273135394347528 2 8142026334047549 0 7152230374451599 2 91724313845525910 61320273542495610 71421283542495611 31118253239465411 31118253239465412 1 81522303744515812 1 81522303744515813 61320273441495613 61320273441495614 31017253239465314 31017253239465315 1 81522303744525915 1 81522293644515816 6132128354146525716 51220273441485617 3 813182329344045515617 31017243139465318 1 6121723283439455218 0 71522293643515919 0 816243240485619 715243240485620 412202937455320 412202937455321 1 917253341495721 1 917253341495722 5132230405022 5132230405023 0102030405023 010203040500 00 0Last Trains for Transfers from This Station.For MiyanosawaFor FukuzumiFor Shin sapporoTransfer at OdoriTransfer at SapporoTransfer at OdoriTransfer at Odori
[/code]

ここからうまい具合に抽出しますが、詳細は省略。

正規表現でざっくり分けて、あとは1時刻ずつ読む感じです(うまく行ってる気がしません)。

苦労した点は、pdfをテキストに変換をすると、
同じ行が2回続いて出力されるPDFがあることです・・・。
PDFの作りの問題なのか、pypdf2の使い方の問題なのか・・・。

栄町分を出力してみるとこんな感じです。時刻表が正しいかはまだ確認していません。

[code]
“1”: {
“fukuzumi”: {
“holiday”: {
“23”: [
0,
10,
20,
30,
40,
50
],
“22”: [
5,
13,
22,
30,
40,
50
],
(省略)
},
“weekday”: {
“23”: [
0,
10,
20,
30,
40,
50
],
“22”: [
5,
13,
22,
30,
40,
50
],
(省略)
}
(省略)
[/code]

ここまでくればもうおっけーです。

あとは、django用のdbに取り込んで時刻検索して返してあげています。

まとめ

これで時刻表のデータが揃ったので、あとは電車に乗りながら動作確認です。
栄町<>さっぽろ間しか乗ることがないので、福住のデータは一生テストされませんけどね!