カテゴリー別アーカイブ: 未分類

Fitbit+Python(3)

睡眠・歩数・RHR・HRVデータの処理
 1週間、1ヶ月といった単位で、行動・生理指標の変化を追う場合は、睡眠・歩数・RHR(安静時心拍)・HRV(心拍変動)データの日単位の取得が必要になります。

睡眠データの取得
 ClientIDとClientSecretを用いて,過去4日分の睡眠データを取得し,csv形式で保存する。40~46行目の関数は,それぞれ「総睡眠時間,目覚め後の時間,入眠時間,覚醒時間,寝返りの回数,寝返り時間,睡眠効率」の7つを取得している。

「合計睡眠時間=Awake(起きている時間)+MainLen(寝ている時間)」
"""
getSleepSmple.py
ClientIDとClientSecretで、Sleep関連データを取得するプログラム
"""
import sys
import fitbit
import pandas as pd
import gather_keys_oauth2 as Oauth2
from datetime import datetime, date, timedelta
     
print('Hello FitbitSleepAPP1') 
   
USER_ID     = "hoge"
CLIENT_SECRET = "hoge"
     
def requestFitbit(DATE):
    rval=""
    global auth2_client
    
    #fitbit_stats = auth2_client.intraday_time_series('activities/minutesSedentary', DATE, detail_level='1min')
    #HRstats = fitbit_stats['activities-minutesSedentary-intraday']['dataset']
       
    sleep_data = auth2_client.sleep(date=DATE)    
    #print(DATE+"睡眠データ取得実行しました") 
   
    OUTPUT_FILE = "SLEEP.csv"
    csv_file = open(OUTPUT_FILE, 'a')
    csv_file.write(DATE+",")
   
    sleepcnt=len(sleep_data["sleep"])
    #print(str(sleepcnt)+"回眠っています") 
   
    totallength=0;
    TAfter=0;
    TFall=0;
    TAwake=0;
    TLessCnt=0;
    TLessDur=0;
    TEffi=0;
    for var in range(0, sleepcnt):
        #print("睡眠:"+str(var)) 
        totallength+=sleep_data["sleep"][var]["minutesAsleep"] #総睡眠時間
        TAfter+=sleep_data["sleep"][var]["minutesAfterWakeup"]
        TFall+=sleep_data["sleep"][var]["minutesToFallAsleep"]
        TAwake+=sleep_data["sleep"][var]["minutesAwake"]
        TLessCnt+=sleep_data["sleep"][var]["restlessCount"]
        TLessDur+=sleep_data["sleep"][var]["restlessDuration"]
        TEffi+=sleep_data["sleep"][var]["efficiency"]
          
    AvgEffi=1
    if sleepcnt!=0:
        AvgEffi=TEffi/sleepcnt;    
      
    print(str(sleepcnt)+"回の睡眠で総睡眠時間は"+str(totallength)+"分です") 
    csv_file.write(str(totallength)+","+str(sleepcnt)+","+str(TAfter)+","+str(TFall)+","+str(TAwake)+","+str(TLessCnt)+","+str(TLessDur)+","+str(AvgEffi)+",")
   
    for var in range(0, sleepcnt):
        if sleep_data["sleep"][var]["isMainSleep"] == True:
            csv_file.write(sleep_data["sleep"][var]["startTime"]+","+str(sleep_data["sleep"][var]["minutesAsleep"])+","+str(sleep_data["sleep"][var]["efficiency"]))
   
    csv_file.write("\n")
    csv_file.close()
       
    return rval
##################################################
    
def writeindex():   
    OUTPUT_FILE = "SLEEP.csv"
    csv_file = open(OUTPUT_FILE, 'a')
                     
    csv_file.write("date,totallength,count,AfterWakeup,FallAsleep,Awake,restlessCount,restlessDur,AvgEffic,MainStart,MainLen,MainEffic\n")
    csv_file.close()
##################################################
    
"""Get tokens"""
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
     
print(ACCESS_TOKEN)  
print(REFRESH_TOKEN)
    
"""Authorization"""
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
     
"""request"""
#requestFitbit("2022-02-22")
writeindex();   #インデックス記入
today = datetime.today()
for var in range(0,4):
    #day=today + timedelta(days=1)
    #print(var+":" + datetime.strftime(yesterday, '%Y-%m-%d'))
    stamp=datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print("target: " + stamp, end=' ')
    requestFitbit(stamp)

歩数データの取得
1日あたりの歩数を取得する

"""
getStepSample.py
個人の1日の歩数データを取得するプログラム
"""
import sys
import fitbit
import gather_keys_oauth2 as Oauth2
from datetime import datetime, timedelta
import json
 
print('Hello FitbitDailyStepsAPP')
 
USER_ID = "hoge"
CLIENT_SECRET = "hoge"
 
def requestFitbit(DATE):
    global auth2_client
 
    # APIエンドポイントを使用して1日の歩数データを取得
    steps_data = auth2_client.make_request(f'https://api.fitbit.com/1/user/-/activities/steps/date/{DATE}/1d.json')
 
    OUTPUT_FILE = "Steps.csv"
    with open(OUTPUT_FILE, 'a') as csv_file:
        csv_file.write(f"{DATE},")
        if "activities-steps" in steps_data and len(steps_data["activities-steps"]) > 0:
            steps_info = steps_data["activities-steps"][0]  # リストの最初の要素を取得
            csv_file.write(f"{steps_info['value']}\n")
        else:
            csv_file.write("No steps data\n")
 
def writeindex():
    OUTPUT_FILE = "Steps.csv"
    with open(OUTPUT_FILE, 'w') as csv_file:
        csv_file.write("date,steps\n")
 
# トークンの取得
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
 
print("AT:"+ACCESS_TOKEN)
print("RT:"+REFRESH_TOKEN)
 
# 認証
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
 
# インデックスの書き込み
writeindex()
 
# 1日の歩数データのリクエスト
today = datetime.today()
for var in range(0, 7):
    stamp = datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print(f"target: {stamp}", end=' ')
    requestFitbit(stamp)

RHR(安静時心拍数)の取得
RHRはその日の安静時の心拍数の評価値

"""
getRHRsample.py
個人のHRVデータを取得するプログラム
"""import sys
import fitbit
import gather_keys_oauth2 as Oauth2
from datetime import datetime, timedelta
import json

print('Hello FitbitRHRAPP')

USER_ID = "hoge"
CLIENT_SECRET = "hoge"

def requestFitbitRHR(DATE, ID):
    global auth2_client

    # APIエンドポイントを使用して安静時心拍数データを取得
    heart_data = auth2_client.make_request(f'https://api.fitbit.com/1/user/-/activities/heart/date/{DATE}/1d.json')

    OUTPUT_FILE = ID + "_RHR.csv"

    with open(OUTPUT_FILE, 'a') as csv_file:
        csv_file.write(f"{DATE},")
        if "activities-heart" in heart_data and len(heart_data["activities-heart"]) > 0:
            heart_info = heart_data["activities-heart"][0]['value']
            if 'restingHeartRate' in heart_info:
                rhr = heart_info['restingHeartRate']
                csv_file.write(f"{rhr}\n")
            else:
                csv_file.write("No resting heart rate data\n")
        else:
            csv_file.write("No heart rate data\n")

def writeindex(ID):
    OUTPUT_FILE = ID + "_RHR.csv"
    with open(OUTPUT_FILE, 'w') as csv_file:
        csv_file.write("date,restingHeartRate\n")

# トークンの取得
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])

print(ACCESS_TOKEN)
print(REFRESH_TOKEN)

# 認証
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)

# インデックスの書き込み
writeindex(USER_ID)

# 安静時心拍数データのリクエスト
today = datetime.today()
for var in range(0, 7):
    stamp = datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print(f"target: {stamp}", end=' ')
    requestFitbitRHR(stamp, USER_ID)

HRV(心拍変動)データの取得
HRVは睡眠時のものとなるため、1日あたり一つ。評価値はrMSSDを示し単位はms。

"""
getHRVsample.py
個人のHRVデータを取得するプログラム
"""
import sys
import fitbit
import gather_keys_oauth2 as Oauth2
from datetime import datetime, timedelta
import json
 
print('Hello FitbitHRVAPP')
 
USER_ID = "hoge"
CLIENT_SECRET = "hoge"
 
