ラズベリーパイ4は他のシリーズに比べ、CPU性能も高く発熱量も多いのでCPUファン付きのケースに入れていました。ファンの電源はGPIOの5Vに接続し、常時回していました。あるとき、ラズベリーパイの設定にCPUが何度になったらファンを回す、という設定があるのに気が付きました。試してみるとちゃんと動作しています。
CPUの温度で動作するという事は、今何度かわかる方法があるはずです。調べてみると以下のコマンドがありました。
$ sudo vcgencmd measure_temp
temp=57.4’C
気が付くと家にあるラズベリーパイは7台にもなっていました。
居間(Living):居間に防犯カメラのモニターとして常時稼働(Pi4:4GB)
玄関(Entrance):玄関の防犯カメラとして常時稼働(Pi3)
玄関2(Entrance-2):側面方向からみた玄関の防犯カメラとして常時稼働(PiZero)
実験用Pi3(Pi3 Work):実験用としているが、2階のカメラモニタとして半常時稼働 (Pi3)
実験用Pi4(Pi4 Work):実験用として必要時稼働 (Pi4:4GB)
テンキー用 (Alexa Key):テンキーによるAlexa定型アクション起動用として常時稼働 (PiZero2)
庭(Garden):たまにやってくる動物を捉えようと設置したカメラ用。常時稼働(Pi4:2GB)
上記のラズベリーパイの全部のCPU温度を時系列でグラフにしてみたくなりました。居間のNature Remo miniにも室温を計測する機能がありました。室温もグラフに加えます。最近2階用にスマートリモコンEZCONを買ったのですが、これには室温計測機能が無いようです。本当はNature Remoを買い増そうかと思ったのですが、去年買った時よりずいぶん高くなっていたので止めて安いものを買ったのです。
毎時0分と30分の30分おきにデータを収集することにしました。プログラムは以下のようになります。
data_collect.py
from paramiko import SSHClient, AutoAddPolicy, SSHException, AuthenticationException, ssh_exception
import socket
import re
from urllib.parse import urlencode
from urllib.request import urlopen, Request
from urllib.error import HTTPError
from json import loads
import os
from datetime import datetime
import schedule
import time
PI_NAME = ['Living', 'Entrance', 'Entrance-2', 'Pi3 Work', 'Pi4 Work', 'Alexa Key', 'Garden', 'Room']
PI_ADR = ['192.168.xx.xx', '192.168.xx.xx', '192.168.xx.xx', '192.168.xx.xx', '192.168.xx.xx', '192.168.xx.xx', '192.168.xx.xx']
PORT = 22
USER = 'pi'
PASSWORD = 'xxx'
TIMEOUT = 5
CMD_CPU = 'sudo vcgencmd measure_temp'
api_key = ""
url = "https://api.nature.global/1/devices/"
headers = {
"accept" :"application/json",
"Authorization" :"Bearer " + api_key,
}
DATA_FILE = '/var/www/html/temperature.csv'
ERROR_FILE = './errorlog.txt'
DATA_NUM = 96
def savedata(data):
list = []
if os.path.exists(DATA_FILE):
f = open( DATA_FILE, 'r' )
for line in f.readlines():
list.append(line)
else:
f.close()
start = 1
num = len(list)
if num >= (DATA_NUM + 1):
start = num - DATA_NUM + 1
f = open( DATA_FILE, 'w' )
header = 'time,'
for i in range(len(PI_NAME)):
if i != 0:
header += ','
header += PI_NAME[i]
f.write(header + '\n')
for i in range(start, num):
f.write( list[i] )
f.write( datetime.now().strftime('%Y/%m/%d %H:%M,') + data + '\n' )
f.close()
def errorlog(log):
f = open(ERROR_FILE, 'a')
f.write( datetime.now().strftime('%Y/%m/%d %H:%M:%S:') + log + '\n' )
f.close()
def cpu_temp():
ret = ''
ssh = SSHClient()
for name, adr in zip(PI_NAME, PI_ADR):
try:
ssh.set_missing_host_key_policy(AutoAddPolicy())
ssh.connect(adr, PORT, USER, PASSWORD, timeout=TIMEOUT)
stdin, stdout, stderr = ssh.exec_command(CMD_CPU)
except AuthenticationException as e:
errorlog(name + ':AuthenticationException:' + e)
ret += ','
except SSHException as e:
errorlog(name + ':SSHException:' + e)
ret += ','
except ssh_exception.NoValidConnectionsError as e:
ret += ','
except socket.timeout:
ret += ','
else:
temp = re.sub('\'C\n', '', re.sub('temp=', '', stdout.readline()))
ret += str(temp) + ','
finally:
ssh.close()
return ret
def room_temp():
ret = ''
request = Request(url, headers=headers)
try:
with urlopen(request) as response:
data_byte = response.read()
data= loads(data_byte)
device_info = data[0]["newest_events"]
ret = str(device_info["te"]["val"])
except HTTPError as e:
errorlog('Room:HTTPError:' + e)
ret = ''
finally:
response.close()
return ret
def correct():
data1 = cpu_temp()
data2 = room_temp()
savedata(data1 + data2)
# every 30minute
schedule.every().day.at('00:00').do(correct)
schedule.every().day.at('00:30').do(correct)
schedule.every().day.at('01:00').do(correct)
schedule.every().day.at('01:30').do(correct)
schedule.every().day.at('02:00').do(correct)
schedule.every().day.at('02:30').do(correct)
schedule.every().day.at('03:00').do(correct)
schedule.every().day.at('03:30').do(correct)
schedule.every().day.at('04:00').do(correct)
schedule.every().day.at('04:30').do(correct)
schedule.every().day.at('05:00').do(correct)
schedule.every().day.at('05:30').do(correct)
schedule.every().day.at('06:00').do(correct)
schedule.every().day.at('06:30').do(correct)
schedule.every().day.at('07:00').do(correct)
schedule.every().day.at('07:30').do(correct)
schedule.every().day.at('08:00').do(correct)
schedule.every().day.at('08:30').do(correct)
schedule.every().day.at('09:00').do(correct)
schedule.every().day.at('09:30').do(correct)
schedule.every().day.at('10:00').do(correct)
schedule.every().day.at('10:30').do(correct)
schedule.every().day.at('11:00').do(correct)
schedule.every().day.at('11:30').do(correct)
schedule.every().day.at('12:00').do(correct)
schedule.every().day.at('12:30').do(correct)
schedule.every().day.at('13:00').do(correct)
schedule.every().day.at('13:30').do(correct)
schedule.every().day.at('14:00').do(correct)
schedule.every().day.at('14:30').do(correct)
schedule.every().day.at('15:00').do(correct)
schedule.every().day.at('15:30').do(correct)
schedule.every().day.at('16:00').do(correct)
schedule.every().day.at('16:30').do(correct)
schedule.every().day.at('17:00').do(correct)
schedule.every().day.at('17:30').do(correct)
schedule.every().day.at('18:00').do(correct)
schedule.every().day.at('18:30').do(correct)
schedule.every().day.at('19:00').do(correct)
schedule.every().day.at('19:30').do(correct)
schedule.every().day.at('20:00').do(correct)
schedule.every().day.at('20:30').do(correct)
schedule.every().day.at('21:00').do(correct)
schedule.every().day.at('21:30').do(correct)
schedule.every().day.at('22:00').do(correct)
schedule.every().day.at('22:30').do(correct)
schedule.every().day.at('23:00').do(correct)
schedule.every().day.at('23:30').do(correct)
while True:
schedule.run_pending()
time.sleep(10)
このプログラムを動かすにはライブラリparamikoとurllib、scheduleのインストールが必要になります。インストールは以下のように行います。
$ pip install paramiko⏎
$ pip install urllib3⏎
$ pip install schedule⏎
プログラムの説明
PI_NAME =で温度計測対象の名称を配列で設定しています。
PI_ADR =は各ラズベリーパイのアドレスを配列で定義しています。
PORT =はSSH接続するときのポート番号です。
USER =はSSH接続するときのユーザー名、PASSWORD =はパスワードです。
TIMEOUT =はSSH接続する際のタイムアウト時間となります。全てのラズベリーパイが常時稼働しているわけではないため、応答がなければ早めに非稼働中とします。
api_key =はNature Remoのアクセストークンです。Nature APIを使用するには必要で、以下のサイトで取得します。
https://home.nature.global/
url =はNature APIのURL、headersはNature APIに必要なヘッダー情報となります。
DATA_FILE =はこのプログラムのOUTPUファイルとなります。HTMLでグラフ表示するため、/var/www/html/ディレクトリにCSV形式で保存します。
ERROR_FILE =はエラーが発生した時に内容を出力するファイルになります。
DATA_NUM=は保持する最大のデータ数になります。30分おきに1日48データ収集しますので、2日分のデータを保持する設定になっています。
プログラムは最初起動時にスケジューラで起動する時刻を設定します。
schedule.every().day.at(‘XX:XX’).do(correct)
48行書くのは野暮ったいですね。流石ヘボプログラマーです。
while文で無限ループし、10秒おきに起動判定を行います。
関数correct()では最初に各ラズベリーパイの温度を収集し、次に部屋の温度を収集し、最後にデータを保存します。
各ラズベリーパイの温度を収集する関数cpu_temp()ではSSH接続し、温度を取得するコマンドによりデータを取得しています。取得した文字列は「temp=57.4’C⏎」というフォーマットなので数字だけを取り出すために[temp=[と[‘C⏎]を取り除いています。データをCSVファイルで保存するために,(カンマ)で区切っています。timeout以外の予期せぬエラー(例外)はエラーログファイルに内容を出力しています。(この例外処理は発生したことがないので正常に動作するのか試したことはありません・・・)
関数room_temp()ではNature APIを使いデバイス情報をJSONフォーマットで取得します。{“newest_events”:{“te”:{“val”:}}}に格納されている部屋の温度を取得します。(参照:https://swagger.nature.global/#/default/get_1_devices)
関数savedata()では引数に与えられたデータに時刻を追加してCSVファイルに保存しています。直近96データより古いものは破棄しています。
プログラムが出力するcsvは以下のようになっています。
temperature.csv
time,Living,Entrance,Entrance-2,Pi3 Work,Pi4 Work,Alexa Key,Garden,Room⏎
2022/08/24 11:30,52.1,45.1,50.8,62.3,,53.2,72.5,26.5⏎
2022/08/24 12:00,54.5,47.8,,65.5,,53.2,73.0,27⏎
2022/08/24 12:30,56.9,45.1,,65.0,,53.7,70.1,28⏎
2022/08/24 13:00,55.0,46.2,48.7,62.3,,53.7,68.1,28.5⏎
2022/08/24 13:30,51.6,45.1,51.4,62.8,,53.2,67.2,28⏎
・・・後略・・・
プログラムは居間のラズベリーパイに稼働させることにしました。
apache2をインストールします。
$sudo apt install apache2
ラズベリーパイ起動時にプログラムを開始するようにします。
/etc/rc.localのexit 0より上に以下の1行を挿入し、start.shシェルスクリプトをバックグラウンドで起動します。
sudo sh /home/pi/start.sh &
シェルスクリプトstart.shは以下のようになっています。
#!/usr/bin/bash
sleep 10
cd /home/pi/
python ./data_collect.py
最初に10秒sleepしているのは、これがないとプログラムがエラーとなるためです。どうやらこのプログラムが起動している段階ではネットワーク系のサービスがまだ起動していないらしく、それを避けるために簡易的に10秒間待っています。
データを格納するcsvファイルが作成されるようになりました。
それをグラフ表示するhtmlは以下のようになります。グラフはGoogle Chartを使用しています。
/var/www/html/temperature.html
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<meta http-equiv="Cache-Control" content="no-cache">
<title>Temperature Graph</title>
<link rel="stylesheet" href="css/style.css">
<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>
<script type='text/javascript'>
google.charts.load('current', {packages: ['corechart', 'bar']});
google.charts.setOnLoadCallback(drawCurveTypes);
function drawCurveTypes() {
var req = new XMLHttpRequest();
req.open('get', 'temperature.csv', true);
req.send(null);
req.onload = function() {
var ma = convertCSVtoArray( req.responseText );
var data = new google.visualization.arrayToDataTable( ma );
var options = {
focusTarget: 'category',
pointSize: 3,
pointShape: 'circle',
chartArea:{left:'5%',width:'75%',height:'85%',top:'5%'},
backgroundColor: '#C0C0C0'
};
var chart = new google.visualization.LineChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
}
function convertCSVtoArray(str) {
var result = [];
tmp = str.split('\n')
for( var i = 0; i < tmp.length; ++i ) {
if( tmp[i] == '' ) continue;
result[i] = tmp[i].split(',');
if( i != 0) {
result[i][0] = new Date(result[i][0]);
for( var j = 1; j < result[i].length; j++) {
if( result[i][j].length != 0 ) {
result[i][j] = Number(result[i][j]);
} else {
result[i][j] = NaN;
}
}
}
}
return result;
}
</script>
</head>
<body>
<p>温度データ</p>
<div id='chart_div' class='graph-temperature'></div>
</body>
</html>
/var/www/html/css/style.cssには以下の4行を追加します。ファイルがない場合は追加します。グラフの横幅はブラウザの横幅を目一杯使うようにしています。縦の540ピクセルはラズパイのモニタの設定ではみ出さないで表示で来る最大の大きさを狙って設定しています。
.graph-temperature{
width: 100%;
height: 540px;
}
表示されるページは以下のようなイメージになります。グラフ内にマウスポインタを当てると指定したポイントの値を表示します。
庭を写すカメラを制御しているGardenは窓際に置いているのですが、日が射すと温度が70℃以上になっているのがわかります。曇りの日はそれほど高くならないようです。
2階のモニタ用ラズパイはPi3でタッチパネルと一体にセットされて、100均で買ったプラスティックケースに入れていますが、ファンもヒートシンクも付けていないため、60°をちょっと超えてしまっています。
居間のテレビの裏に置いてあるLivingは時々60℃を超えてはファンが回って冷えている感じなのでしょうか。
PiZero2のAlexa Keyは熱対策なしの上に、テンキーで蓋をされているようになっているためか、はたまたZeroよりCPU能力が高く発熱量が多いためか50℃を超えたところで安定しています。
玄関フード内に取り付けたEntrance-2はラズパイZeroですが、これも熱対策は何もしていません。常に気温より20℃くらい高い温度です。
玄関の内側に取り付けたEntranceはPi3ですが、これは全体がヒートシンクになっているというアルミケースを使用しています。常に40℃前半を維持していて素晴らしい冷却能力を示しています。
部屋は暑くなってきたらエアコンを点けているのがわかります。もう秋なのか夜は25℃以下になることもあるようです。