2020年4月、体温計が品切れになるという社会。無いものは作る!ということで体温計を作ってみました。普通に体温を表示するだけでは芸が無いのでネット上に記録を残せる体温計にしてみました。「ウェアラブルIoT体温計」です。
この記事ではセンサー、マイコン、電子回路、マイコンのソースコード、webサーバー側のプログラムについて説明します。
最後に測定結果をグラフにするためのChart.jsについても触れています。
体温センサー
体温を測るセンサーはMAX30205を使っています。これは体温を測る専用センサ。±0.1℃となってます。I2CでArduinoやESP8266にデータを取り込むことが可能です。I2Cは信号線2本と電源、グランド、合計4本の線をつなぐだけで使えるので便利。
MAX30205の購入は以下のサイトから買いました。
MAX30205 ±0.1°C Human Body Temperature Sensor by ClosedCube on Tindie
https://www.tindie.com/products/closedcube/max30205-01degc-human-body-temperature-sensor/
MouserやAliExpressでも似たような物があるので、これ以外でも使えると思います。
マイコン
マイコンはI2Cのデータの読み取り、Wi-Fiでネットワークへのデータ送信が必要になります。そうなるとESP8266が良いでしょう。ESP8266はESP-WROOM-02とも呼ばれています。(どっちが正式名称?)
秋月で250円で買えます。
http://akizukidenshi.com/catalog/g/gM-09607/
ESP8266はArduino IDEを使ってコードを書いてArduinoと同じような感覚で使えます。若干最初に環境のセットアップが必要になります。そこらへんは方法があちこちのサイトにありますので、ここでは省略します。
ESP8266の特徴はDeepSleepという省電力モードがあったり、Wi-Fiに簡単に繋がることです。
電子回路
回路図は以下の通りです。
KiCADで設計してます。回路の特徴をちょっと説明しておきます。もともとBME280という気温を測定するセンサーを繋ごうと作ったものを今回は体温計にしています。
ESP8266は3.3Vで動作します。3.3Vの電圧を作るためにスイッチサイエンスの「TPS610986 超低消費電力 3.3V出力昇圧モジュール」を使っています。850円で安くはありませんが、入力0.7 V~4.5 Vで3.3Vを出してくれます。普通に3.3Vのレギュレータを入れても良いのですが電池の電圧が下がった時にもESP8266を動かすためです。稼働時間を気にしない場合には不要です。
センサーは常時電流を流していると電池の減りが早いのでTLP222Aを使い必要な時だけ電流を流してます。
U4の端子はプログラム書き込み用です。スイッチサイエンスの「FTDI USBシリアル変換アダプター Rev.2」を使い書き込みます。ピンの配列はこれに合わせてあります。
似た商品に「FTDI USBシリアル変換アダプター(5V/3.3V切り替え機能付き)」もあります。どっちでもできると思う。
リセットスイッチとプログラム用の押しボタンスイッチを付けてます。なんでも良いと思いますが、こんなのとか。タクトスイッチ黒色
LEDは直径3mmのタイプ。なんでも良いです。デバッグ用に付けましたが、いらないかも。
抵抗はプルアップやプルダウン、LEDの電流制限用になってます。プルアップとプルダウンには10Kオーム、LEDには1Kオームを入れてます。
コンデンサですが470uF 16Vの電解コンデンサを入れています。これはESP8266がネットに繋がる時に電流を消費するのでその対策です。
基板
基板は次の通りです。
KiCADで作ってます。Fusion PCBに発注しました。最初はブレッドボードで動作確認しましたが、小さくしたいので基板にしてます。抵抗はチップ抵抗。
部品を取り付けた基板はこんな感じ
ESP8266は側面に端子が出てるので手ハンダでも付けられます。
ESP8266のコード
ESP8266に書き込むコードは次のようになります。
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <SPI.h>
#include "ClosedCube_MAX30205.h"
ClosedCube_MAX30205 max30205;
#define LED1 12
#define LED2 13
#define BME_VIN 14
//wifi
const char* ssid = "あなたのWi-FiのSSIDを記入";
const char* password = "あなたのWi-Fiのパスワードを記入";
const char* host = "サーバのドメインを記入";
void setup() {
Serial.begin(115200);
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
pinMode(BME_VIN, OUTPUT);
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(BME_VIN, LOW);
max30205.begin(0x48);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
}
void loop() {
digitalWrite(BME_VIN, HIGH);
max30205.begin(0x48);
digitalWrite(LED1, HIGH);
digitalWrite(LED2, LOW);
delay(100);
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
Serial.print("T=");
float tempValue = max30205.readTemperature();
digitalWrite(BME_VIN, LOW);
Serial.print(tempValue);
Serial.println("C");
delay(300);
//Wi-Fiへの接続
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
return;
}
//測定値をfloatからStringに変換
String tempString = String(tempValue);
String url = "/bodyTemp/recordToCSV.php?t=" + tempString;
Serial.print("Requesting URL: ");
Serial.println(url);
client.println(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" );
delay(1000);
while (client.available()) {
String line = client.readStringUntil('\r');
Serial.print("line:");
Serial.print(line);
}
//1:μ秒での復帰までのタイマー時間設定 2:復帰するきっかけの設定(モード設定)
ESP.deepSleep(1 * 60 * 1000 * 1000, WAKE_RF_DEFAULT);
//1min = 1min * 60sec * 1000msec * 1000uSec
//deepsleepモード移行までのダミー命令
delay(1000);
}
デバック用にSerial.printが入れてありますが、確認用です。動作確認後にはコメントにしてください。残しておいても特にエラーとかにはなってないようです。
サーバーにある置いてある
/bodyTemp/recordToCSV.php
に測定値をGET形式で送信しています。送信後、適当な時間を置いて、サーバーからのレスポンスを受信しています。本当はここで、ちゃんと通信が出来ているのか確認すると良いのですが、してません。送りっぱなし。
サーバー側のプログラム(PHP)
次にサーバー側のプログラムを用意します。サーバーではPHPでESP8266から送られてきた数値をCSV形式(カンマ区切り)で保存しています。
<?php
date_default_timezone_set('Asia/Tokyo');//タイムゾーンの設定
//文字コードの設定
mb_internal_encoding("UTF-8");
$temp = $_GET["t"];
$fhandle = fopen("./temperature.csv", "a+");//読み込み/書き出し用でオープンします。 ファイルポインタをファイルの終端
//現在時刻の取得
fwrite($fhandle,date("Y/m/d G:i"));
fwrite($fhandle,",");
fwrite($fhandle,$temp);
fwrite($fhandle, "\n");
//レスポンス
header("Content-type: text/html");
echo "temp=";
echo $temp;
echo "C\n";
このPHPプログラムを
/bodyTemp
に
recordToCSV.php
という名前で保存しておきます。
同じフォルダに書き込み用のファイル
temperature.csv
を置いておきます。このファイルは書き込みができるようにパーミッションを設定しておきます。CyberDuckの画面では次のようになります。
これで次のように記録されるはずです。
2020/04/13 6:57,34.05
2020/04/13 6:58,34.05
2020/04/13 7:00,33.95
2020/04/13 7:01,33.87
2020/04/13 7:02,34.07
2020/04/13 7:03,34.17
2020/04/13 7:04,34.37
2020/04/13 7:05,34.50
2020/04/13 7:06,34.49
2020/04/13 7:07,34.46
2020/04/13 7:08,34.57
2020/04/13 7:09,34.67
2020/04/13 7:11,34.84
2020/04/13 7:12,34.89
2020/04/13 7:13,34.99
2020/04/13 7:14,35.00
2020/04/13 7:15,34.99
2020/04/13 7:16,34.92
2020/04/13 7:17,34.8
最初に日付と時刻、カンマを入れて体温になります。ブラウザで表示すると見えるはずです。ただのテキストを表示しているので自動更新とかはありませんが測定して記録するという目的は達成できました。
webブラウザでグラフに表示する
次に見やすくなるようにグラフにしてみたいと思います。グラフにするにはChart.jsというライブラリを使います。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP8266+MAX30205 body Temp</title>
<script type="text/javascript" src="main.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h1 class="logo">ESP8266+MAX30205 body Temp log.</h1>
<h2>Body Temperature [℃]</h2>
<canvas id="temperature" class="graph"></canvas>
</body>
</html>
main.jsは次の通り。
//CSVファイルを読み込む
function getCSV(){
var req = new XMLHttpRequest(); // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
req.open("get", "temperature.csv", true); // アクセスするファイルを指定
req.send(null); // HTTPリクエストの発行
// レスポンスが返ってきたらconvertCSVtoArray()を呼ぶ
req.onload = function(){
convertCSVtoArray(req.responseText); // 渡されるのは読み込んだCSVデータ
}
}
// 読み込んだCSVデータを配列に入れてグラフにする
function convertCSVtoArray(str){
var tmp = str.split("\n"); // 改行を区切り文字として行を要素とした配列を生成
var result = [];
// 各行ごとにカンマで区切った文字列を要素とした二次元配列を生成
for(var i=0;i<tmp.length;++i){
result[i] = tmp[i].split(',');
}
var temp= [];//温度を入れる配列
var time = [];//時刻を入れる配列
for(var i=0;i<tmp.length;++i){
time[i] = result[i][0];
temp[i] = result[i][1];
}
drawLineGraph(time,temp);//グラフを描画
}
var drawLineGraph = function(time,temp){
var ctx = document.getElementById("temperature");//ページ上の要素を取得
var myLineChart = new Chart(ctx, {
type: 'line',
data: {
labels:time,
datasets: [
{
label: '体温',
data: temp,
borderColor: "rgba(255,0,0,1)",
backgroundColor: "rgba(0,0,0,0)",
pointRadius: 1
}
],
},
options: {
title: {
display: true,
text: '体温℃'
},
scales: {
yAxes: [{
ticks: {
suggestedMax: 38,
suggestedMin: 15,
stepSize: 1,
callback: function(value, index, values){
return value + '度'
}
}
}]
},
}
});
}
window.onload = function () {
getCSV();
};
ちょっとchart.jsわかってないんですが、こんなんで出来ました。
ケース
最後にケースを3Dプリンタで作りました。
測定結果
こんな感じで記録取れてます。
上のグラフ、最初は身体に装着してないので室温を記録してます。一時的に上がってるのはセンサーを指で触れたりとかしたから。高い部分は身体に装着して1日過ごした部分です。たまに下がったのは絆創膏がはがれてしまったり、お風呂はいったところ。
なお、身体に貼り付ける時には絆創膏が良いです。途中で剥がれないように養生テープを使ってみました。剥がしたら赤くなってました。
単4電池で60時間以上動いています。測定間隔を5分にするとこの5倍は動くと思うので12日間ぐらいは使えると思います。
測定精度
体温計としてどうなのか?キャリブレーションとかは?という疑問です。普通の体温計(電子体温計5分測定のやつ)で測ってみました。
36.4℃
同時刻の測定値は35.5℃ぐらい。0.9〜1.0℃低めの測定値になってます。しかし布団の中にいる時の測定値を見ると、そこまでの誤差は無い様子です。
おそらく布団の中に入っている時は空間全体が体温に近い温度で正しい体温が測定できている。起きて動いている状態ではセンサーの反対側が室温に触れていて低めの温度になっている。
身体に貼り付ける反対側の断熱を工夫すれば測定誤差は減らせるのでは無いかと考えています。