def requestFitbit(DATE):
    global auth2_client
 
    # APIエンドポイントを使用してHRVデータを取得
    hrv_data = auth2_client.make_request(f'https://api.fitbit.com/1/user/-/hrv/date/{DATE}.json')
 
    OUTPUT_FILE = "HRV.csv"
    with open(OUTPUT_FILE, 'a') as csv_file:
        csv_file.write(f"{DATE},")
        if "hrv" in hrv_data and len(hrv_data["hrv"]) > 0:
            hrv_info = hrv_data["hrv"][0]  # リストの最初の要素を取得
            csv_file.write(f"{hrv_info['value']['dailyRmssd']},{hrv_info['value']['deepRmssd']}\n")
        else:
            csv_file.write("No HRV data\n")
 
def writeindex():
    OUTPUT_FILE = "HRV.csv"
    with open(OUTPUT_FILE, 'w') as csv_file:
        csv_file.write("date,dailyRmssd,deepRmssd\n")
 
# トークンの取得
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
 
print(ACCESS_TOKEN)
print(REFRESH_TOKEN)
 
# 認証
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
 
# インデックスの書き込み
writeindex()
 
# HRVデータのリクエスト
today = datetime.today()
for var in range(0, 4):
    stamp = datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print(f"target: {stamp}", end=' ')
    requestFitbit(stamp)

Fitbit+Python(2)

1週間ぶんのHRを取得する
計測したデータはすべてクラウド上に上がっているので、1週間ぶんのデータを取得することも可能です。おおまかな手順は下記の動画をご覧ください。

下記スクリプトは、指定した日付から遡って3日ぶんのHRを取得するスクリプトです。

#getWeekHR.py
import sys
import fitbit
import gather_keys_oauth2 as Oauth2
from datetime import datetime, date, timedelta
    
print('Hello FitbitAPP4') 
USER_ID     = ""; CLIENT_SECRET = ""
    
def requestFitbit(DATE):
    rval=""
    global auth2_client,USER_ID 
   
    fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1min')
    HRstats = fitbit_stats['activities-heart-intraday']['dataset']
      
    OUTPUT_FILE = USER_ID+"_HR.csv"
    csv_file = open(OUTPUT_FILE, 'a')
     
    csv_file.write(DATE+",")
    for num1 in range(24):
        for num2 in range(60):
            key='{:02}'.format(num1)+':{:02}'.format(num2)+':00'
            hr=""
            for var in range(0, len(HRstats)):
                if str(HRstats[var]['time']) == key:
                    hr=str(HRstats[var]['value'])
                    break
            csv_file.write(hr)
            csv_file.write(",")              
               
    csv_file.write("\n")
    csv_file.close()
    return rval
##################################################
   
def writeindex():    
    global USER_ID 
    OUTPUT_FILE = USER_ID+"_HR.csv"
    csv_file = open(OUTPUT_FILE, 'a')
       
    csv_file.write(",")
    for num1 in range(24):
        for num2 in range(60):
            key='{:02}'.format(num1)+':{:02}'.format(num2)+':00'
            csv_file.write(key)
            csv_file.write(",")              
               
    csv_file.write("\n")
    csv_file.close()
##################################################
   
"""Get tokens"""
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
    
"""Authorization"""
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
    
"""request"""
   
writeindex()
today=date(2023, 6, 26)
for var in range(0, 3):
    stamp=datetime.strftime(today - timedelta(days=var), '%Y-%m-%d')
    print("target: " + stamp)
    requestFitbit(stamp)

無事に実行されると、「ID_HR.csv」のようなファイル名のデータが作成されます。エクセルで開くと、下記のような構造になっています。60(分)✕24(時間)=1440個のデータが3日ぶん格納されていることがわかります。

無事に実行されると、「ID_HR.csv」のようなファイル名のデータが作成されます。エクセルで開くと、下記のような構造になっています。60(分)✕24(時間)=1440個のデータが3日ぶん格納されていることがわかります。

無事に実行されると、「ID_HR.csv」のようなファイル名のデータが作成されます。エクセルで開くと、下記のような構造になっています。60(分)✕24(時間)=1440個のデータが3日ぶん格納されていることがわかります。

皮膚温バイオフィードバック装置-プログラム解説

導入
 バイオフィードバック(以下,BF)は,心身のリラクセーション法として有効な手法であり,脳波や心拍数,筋電図,血圧などの生理的指標を自己制御可能にする訓練方法です。しかし,従来のBF装置は高価であり,専門施設でしか利用できないため,その普及が阻まれています。そこで本研究は,これに対する解決策として,オープンソースハードウェアであるArduino型マイクロコンピューターとデジタルファブリケーション技術を活用し,容易に皮膚温BF訓練を実施できる新たな低コストデバイスの開発を行いました。

装置の構成
 マイクロコンピュータはMakerNanoを,皮膚温センサーはLM35DZを用います。フィードバック部分は,視覚フィードバックにカラーLEDモジュールと液晶ディスプレイを,聴覚フィードバックにスピーカーを用います。

使用した電子部品の入手先
 使用した部品の入手先を以下に示しました。なお,各部品名の「こちら」を選択すると,販売先のサイトに移動します。

・MakerNano:こちら
・LM35DZ:こちら
・スピーカー:こちら
・RGBLED:こちら
・液晶ディスプレイ:こちら
・DCDCコンバータ:こちら
・ピンヘッダー:こちら
・熱収縮チューブ:こちら
・細径3心並列線:こちら
・電池ボックス:こちら
・単4電池:こちら

測定方法
 温度センサは,指サックを用いて,非利き手の第2指腹測部に直接固定します。次に,右側面(下部)にある電源スイッチを入れて装置を起動します。安静時は,装置左下部のスイッチを右にスライドさせFBを無効にし,訓練時は左にスライドしてFBを有効にします。注意点として,温度センサに風が直接当たらない場所で訓練をしてください。詳しい訓練方法については,下記の2分程度の動画をご覧ください。

プログラムの解説
 ここでは,皮膚温BF装置の開発に用いたソフトウェアの説明を行います。使用したソフトウェアは,長野(2022)を参考にArduino開発環境1.8.19と,長野(2016)を参考にProcessing開発環境3.5.4のプログラムの作成を行いました。

1. Arduino開発環境

プログラム名説明
st7032液晶ディスプレイに皮膚温度を表示
Adpatesion安静期間の自動管理化
switchFB刺激の有無の切り替え
BFst7032_230705プログラムの全体像

2. Processing開発環境

プログラム名説明
SBF230707皮膚温のグラフ表示とCSV形式の発行
Subroutine1現在時刻と経過時間

1. Arduino開発環境

プログラム:st7032
 液晶ディスプレイ部分に該当するプログラムです。GNDと5Vで電源供給を行い,アナログピンの4,5番からI2C通信(Inter-Integrated Circuit)を行い,マイコンと液晶ディスプレイの間でデータの送受信を行っています。1行目で,液晶ディスプレイの制御に必要な「ST7032_asukiaaa.h」ライブラリをインクルードし,バージョン1.0.5を使用しました。3行目で,液晶ディスプレイの制御を行うために「lcd」とクラスをインスタンス化しています。これにより,11行目で表示領域8列2行とし,12行目でコントラスト(文字の濃さ)0~63を指定するなど液晶ディスプレイを制御する機能を呼び出しています。6行目のsetup関数でこれらの初期化を行っています。5行目のtemp1,tempdは,現在温度,1秒間で生じた温度の変化量を格納しています。15~24行目は安静期間の自動管理化を行っています。安静期間終了の条件を満たした場合,液晶ディスプレイの7列0行(画面右上)に「*」を表示し,満たされない場合,何も表示しませんでした。26~28行目は,Stringオブジェクトのbuf1,buf2を用いることで,temp1,tempdのデータを文字列に変換し,表示するための準備を行っています。30~33行目では,液晶ディスプレイの画面1行目にbuf1から現在温度,2行目にbuf2から1秒間で生じた温度の変化量をStringオブジェクトから「.c_str()」メソッドを用いて表示しました。これらを15行目のloop関数で繰り返し実行しました。

#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ

ST7032_asukiaaa lcd;  //液晶ディスプレイのライブラリを使用

double temp1, tempd;   //温度データを格納する変数

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

void setup() {
  lcd.begin(8, 2);     //液晶ディスプレイで8列2行で指定表示
  lcd.setContrast(5);  //液晶ディスプレイのコントラスト
}

void loop() {
  //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
  if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
    lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
    lcd.print("*");             //「*」表示
    readyFlg=true;
  } else {                      //条件を満たさなかった場合
    lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
    lcd.print(" ");             //何も表示しない
  }

  String buf1, buf2;
  buf1 += temp1;      //buf1は現在温度
  buf2 += tempd;      //buf2は温度変化量

  lcd.setCursor(0, 0); //液晶ディスプレイの1行目にカーソルを移動
  lcd.print(buf1.c_str()); //1行目に現在温度を表示
  lcd.setCursor(0, 1);    //液晶ディスプレイの2行目にカーソルを移動
  lcd.print(buf2.c_str());  //2行目に温度変化量を表示
}

プログラム:Adpatesion 
 このプログラムは、安静期間の自動管理化を行うため、大河内(1990)の安静期間の処理を参考に行っています。具体的には、1分ごとの皮膚温の平均値が連続する3分間で増減せず、その範囲が0.4℃以内である場合に安静期間の終了を示す「*」を液晶ディスプレイに表示します。プログラム内では、現在の温度を格納する変数temp1、平均値計算用のsum、average、cntが定義され、連続する3分間(180サンプル)の温度データを保持するための配列historyと、そのデータを格納するインデックスhistory Indexが用意されています。history Indexは180を超えると自動的に最初の位置に戻り、配列の境界をループしながらデータを保存します。初期化として、最小・最大温度を0℃で設定し、取得した温度データをhistory配列に格納、次のインデックスを更新します。履歴内のデータと比較して、現在の最小・最大温度を更新し、連続する180サンプルが増減なく0.4℃以内であるかをチェックします。条件が満たされた場合はArduinoのシリアルモニタ内に「***」を表示し、満たされない場合は「—」を表示しました。温度データの取得と処理は10msごとに行われ、安静期間の判定と表示が繰り返されることで、安静状態をモニタリングします。この処理により、安静期間の終了を正確に管理し、リアルタイムで液晶ディスプレイにフィードバックを提供します。

double temp1;   //現在温度のデータを格納する変数
double sum, average;    //温度データの合計値と平均値を格納
long cnt;      //温度データのサンプリング数

const int HISTORY_SIZE = 180; // 履歴のサイズ
double history[HISTORY_SIZE]; // 測定値の履歴
int historyIndex = 0;         // 履歴のインデックス

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

bool readyFlg=false;  //安静期間のフラグ


void loop() {

    if(readyFlg==true){
      Serial.print(",***"); //安静期間終了時に「***」表示
    }
    else{
      Serial.print(",---"); //安静期間が終了していない場合「---」表示
    }
    
    Serial.println();   //シリアルモニタに「***/---」を表示するための空行
    sum = 0;    //合計値の初期化
    cnt = 0;
    delay(10);  //10ms単おきに行う

    history[historyIndex] = temp1;  //温度データを記録するための配列
    historyIndex = (historyIndex + 1) % HISTORY_SIZE;  //新しい温度データを保存

    minTemp = history[0];   //最小温度をデータの最初の要素で初期化
    maxTemp = history[0];   //最大温度をデータの最初の要素で初期化

    for (int i = 1; i < HISTORY_SIZE; i++) {  //温度データから最小・最大温度を算出
      if (history[i] < minTemp) {   //データ内で最小温度より低い場合
        minTemp = history[i];       //最小温度を更新
      }
      if (history[i] > maxTemp) {    //データ内で最大温度より低い場合
        maxTemp = history[i];        //最大温度を更新
      }
    }

    //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
    if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print("*");             //「*」表示
      //Serial.println("***");
      readyFlg=true;
    } else {                      //条件を満たさなかった場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print(" ");             //何も表示しない
    }

プログラム:switch
 スライドスイッチを用いて,光や音のFB有無の切り替えを行うプログラムです。1行目では視覚FBに用いるLEDの制御にAdafruit_NeoPixelライブラリ(Ver1.10.7)をインクルードし,2行目では液晶ディスプレイの制御にST7032_asukiaaa.hライブラリ(Ver1.0.5)をインクルードしました。4,5行目では,LEDの制御ピン番号,使用するLEDの個数を指定し,7行目ではこれらをもとにAdafruit_NeoPixelクラスをインスタンス化しています。9,10行目で,FB有無の切り替えに用いるスイッチピン7,ブザーピン2など制御に用いるピン番号を指定しています。13行目で,スイッチの状態を入力モードにして,「INPUT_PULLUP」から内部プルアップ抵抗を有効にして,ピンの状態がHigh(オン)になっています。14行目で,ブザーのピンを出力モードに設定して,制御するための処理を行い,これらをsetup関数から初期化しています。17行目では,スイッチの状態(オン/オフ)を読み取ります。19行目で,スイッチの状態がオフの時に19~32行目で実行するFB有の状態にしました。温度が上昇した際(dir==1)に視覚FBのLEDライトが赤く点灯し,聴覚FBのブザー500Hzを10ms間出力しました(19~26行目)。温度が下降した際(dir==-1)に視覚FBのLEDライトが青く点灯し,聴覚FBのブザー1000Hzを10ms間出力しました(27~32行目)。33~35行目では,FB無しの状態を割り当て,スイッチの状態がオフの時にLEDライトが消灯し,ブザーが消音するようにFBの有無の切り替えを行いました。

#include <Adafruit_NeoPixel.h>  //LEDのライブラリ
#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ
 
#define PIN        3   //LEDの制御ピン
#define NUMPIXELS 1    //LEDの数
 
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);   //LEDのライブラリを使用

const int switchPin = 7;    //FB有無の切り替えに用いるスイッチ
int Bpin = 2;  //ブザーPIN2

void setup() {
  pinMode(switchPin, INPUT_PULLUP);  // 内部プルアップ抵抗を有効にする
  pinMode(Bpin, OUTPUT);

void loop() {
  int switchState = digitalRead(switchPin);
 
  if (switchState == LOW) {   //FBスイッチがオフの時に
    pixels.clear();
    if (dir == 1) {
        pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
      if (mag > 0) {
        tone(Bpin, 500, 10);    //温度上昇時は500Hz
      }
    }
    if (dir == -1) {
      pixels.setPixelColor(0, pixels.Color(0, 0, mag)); //温度下降を青で表示
      if (mag > 0) {
        tone(Bpin, 1000, 10);   //温度下降時は1000Hz
      }
    }
  } else if (switchState == HIGH) {         //左下のスイッチを入れるとFBなし
    pixels.setPixelColor(0, pixels.Color(0, 0, 0));    //LEDを消す
    noTone(Bpin);         //ブザー音を消す
  }

プログラム:BFst7032_230705
 長野(2022)が作成した皮膚温BFプログラムと,上記で開発したプログラムを統合した全体像です。温度センサーから入力された値をもとに,「現在温度,温度変化量,変化の強さ,サンプリング数,安静期間終了の判定」が算出されます。温度が上昇したときに,LEDが赤く点灯し,ブザーから500HzでFBが行われます。温度が下降したときにLEDが青く点灯し,ブザーから1000HzでFBが行われました。温度変化が顕著に行われない場合LEDの色は変化せず,温度の変化量が大きい場合はLEDがより強く点灯しました。1秒毎に平均値の算出および各種FBなどが行われました。これらの機能を用いて,BF装置のプログラムを作成しました。

#include <Adafruit_NeoPixel.h>  //LEDのライブラリ
#include <ST7032_asukiaaa.h>    //液晶ディスプレイのライブラリ

#define PIN        3   //LEDの制御ピン
#define NUMPIXELS 1    //LEDの数

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);   //LEDのライブラリを使用
ST7032_asukiaaa lcd;        //液晶ディスプレイのライブラリを使用

const int switchPin = 7;    //FB有無の切り替えに用いるスイッチ

double mv, temp0, temp1, tempd;   //温度データを格納する変数
double sum, average;    //温度データの合計値と平均値を格納
long t, t0, cnt;      //現在の時刻・温度データをサンプリングした時の時刻・温度データのサンプリング数
int Bpin = 2;  //ブザーPIN2

const int HISTORY_SIZE = 180; // 履歴のサイズ
double history[HISTORY_SIZE]; // 測定値の履歴
int historyIndex = 0;         // 履歴のインデックス

double minTemp = 0.0;  // 最小温度
double maxTemp = 0.0;  // 最大温度

bool readyFlg=false;  //安静準備フラグ

void setup() {
  pinMode(switchPin, INPUT_PULLUP);  // 内部プルアップ抵抗を有効にする

  pinMode(Bpin, OUTPUT);
  pinMode(13, OUTPUT);
  Serial.begin(115200);  //シリアル通信115200
  analogReference(INTERNAL); //参照電圧を1.1Vに設定
  cnt = sum = 0;
  pixels.begin();      //LEDを初期化

  lcd.begin(8, 2);     //液晶ディスプレイで8列2行で指定表示
  lcd.setContrast(5);  //液晶ディスプレイのコントラスト
}

void loop() {
  t0 = t;
  t = millis();        //現在時刻をms単位で取得
  mv = (double)1100 / (double)1024 * analogRead(0);  //デジタル値を電圧に変換
  sum = sum + mv;
  cnt++;        //サンプリング数のカウント
  if (t0 / 1000 != t / 1000) {    //1秒(100ms)毎に平均算出
    average = sum / (double)cnt;  //平均を計算

    temp0 = temp1;                //前回の温度を保存
    temp1 = average * 0.1;        //電圧を温度に変換
    tempd = temp1 - temp0;        //温度変化量を算出

    int mag, dir;                 //変化の程度(mag)と方向(dir)
    if (tempd > 0) {
      mag = abs(tempd * 100);
      dir = 1;                    //温度上昇時
    } else if (tempd <= 0) {
      mag = abs(tempd * 100);
      dir = -1;                   //温度下降時
    }

    int switchState = digitalRead(switchPin);

    if (switchState == LOW) {   //FBスイッチがオフの時に
      pixels.clear();
      if (dir == 1) {
        pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
        if (mag > 0) {
          tone(Bpin, 500, 10);    //温度上昇時は500Hz
        }
      }
      if (dir == -1) {
        pixels.setPixelColor(0, pixels.Color(0, 0, mag)); //温度下降を青で表示
        if (mag > 0) {
          tone(Bpin, 1000, 10);   //温度下降時は1000Hz
        }
      }
    } else if (switchState == HIGH) {         //左下のスイッチを入れるとFBなし
      pixels.setPixelColor(0, pixels.Color(0, 0, 0));    //LEDを消す
      noTone(Bpin);         //ブザー音を消す
    }

    pixels.show();

    Serial.print(temp1); //現在温度
    Serial.print(",");   //カンマ区切り
    Serial.print(tempd); //温度変化量
    Serial.print(",");
    Serial.print(mag);   //変化の強さ
    Serial.print(",");
    Serial.print(cnt);   //サンプリング数

    if(readyFlg==true){
      Serial.print(",***"); //安静期間終了時に「***」表示
    }
    else{
      Serial.print(",---"); //安静期間が終了していない場合「---」表示
    }
    
    Serial.println();   //シリアルモニタに「***/---」を表示するための空行
    sum = 0;    //合計値の初期化
    cnt = 0;
    delay(10);  //10ms単おきに行う

    history[historyIndex] = temp1;  //温度データを記録するための配列
    historyIndex = (historyIndex + 1) % HISTORY_SIZE;  //新しい温度データを保存

    minTemp = history[0];   //最小温度をデータの最初の要素で初期化
    maxTemp = history[0];   //最大温度をデータの最初の要素で初期化

    for (int i = 1; i < HISTORY_SIZE; i++) {  //温度データから最小・最大温度を算出
      if (history[i] < minTemp) {   //データ内で最小温度より低い場合
        minTemp = history[i];       //最小温度を更新
      }
      if (history[i] > maxTemp) {    //データ内で最大温度より低い場合
        maxTemp = history[i];        //最大温度を更新
      }
    }

    //3分間の皮膚温の変動が0.4℃以内の時にアスタリスク表示
    if (maxTemp - minTemp <= 0.4) {   //最大・最小温度の差が0.4℃以内の場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print("*");             //「*」表示
      //Serial.println("***");
      readyFlg=true;
    } else {                      //条件を満たさなかった場合
      lcd.setCursor(7, 0);        //液晶ディスプレイの右上に
      lcd.print(" ");             //何も表示しない
    }

    String buf1, buf2;
    buf1 += temp1;      //buf1は現在温度
    buf2 += tempd;      //buf2は温度変化量

    lcd.setCursor(0, 0); //液晶ディスプレイの1行目にカーソルを移動
    lcd.print(buf1.c_str()); //1行目に現在温度を表示
    lcd.setCursor(0, 1);    //液晶ディスプレイの2行目にカーソルを移動
    lcd.print(buf2.c_str());  //2行目に温度変化量を表示
  }
}

2. Processing開発環境

プログラム:SBF230707
 1つ目は、シリアル通信を介して受信した皮膚温データを処理し、リアルタイムでグラフ表示しながら、そのデータをCSVファイルに保存するプログラムです。1行目では、Processing開発環境のシリアル通信ライブラリをインポートして、3行目ではシリアル通信を行うための準備として、Serialオブジェクトを宣言しています。4、5行目では、BF装置とシリアル通信を行うため、Arduino開発環境で表示されたシリアルポートの指定および通信速度115200ポートレートを使用しています。6、7行目では、ST波形などを表示するためのエリアとして、幅640、高さ200と指定しています。8~10行目では、受信したデータ「現在温度、温度変化量、変化の強さ、サンプリング数、安静期間終了の判定」の5つのデータについて、float型の配列の宣言からデータ格納を行い、String関数からデータ文字列を格納するための宣言を行っています。また、現在データと前のデータを線でつなぐため、prevDate配列を使用しています。11~15行目は、グラフを描画するために、赤・青・透明色の指定を行っています。17行目は、8~10行目のデータの初期値を0と指定し、18行目はCSVファイルの出力に使用するファイル名の格納を行っています。19行目は、一時的に取得したデータを格納するため、buf2という変数を指定。20行目では、CSVファイルにデータを書き込むためにオブジェクトの指定を行っています。21行目では、プログラムの開始時刻をミリ秒単位で格納するため、startMills変数を指定しています。27行目では、プログラムの実行開始時に表示されるウィンドウサイズを幅600ピクセル、高さ500ピクセルに設定しています。28行目では、myPortオブジェクトを作成し、4、5行目で指定したポート名とポートレートで初期化を行っています。30、31行目は、現在時刻および経過時間を描画するプログラム(図3-6)内で用いたgetTimestamp関数から取得した現在日時のタイムスタンプに基づいて、CSVファイル名を指定しています。32行目は、新しいデータが一定数蓄積するたびにファイル内に書き込むため初期化を行っています。

 36~92行目では、draw関数を用いて取得したデータをグラフに描画するための処理を行っています。39~43行目は、データの表示設定を行っており、変数datecountを用いてデータの受信回数が600の倍数の時に(39行目)次の条件を実行しました。background(255)からウィンドウ全体を白く塗りつぶし、fill(0, 0, 0)からテキスト描画を黒色で塗りつぶしました。さらにテキストサイズを12として、センサーから取得された温度データの単位を示すため、画面左上に摂氏40℃を表す40degを表示しました。46行目では、myPortオブジェクトから指定したシリアル通信が可能な場合、次の処理を行いました。48行目のmyPort.readStringUntiでデータを読み込み、取得したデータがnull表記ではない場合(50行目)、trim(inString)からデータ前後の空白を削除し、データ処理を行いました(52行目)。シリアルポートから読み込まれた1行のテキストデータを格納するためのInstring関数をCSV形式のデータ入力のため、カンマで分割して、得られた文字列をparts配列に格納しており(53行目)、取得中のデータに不足はないか確認しています(55、56行目)。57行目では、現在のSTをグラフに描画する際に、前回のデータが紛失しないようにデータの保存が行われています。60行目では、グラフの縦軸の範囲を可視化するため、元の範囲20~40の値を0~500に変換しています。これらデータをCSVファイルに記録するため、dataMV配列を使用しています(61行目)。64、65行目では、getTimestamp2から現在時刻、getElapsedTimeから経過時間を取得しています。67、68行目では、Processing開発環境のコンソール画面に表示するために、bufという変数を用いて「タイムスタンプ、経過時間」とdataMV配列に格納された温度データがカンマ区切りで表示されました。これにより、Arduino開発環境のシリアルモニタなどから温度データを確認する手間を省きました。69行目では、連続して受信したデータbufをbuf2に追加し、一度に大量のデータをファイルに書き込む回数を減らしました。70行目では、取得したデータ数が10の倍数ごとにCSVファイルに、writer.flushからリアルタイムで書き込む処理を行っています(71行目)。その後、ファイル内の残りのデータを書き込み(72行目)、73行目でbuf2を空にしました。これにより、ファイルのサイズを一定に保ちつつ、連続してデータを記録することができました。76~82行目では、グラフを描画するための処理を行っています。受信したデータ数を600で割った値からグラフのx座標を計算して(76行目)、描画する線の太さを1に設定(77行目)。78行目で実際に描画を行い、11~16行目で設定した色を用いて、描画する線の輪郭線の色を設定しています。81行目では、直前のデータから現在のデータまでを、グラフ上での位置を計算して線で結ぶ描画処理を行っています。「xp-1」から線の始点のx座標、「500-prevData[i]」から線の始点のy座標、「xp」から線の終点のx座標、「500 – data[i]」から線の終点のy座標をそれぞれ表しています。84、85行目では、現在時刻と経過時間を描画し、描画された文字が潰れないようにデータのカウントの更新を行っています(88行目)。94~98行目では、プログラムの終了時にファイルに書き込めてないデータをCSVファイルに強制的に書き込み(95行目)、ファイルを閉じるための処理を行っています(96、97行目)。

import processing.serial.*;

Serial myPort;
String portName = "COM6"; //BT serial port
int baudRate = 115200;
int dataPlotWidth = 640;
int dataPlotHeight = 200;
float[] data = new float[5];
String[] dataMV = new String[5];
float[] prevData = new float[5];
color[] colors = {
  color(255, 0, 0), 
  color(0, 0, 255), 
  color(0, 0, 0, 0), 
  color(0, 0, 0, 0)
};
int datacount=0;
String OutFileName;
String buf2;
PrintWriter writer;
int startMillis;


void setup() 
{
  //size(dataPlotWidth, dataPlotHeight);
  size(600,500);
  myPort = new Serial(this, portName, baudRate);
  
  OutFileName=getTimestamp()+".csv";
  writer = createWriter("./data/"+OutFileName); // Create a new file in the sketch directory
  buf2="";

}

void draw()
{
  
  if((datacount % 600) == 0) {
    background(255);  // clear screen    
    fill(0, 0, 0); // set the fill color to black
    textSize(12);
    text("40 deg", 10, 20); // 
  }
  
  while (myPort.available() > 0) 
  {
    String inString = myPort.readStringUntil('\n');
    
    if (inString != null) 
    {
      inString = trim(inString);
      String[] parts = split(inString, ',');
      
      if(parts.length >3) 
      {
        for (int i = 0; i < 5; i++) 
        {
          prevData[i] = data[i];
          data[i] = map(float(parts[i]), 20, 40, 0, 500);  // Assuming your data is in the range 0-200
          dataMV[i] = parts[i];
          
        }
        String stamp=getTimestamp2();
        String etime= getElapsedTime();
        String buf=stamp+","+etime+","+dataMV[0] + "," + dataMV[1] + "," + dataMV[2] + "," + dataMV[3] + "," + dataMV[4];
        println(buf);
        buf2=buf2+buf+"\n";
        
        if(datacount%10==0){
          writer.print(buf2);
          writer.flush();
          buf2="";
        }

        int xp = datacount%600;
        strokeWeight(1);
        for (int i = 0; i < 1; i++) 
        {
          stroke(colors[i]);
          line(xp-1, 500 - prevData[i], xp, 500 - data[i]);
        }
        
        drawCurrentTime();
        drawElapsedTime();

    
        datacount++;
      }
    }
  }
}

void stop() {
  writer.flush(); // Writes the remaining data to the file
  writer.close(); // Finishes the file
  super.stop();
}

プログラム:Subroutine1
 2つ目は、現在時刻および経過時間を描画するプログラムです(図3-6)。1行目は、現在の日時情報をフォーマットして文字列で返すために、getTimestampというString型の関数の宣言を行っています。2~7行目では、現在の日時を「年、月、日、時、分、秒」から取得して、それぞれ変数に代入しています。10行目では、2~7行目で取得した現在日時の情報を連結した文字列のタイムスタンプを作成しています。12行目では、作成した文字列のタイムスタンプは、getTimestamp関数内のtimestampに格納されているため、その関数内でしか使用できません。そのため、return timestampを使用し、timestampを関数の外に返すことでgetTimestampの呼び出し元に、作成した文字列のタイムスタンプが出力できます。15行目では、getTimestam2というString型の関数の宣言を行っています。16~18行目では、現在時刻「時、分、秒」を取得し、それぞれ変数に代入しています。21行目では、現在時刻の情報を連結した文字列のタイムスタンプを作成しています。23行目では、12行目と同様にreturn timestampを使用し、getTimestam2の呼び出し元に、作成した文字列のタイムスタンプを出力する処理を行っています。

26~42行目は、Processing開発環境の実行ボタンを選択した際、ウィンドウ内に表示する現在時刻および経過時間の描画設定を行っています。26行目では、現在時刻を描画するため、drawCurrentTimeという関数の宣言を行っています。28行目では、getTimestam2から現在時刻をstamp2に格納しています。29行目のfill(255)は描画される形状の塗りつぶしを白色に設定し、noStroke()は形状の輪郭線を非表示に設定しています。これにより、白色の背景が表示されます。30行目では、rect関数を用いて、位置(450, 5)に幅130、高さ50の現在時刻を表示するための長方形の背景を描画しています。31行目では、描画の属性を設定しており、fill(0)から黒色のテキストに設定し、Stroke(0)は形状の輪郭線を黒色に設定しています。また、textSize(24)からテキストサイズを24としました。32行目では、text関数を使用して、28行目のstamp2から画面右上(x:450, y:30)に現在時刻を表示しました。35~42行目も同様の処理を行い、経過時間を描画するための関数をdrawElapsedTimeと定義し(35行目)、経過時刻を取得するための変数をelapsedTimeに格納しました(37行目)。経過時刻を描画するための背景色やテキストサイズは同様です(38~40行目)。描画位置に関しては、画面右上の現在時刻の下の段(x:450, y:60)に表示しました(41行目)。

44~55行目は、経過時間の計算を行い、「時、分、秒」のフォーマットに整形するString型の関数getElapsedTime(44行目)を用いたプログラムです。45行目では、millis()関数を使用してプログラムの実行開始からの経過時間をミリ秒数単位で取得し、elapsedMillis変数に格納しています。47~49行目は、取得したミリ秒数を元に、経過時間を「時間、分、秒」に変換しています。経過ミリ秒数を1000で割った値を「秒」、秒を60で割った値を「分」、分を60で割った値を時間に変換しています。51、52行目では、秒数や分数で60を超えた値を再計算できるように、60未満の範囲に収めています。54行目では、nf()関数を使用して、表示される経過時間の間にコロンで区切り「時間:分:秒」の形式で表示する文字列を形成しています。以上から、プログラムの実行開始と同時に設定したウィンドウ内に現在時刻および経過時間を表示することが可能となりました。

String getTimestamp() {
  int y = year();
  int m = month();
  int d = day();
  int h = hour();
  int min = minute();
  int s = second();

  // Use nf() to add leading zeroes to each time component if needed
  String timestamp = nf(y, 4) + nf(m, 2) + nf(d, 2) + "_" + nf(h, 2) + nf(min, 2) + nf(s, 2);
  
  return timestamp;
}

String getTimestamp2() {
  int h = hour();
  int min = minute();
  int s = second();

  // Use nf() to add leading zeroes to each time component if needed
  String timestamp =  nf(h, 2) +":"+ nf(min, 2) +":"+ nf(s, 2);
  
  return timestamp;
}

void drawCurrentTime()
{
  String stamp2=getTimestamp2();
  fill(255); noStroke();
  rect(450, 5,130,50); 
  fill(0); stroke(0);textSize(24); 
  text(stamp2, 450, 30); //   
}

void drawElapsedTime()
{
  String elapsedTime = getElapsedTime();
  fill(255); noStroke();
  rect(450, 55,130,50); 
  fill(0); stroke(0);textSize(24); 
  text(elapsedTime, 450, 60);
}

String getElapsedTime() {
  int elapsedMillis = millis() - startMillis;

  int s = elapsedMillis / 1000;
  int m = s / 60;
  int h = m / 60;

  s = s % 60;
  m = m % 60;

  return nf(h, 2) + ":" + nf(m, 2) + ":" + nf(s, 2);
}

====================================================================

引用文献
長野祐一郎(2016). 自作測定装置で学ぶ皮膚温バイオフィードバック バイオフィードバック研究,43,49-51.
長野祐一郎(2022). 自作測定機器を用いたバイオフィードバック バイオフィードバック研究,49,77-81.
大河内浩人(1990). 皮膚温制御におよぼす訓練課題とフィードバックの効果 バイオフィードバック研究,17,8-14.

Fitbit+Python(1)

ここでは、Fitbitで計測されたデータをPythonで取得する方法を解説します。作業のおおまかな手順は下記の動画をご覧ください。

Anacondaのインストール
Python実行環境であるAnacondaのインストールを行う。とりあえずAnaconda3をダウンロードして道なりにインストール。Environmentから仮想環境「Fitbit」を作成し、「OpenTerminal」でターミナルを表示する。Pythonコマンドで動作を確認する。Ctrl+Zで対話モードを終了できる。インストールは、こちらのサイトが参考になる。

Fitbitの開発者向けサイトでアプリケーションを登録する
PythonからFitbitAPIを使うにはアプリの登録が必要となる。FitbitDevelopperサイトにログインして、Manage>RegisterAppの順で進み、アプリケーションを登録する。設定は以下の画像の通りにする。URL関係は全てダミーのURLで問題なく、RedirectURLは、「http://127.0.0.1:8080/」とする。ページ内で作成された「OAuth 2.0 Client ID」と「Client Secret」をPythonスクリプト中で使用することになる。
Devサイトでのアプリ作成方法

Fitbit API Python Clientのインストール
GitHub – orcasgit/python-fitbit: Fitbit API Python Client Implementationからcodeボタンを押し、Zipファイルをダウンロードし、中身をCドライブ直下に配置する(こちらのサイトが参考になる)。さらに、下記コマンドで追加パッケージをインストールする。

pip install -r requirements/base.txt
pip install cherrypy 

Pythonスクリプトでクラウド上のファイルを取得
c:\python-fitbitフォルダに下記スクリプトをfitbit01.pyという名前で保存し、実行する。Anaconda Promptから下記コマンドを打ち込む

python fitbit01.py

#fitbit01.py
import sys
import fitbit
import gather_keys_oauth2 as Oauth2
   
print('Hello FitbitAPP3') 
   
USER_ID     = "hoge"
CLIENT_SECRET = "hoge"
   
DATE = "2021-06-03" # 取得したい日付
   
   
server = Oauth2.OAuth2Server(USER_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN = str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN = str(server.fitbit.client.session.token['refresh_token'])
   
#print(ACCESS_TOKEN) print('\n') 
#print(REFRESH_TOKEN)print('\n') 
   
"""Authorization"""
auth2_client = fitbit.Fitbit(USER_ID, CLIENT_SECRET, oauth2=True, access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
   
"""Getting data"""
#print('Getting data\n') 
#fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1min')
    
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/heart', DATE, detail_level='1min')
stats = fitbit_stats['activities-heart-intraday']['dataset']
   
OUTPUT_FILE = "HR_%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()
   
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/steps', DATE, detail_level='1min')
stats = fitbit_stats['activities-steps-intraday']['dataset']
   
OUTPUT_FILE = "STEP%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()
   
##################################################
fitbit_stats = auth2_client.intraday_time_series('activities/calories', DATE, detail_level='1min')
stats = fitbit_stats['activities-calories-intraday']['dataset']
   
OUTPUT_FILE = "CALO%s.csv" % DATE
csv_file = open(OUTPUT_FILE, 'w')
for var in range(0, len(stats)):
    csv_file.write(stats[var]['time'])
    csv_file.write(",")
    csv_file.write(str(stats[var]['value']))
    csv_file.write("\n")
csv_file.close()

トラブルシューティング

・RedirectURLが、http://127.0.0.1:8080/で正しく動作しない場合
ポート8080をなにかのプログラムが使っている可能性がある。その場合は、RedirectURLを、「http://127.0.0.1:8088/」とし、python-fitbitフォルダ中のgather_keys_oauth2.pyの該当部分を「8088」に変更してスクリプトを実施する。

・Pythonのスクリプトは何で作ればいいの?
プログラミング用のテキストエディタ、Notepad++がおすすめです。

H10+EliteHRV+Kubios

PolarH10+EliteHRV+Kubiosで心拍変動解析
 PolarH10は、運動研究分野で広く使われるチェストバンド型心拍モニターであり、多くの研究に採用されています。EliteHRVは、PolarH10と併用することでHRV測定を行うことのできるスマートフォンソフトウェアであり、無料で利用できます。HF・LFパワーやSDNN、RMSSDなど、基本的なHRV指標をPC無しで確認できます。Kubiosは、HRV解析に広く用いられるソフトウェアであり、機能を限定したStandard版を無料で利用することができます(有料版は、エラー補正機能など、より先進的な追加機能を使用することができます)。Kubiosのマニュアルは、HRV解析を理解する上で非常に参考になります。
 これらのハードウェア・ソフトウェアの組み合わせは、心拍・心拍変動の計測と解析を行う上で、コストパフォマンスの高い優れた方法と言えます。下記動画に大まかな手順を示したので、研究や実験演習における、心拍・心拍変動解析に導入されてはいかがでしょうか。

Schaffarczyk, M., Rogers, B., Reer, R., & Gronwald, T. (2022). Validity of the polar H10 sensor for heart rate variability analysis during resting state and incremental exercise in recreational men and women. Sensors, 22(17), 6536. 

Perrotta, A. S., Jeklin, A. T., Hives, B. A., Meanwell, L. E., & Warburton, D. E. (2017). Validity of the elite HRV smartphone application for examining heart rate variability in a field-based setting. The Journal of Strength & Conditioning Research, 31(8), 2296-2302. 

Tarvainen, M. P., Niskanen, J. P., Lipponen, J. A., Ranta-Aho, P. O., & Karjalainen, P. A. (2014). Kubios HRV–heart rate variability analysis software. Computer methods and programs in biomedicine, 113(1), 210-220.

Arduinoで生理指標を測る

論文中で必要とする電子部品等は下記から入手可能です。
秋月電子
スイッチサイエンス
RS

Arduinoを用いた電子工作の参考サイト
ブレッドボードの使い方
Arduino 日本語リファレンス
アナログ回路の基礎

参考になる文献・書籍
・神崎 康宏(2012)Arduinoで計る,測る,量る : 計測したデータをLCDに表示,SDカードに記録,無線/インターネットに送る方法を解説 CQ出版 
https://ci.nii.ac.jp/ncid/BB08696362
・鈴木 哲哉(2014)ボクのArduino工作ノート 改訂版 ラトルズ 
https://ci.nii.ac.jp/ncid/BB1457169X
・増田正・大路駿介  (2020). PT・OT・アスリートのためのプログラミングとマイコン電子工作入門 (1) プログラミングとマイコンを用いた電子工作. バイオメカニズム学会誌, 44, 48-52.
https://www.jstage.jst.go.jp/article/sobim/44/1/44_48/_pdf/-char/ja
・増田正, & 大路駿介. (2020). PT・OT・アスリートのためのプログラミングとマイコン電子工作入門 (2) Arduino® マイコンと電子回路の活用. バイオメカニズム学会誌, 44(2), 119-123.
https://www.jstage.jst.go.jp/article/sobim/44/2/44_119/_pdf/-char/ja

論文中で紹介されたプログラムを掲載します。

ECG測定に用いたプログラム(Arduino)

#include <Wire.h> //I2C通信ライブラリ
#include <Adafruit_ADS1X15.h> //ADS1015ライブラリ
Adafruit_ADS1015 ads; //ADS1015クラスのインスタンス化

void setup(void)
{
  Serial.begin(115200);  //シリアル通信開始
  // ADS1015 gain 2/3  input range +/- 6.144V 
  ads.begin(); //ADS1015通信開始
}

void loop(void)
{
  int16_t results; //AD変換結果
  results = ads.readADC_Differential_0_1();  //差動入力
   
  float   multiplier = 3.0F;  // デフォルトゲイン(2/3倍)における係数
  Serial.print(results * multiplier); //得られたデジタル値をmvに換算しシリアルモニタに表示
  Serial.print(",4000,1000"); 
  Serial.println(); //シリアルモニタ改行
  delay(0);
}

EMG測定に用いたプログラム(Arduino)

#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1015 ads;

void setup(void)
{
  Serial.begin(115200);
  // ADS1015 gain 16x  input +/- 0.256V
  ads.setGain(GAIN_SIXTEEN); //ゲインを16倍に設定
  ads.begin();
}

void loop(void)
{
  int16_t results;
  results = ads.readADC_Differential_0_1();  //差動入力   

  float multiplier = 0.125F; //ゲイン16倍時の係数
  Serial.print(results * multiplier); 
  Serial.print(",100,-100"); 
  Serial.println();
  delay(0);
}

SCC測定に用いたプログラム(Arduino)

#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1015 ads;

void setup(void)
{
  Serial.begin(115200);
  // ADS1015 gain 8x  input +/- 0.512V
  ads.setGain(GAIN_EIGHT);  //ゲインを8倍に設定
  ads.begin();
}

void loop(void)
{
  int16_t results1,results2;
  results1 = ads.readADC_Differential_0_1();  //差動入力
    
  float   multiplier = 0.25F; //ゲイン8倍時の係数
  Serial.print(results1 * multiplier); 
  Serial.print(","); 
  Serial.print("194,"); //24k Ohm = 42uS
  Serial.print("-7,");  //1M Ohm = 1uS
  Serial.println();

  delay(200);
}

SCLおよびSCR測定に用いたプログラム(Arduino)

#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1015 ads;

void setup(void)
{
  Serial.begin(115200);
  // ADS1015 gain 2x  input +/- 2.048V
  ads.setGain(GAIN_TWO);        
  ads.begin();
}

void loop(void)
{
  int16_t results1,results2;
  results1 = ads.readADC_Differential_0_1();  //作動入力
  results2 = ads.readADC_Differential_2_3();
  
  float   multiplier = 1.0F;
  Serial.print(results1 * multiplier); 
  Serial.print(","); 
  Serial.print(results2 * multiplier); 
  Serial.print(",");   
  Serial.print("1111,"); //24k Ohm = 42uS
  Serial.print("-41,");  //1M Ohm = 1uS
  Serial.println();

  delay(200);
}

EMGの無線計測に用いたプログラム(Arduino)

#include "BluetoothSerial.h" //BT通信ライブラリ
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
Adafruit_ADS1015 ads;
BluetoothSerial SerialBT; //BluetoothSerial クラスのインスタンス化
long t1,t0; //時間管理用変数
int cnt=0; //加算回数
int16_t results;
float multiplier = 0.125F;
float mv,average,sum; //平均値算出用変数

void setup(void)
{
  pinMode(10, OUTPUT); //内蔵赤色LEDを出力モードに
  digitalWrite(10, LOW); //内蔵赤色LEDを点灯

  Serial.begin(115200);
  SerialBT.begin("EMG32"); //Bluetooth device name
  Wire.begin(32, 33); //32端子をSDA,33端子をSCLとしてI2C通信開始
  
  // ADS1015 gain 16x  input +/- 0.256V
  ads.setGain(GAIN_SIXTEEN);
  ads.begin();
}

void loop(void)
{
  results = ads.readADC_Differential_0_1();  //作動入力   
  mv=abs(results * multiplier); //筋電位の絶対値を得る
  sum+=mv;  
  cnt++;
  
  t0=t1;
  t1=millis()/10;   //10ms間隔で平均mV数を表示
  if(t1!=t0){    
    average=sum/cnt;
    SerialBT.print(average);
    SerialBT.print(",100,0");
    SerialBT.println();
    sum=0;
    cnt=0;
  }  
}

SCCをWi-Fi経由でサーバーに送信するプログラム(Arduino)

#include <WiFi.h> //Wifiライブラリ
#include <WiFiMulti.h> //複数のアクセスポイントを管理するためのクラス
#include <HTTPClient.h>//HTTP通信ライブラリ
#include <Wire.h>
#include <Adafruit_ADS1X15.h>

WiFiMulti wifiMulti; //WiFiMultiクラスのインスタンス化
Adafruit_ADS1015 ads;

void setup(void)
{
  pinMode(10, OUTPUT);
  digitalWrite(10, LOW);

  Serial.begin(115200);
  Wire.begin(32, 33); //I2C connection
  
  ads.setGain(GAIN_EIGHT);  // ADS1015 gain 8x
  ads.begin();

  delay(4000);  //4秒待機した後に通信開始
  wifiMulti.addAP("MySSID", "MyPassword"); //WiFiアクセスポイントの登録
}

void loop(void)
{
  int16_t results;
  float multiplier = 0.25F;
  float SC;
  results = ads.readADC_Differential_0_1();  //作動入力   
  SC=results * multiplier;

  if((wifiMulti.run() == WL_CONNECTED)) { //登録したアクセスポイントに接続
    HTTPClient http; //HTTPClientをインスタンス化
    Serial.print("[HTTP] begin...\n");
    String URL="http://hogehoge.ne.jp/hogehoge/getsc.php";
    URL+="?SC="+String(SC);
    http.begin(URL); //HTTP通信開始

    Serial.print("[HTTP] GET...\n");
    Serial.println(URL);
    int httpCode = http.GET(); //URLをGET方式でリクエスト

    if(httpCode > 0) {
      Serial.printf("[HTTP] GET... code: %d\n", httpCode);
      if(httpCode == HTTP_CODE_OK) { //通信に成功したら
          String payload = http.getString(); //通信結果をpayloadへ格納
          Serial.println(payload);
      }
    } else { //失敗したらエラーとコードを表示
      Serial.printf("[HTTP] GET... failed, error: %s\n", 
                    http.errorToString(httpCode).c_str());
    }
    http.end(); //HTTP通信終了
  }
  delay(1000); //1秒ごとに繰り返す
}

Wifi経由受信したSCCデータをサーバーに記録するプログラム(PHP)

<?
$SCval=$_GET['SC'];	//Arduinoから送られたデータを受け取る
$timestr=strftime("%Y%m%d_%H%M%S"); 	//タイムスタンプ作成

if($SCval!=""){
	$fp=fopen("SCdata.csv","a");	//追加形式でファイル開く
	fprintf($fp,"%s,%s\n",$timestr,$SCval);	//タイムスタンプとデータを保存
	fclose($fp);		//ファイル閉じる
	echo"saved SC value!";	//Arduino側へ文字列送信
}
?>


22BF学会講習会

導入
 近年、Arduino等の安価なマイクロコンピュータを用いた電子工作が広く普及しています。これらを利用し、バイオフィードバック装置を自作し、利用する方法を紹介します。
 マイクロコンピュータの多くはAD変換機能を持っているため、様々なセンサーを接続することで測定装置として利用可能です。センサーを読み取るだけでなく、LEDやスピーカー、液晶ディスプレイなどに測定結果を出力することも可能です。シングルタスクOSを搭載しているため、測定値のリアルタイム処理できる点や、比較的コンパクトかつ低価格で装置を構成可能な点も、バイオフィードバックへの応用に適しています。
 本講習会では、末梢皮膚温変化を題材に、ハードウェア・ソフトウェアの両面から、バイオフィードバック装置の動作を学びます。 

装置の構成
 マイクロコンピュータはArduinoUNOを、皮膚温センサーはLM35DZを用います。フィードバック部分は、視覚フィードバックにカラーLEDモジュールを、聴覚フィードバックにスピーカーを用います。

測定の方法
 センサーはメンディングテープを用い、非利き手人差し指の腹側に装着する。テープをきつく巻き過ぎて指が圧迫されないように注意する。計測中は手のひらを上に向ける。下側に向けると机の温度を測ることになるので注意。室内温度は23~25℃程度に調節し、エアコンの風が直接指先にあたらないよう、風量・風向を調節する。


学習の手順
 ここでは下記の順序でバイオフィードバック機器の動作を学んでいきます。

プログラム名:ADC・・・AD変換の基礎
プログラム名:temp01・・・基本的な皮膚温測定
プログラム名:temp02・・・より細かく皮膚温を測定する方法
プログラム名:Tone・・・聴覚フィードバックに用いるスピーカーの使い方
プログラム名:ColorLED・・・視覚フィードバックに用いるスピーカーの使い方
プログラム名:TempABF・・・聴覚を用いた皮膚温バイオフィードバックの方法
プログラム名:TempVBF・・・視覚を用いた皮膚温バイオフィードバックの方法
プログラム名:TempAVBF・・・視聴覚を用いた皮膚温バイオフィードバックの方法

 使用するプログラム一式は下記のアドレスからダウンロードできます。Zip形式で圧縮されているため、必ず展開してから使用してください(圧縮されたままだとプログラムが動作しません)。

 ハードウェアの組み立ては、各プログラムの動画と下の画像をご覧ください。

プログラム:ADC
 AD変換は生体計測をする上で欠かすことのできない方法です。ここでは、ArduinoUNOを用い、皮膚温センサーから得られる電圧をAD変換し、数値として表示する方法を学びます。

//A0に入力された値をAD変換し表示
void setup() {
  Serial.begin(115200);        // シリアル通信の初期化
}

void loop() {
  int val = analogRead(0);
  Serial.println(val);        //analog0の値を表示
  delay(1000);
}

プログラム:temp01
 AD変換された値には単位がありません。デジタル化された値をmVに換算することで、温度を表示する方法を学びます。今回用いる皮膚温センサーは、1℃あたり10mVの出力があるため、mV値を0.1倍することで温度を得ることができます。

//1秒間隔でデジタル値を取得して温度に変換し表示
double mv,temp;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1Vに設定
}

void loop() {
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  temp=mv*0.1;           //電圧を温度に変換
  Serial.print(mv);  
  Serial.print(",");
  Serial.print(temp);   //温度表示
  Serial.println();
  delay(1000);          //1000ms停止
}

プログラム:temp02
 皮膚温の精神的な変化は微弱であるため、自己制御を実現するためには、0.01℃程度の変化を測定する必要があります。ここでは、1秒間の平均値を算出することで、より細かく皮膚温を調べる方法を学びます。

//1秒間隔で可能な限りデジタル値を取得して平均を表示
double mv,temp;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);        // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
}

void loop() {
  t0=t;
  t=millis();   //現在時刻をms単位で取得
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  sum=sum+mv;   //mv値の足し込み
  cnt++;
  if(t0/1000 !=t/1000){       //1秒(1000ms)毎に平均算出
    average=sum/(double)cnt;  //平均を計算
    temp=average*0.1;         //電圧を温度に変換
    Serial.print(cnt);        //サンプル数表示
    Serial.print(",");
    Serial.print(average);    //平均電圧を表示
    Serial.print(",");
    Serial.print(temp);       //温度を表示
    Serial.println();
    sum=0;cnt=0;              //合計値とカウントを初期化
  }
}

プログラム:Tone
 聴覚フィードバックに用いるスピーカーの使用方法を学びます。tone命令を用いることで、出力する音の周波数と、長さを指定できます。プログラム中の12は、デジタルポートの12番から音を出す事を意味します。

//スピーカーから500Hz,1000Hzの音を表示
void setup() {
}

void loop() {
  tone(12, 500, 100);   //500Hz 100ms
  delay(1000);          //1000ms停止
  
  tone(12, 1000, 100);  //1000Hz 100ms
  delay(1000);          //1000ms停止
}

プログラム:ColorLED
 視覚フィードバックに用いるカラーLEDの使用方法を学びます。カラーLEDの制御には、Adafruit_NeoPixelライブラリを用います(ライブラリのインストール方法は動画をご覧ください)。プログラム冒頭で、デジタルポートの13番を制御用に使うこと、LEDの数が1個であることを定義しています。プログラムは、setup関数で初期化を行い、loop関数でLEDの点灯を処理しています。色のセットは、pixels.clear→pixels.setPixelColor→pixels.showの順で行います。pixels.Color(R, G, B)の形式で、色の強さは0~255で指定します。

//カラーLEDを用い、赤・緑・青色を点灯
#include <Adafruit_NeoPixel.h>
#define PIN 13        //制御用ピン
#define NUMPIXELS 1   //LEDの数
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();     //LEDを初期化
}

void loop() {
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(20, 0, 0));  //赤
  pixels.show();
  delay(1000);    //1000ms停止

  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 20, 0));  //緑
  pixels.show();
  delay(1000);    //1000ms停止

  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(0, 0, 20));  //青
  pixels.show();
  delay(1000);    //1000ms停止
}

プログラム:TempABF
 皮膚温変化を聴覚刺激として2値(上昇もしくは下降)でフィードバックします。上昇・下降は23行において求められる皮膚温変化量によって判定します。25行から27行では、温度変化に応じて上昇下降の方向を求めています。29行から34行では、変化の方向に応じてフィードバックする音を、500Hzもしくは1000Hzに振り分けています。36行から41行は、現在温度や変化量をシリアルモニタに表示しています。

//皮膚温を聴覚でフィードバック
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
}

void loop() {
  t0=t;
  t=millis(); //現在時刻をms単位で取得
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  sum=sum+mv;
  cnt++;  
  if(t0/1000 !=t/1000){   //1秒(100ms)毎に平均算出
    average=sum/(double)cnt;  //平均を計算

    temp0=temp1;          //前回の温度を保存
    temp1=average*0.1;    //電圧を温度に変換
    tempd=temp1-temp0;    //温度変化量を算出

    int mag,dir;  //変化の程度(mag)と方向(dir)
    if(tempd>0){mag=abs(tempd*100); dir=1;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    if(dir==1){
      if(mag>0){tone(12, 500, 10);}                     //温度上昇時は500Hz
    }
    if(dir==-1){
      if(mag>0){tone(12, 1000, 10);}                    //温度下降時は1000Hz
    }

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

プログラム:TempVBF
 皮膚温変化を視覚刺激として色と強さでフィードバックします。具体的には、上昇時は赤、下降時は青でLEDが点灯し、皮膚温の変化量が大きいほど明るく光ります。変化量の算出、上昇・下降の判定方法はTempABFと同様です。視覚フィードバックを処理する34行から41行では、上昇の場合に

1
pixels.Color(mag, 0, 0)
で赤色に、下降の場合には
1
pixels.Color( 0, 0,mag)
で青色に、LEDを点灯させています。magには、皮膚温変化量tempdを100倍した値が入っていますので、変化量が大きいほど強く光ります。それ以外の処理は、TempABFと同様です。

//皮膚温を視覚でフィードバック
#include <Adafruit_NeoPixel.h>
#define PIN        13 
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
  pixels.begin();               //LEDを初期化
}

void loop() {
  t0=t;
  t=millis(); //現在時刻をms単位で取得
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  sum=sum+mv;
  cnt++;  
  if(t0/1000 !=t/1000){   //1秒(100ms)毎に平均算出
    average=sum/(double)cnt;  //平均を計算

    temp0=temp1;          //前回の温度を保存
    temp1=average*0.1;    //電圧を温度に変換
    tempd=temp1-temp0;    //温度変化量を算出

    int mag,dir;  //変化の程度(mag)と方向(dir)
    if(tempd>0){mag=abs(tempd*100); dir=1;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    pixels.clear();
    if(dir==1){
      pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
    }
    if(dir==-1){
      pixels.setPixelColor(0, pixels.Color( 0, 0,mag)); //温度下降を青で表示
    }
    pixels.show();

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

プログラム:TempVABF
 TempVABFは、視覚・聴覚、双方のフィードバックを行うものです。内容は、TempVBFに聴覚フィードバック処理(37・41行部分)を追加しただけです。

//皮膚温を視聴覚でフィードバック
#include <Adafruit_NeoPixel.h>
#define PIN        13 
#define NUMPIXELS 1
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
double mv,temp0,temp1,tempd;
double sum,average;
long t,t0,cnt;

void setup() {
  Serial.begin(115200);         // シリアル通信の初期化
  analogReference(INTERNAL);    //参照電圧を1.1vに設定
  cnt=sum=0;
  pixels.begin();               //LEDを初期化
}

void loop() {
  t0=t;
  t=millis(); //現在時刻をms単位で取得
  mv = (double)1100/(double)1024 * analogRead(0); //デジタル値を電圧に変換
  sum=sum+mv;
  cnt++;  
  if(t0/1000 !=t/1000){   //1秒(100ms)毎に平均算出
    average=sum/(double)cnt;  //平均を計算

    temp0=temp1;          //前回の温度を保存
    temp1=average*0.1;    //電圧を温度に変換
    tempd=temp1-temp0;    //温度変化量を算出

    int mag,dir;  //変化の程度(mag)と方向(dir)
    if(tempd>0){mag=abs(tempd*100); dir=1;}   //温度上昇時
    if(tempd<=0){mag=abs(tempd*100); dir=-1;} //温度下降時
       
    pixels.clear();
    if(dir==1){
      pixels.setPixelColor(0, pixels.Color(mag, 0, 0)); //温度上昇を赤で表示
      if(mag>0){tone(12, 500, 10);}                     //温度上昇時は500Hz
    }
    if(dir==-1){
      pixels.setPixelColor(0, pixels.Color( 0, 0,mag)); //温度下降を青で表示
      if(mag>0){tone(12, 1000, 10);}                    //温度下降時は1000Hz
    }
    pixels.show();

    Serial.print(temp1);  Serial.print(",");  //現在温度
    Serial.print(tempd);  Serial.print(",");  //温度変化量 
    Serial.print(mag);    Serial.print(",");  //変化の強さ
    Serial.println();
    sum=0;cnt=0;
    delay(10);  
  }
}

補足

・温度センサーに関して
現在温度センサーLM35DZは入手が難しくなっており、TMP36GT9Zを代用することができます。その場合は、温度算出部分を下記のように変更してください。

temp1=average*0.1-50;    //電圧を温度に変換

・使用した電子部品の入手先
ArduinoUNO
TMP36GT9Z
カラーLEDモジュール
スピーカー
ピンヘッダー
収縮チューブ
細径3心並列線