yoyo が 2024年10月31日23時56分10秒 に編集
初版
タイトルの変更
IoTドキドキぼっと
タグの変更
RaspberryPi
soracom
心拍センサ
記事種類の変更
製作品
ライセンスの変更
(MIT) The MIT License
本文の変更
## 今回作った作品 心拍数をモニタリングしながら、ドキドキに応じた叱咤激励のメッセージをしゃべってくれたり、 LEDの色で今のドキドキの状況を伝える作品を作りました! @[youtube](https://youtu.be/RXufHriY7TM) ## 製作の動機 私は結構見た目の印象から落ち着いているように見られがちなのですが、 実は、内心、密かにドキドキすることが多く、見た目と心のギャップが結構あったりします。 人前で話すとき淡々としゃべってるように見えて、本当は内心めちゃめちゃ緊張していたりします。 このドキドキのギャップも含めて自分のことをもっと知ってほしい。LT(Lightning Talk)の時でいえば 淡々としゃべってるように見えるかもしれないけど、本当はすごくドキドキしていて温かく見守って ほしい欲求、密かにめっちゃありました。  ## 製作開始 心拍数をモニタリングしながら、ドキドキに応じたメッセージをしゃべってくれたり、 光の色で今のドキドキの度合を伝えることで、見た目と心のギャップを補ってくれる”ぼっと”を作りたい! 頭の中に思い描いた”ドキドキぼっと”を形にすべく、製作に着手しました。 ## 構成  ① Raspberry Piに心拍センサを接続し、心拍数を測定、心拍数が閾値(80 bpm)以下ならLEDを青色にして点灯、 閾値(80bpm)を超えたら赤色にして点灯します。 ② 超音波センサの前に手をかざすと、DACオーディオアンプに接続したスピーカからI2Sにより心拍数に応じた 音声メッセージが再生されます。(自分の意図したタイミングで音声を再生させたかった為、超音波センサを スイッチ代わりにとして使いました。) ③ SORACOM AirでSORACOMのプラットフォームと接続、心拍数のデータをJSON形式にてHarvestに送ります。 ④ データが貯まっていき、貯まったデータはSORACOM Lagoonというサービスでグラフ化、可視化されます。 ⑤ ユーザはPC、タブレットで登壇者のドキドキ状態(心拍数)をグラフで確認できます。 # 準備 **・ 音声データの準備** Open J Talkにて発話させたいメッセージのテキストを入力、自分好みの音質・速度に調整し、音声合成します。 作成後、.wavデータをダウンロードし、Raspberry Pi上の任意のフォルダに保存します。  # 材料 ・Raspberry Pi 4 ・Adafruit I2S 3W Stereo Speaker Bonnet ・Speaker Cone ・超音波センサ ・シリアルLED ・リチウムイオンバッテリ ・GROVE-心拍センサ ・好きな柄の布 ・ジャンパー線(オスーメス) ・ジャンパー線(メスーメス) ・micro SD ・3G対応データ通信端末 AK-020 ## 製作 **・テクノ手芸(キーホルダ製作)** ①布を一針一針手で縫っていきます。 (ここの部分写真を撮り忘れた為、別柄のもう一つつくったときの写真を載せています。作業としては同じです)  ②ハトメをつけてキーホルダ化し、また布に超音波センサや電子部品など糸で縫い付けていきます。 (本作品ではグルーガン(熱で溶けるノリで冷めると接着剤みたく固定できる)は最後まで使わないようにしました。 衝撃などでぽろって部品が取れないようにするためです。こだわりポイントです。)  ③スピーカの縫い付けは他の電子部品と違って工夫が必要です。 スピーカは凸型になっていて、平らな布には取り付けにくい形をしているからです。 なので、まずタイラップで輪っかを作り、タイラップを布に縫い付けます。スピーカを輪っかになった タイラップに通した後、タイラップを縛ることでスピーカを固定しました。 リチウムイオン電池の固定は、3DプリンタでL型の部品を作り、タイラップで布に固定しました。 布が破れないようにするため、あらかじめハトメ(金属の輪っかの部品)で穴箇所を補強しました。  ④配線図をみながらジャンパ線を使ってRaspberry Pi(音声モジュール)と電子部品を接続し、ハーネスなど結束していきます。   ## コード 次にコードを書いて、キーホルダが光ったり、しゃべったりするようにします。 コードはこちらです。 ```Python:Python import time from collections import deque import board import RPi.GPIO as GPIO import subprocess import shlex import neopixel import sys import requests import json # 超音波センサで測距 class UltraSound: def __init__(self): # TRIGとECHOのGPIO番号 self.TRIG_PIN = 17 self.ECHO_PIN = 27 # 気温24[℃]の場合の音速[cm/s] self.v = 33150 + 60*24 #34590 # ピン番号をGPIOで指定 GPIO.setmode(GPIO.BCM) # TRIG_PINを出力, ECHO_PINを入力 GPIO.setup(self.TRIG_PIN,GPIO.OUT) GPIO.setup(self.ECHO_PIN,GPIO.IN) GPIO.setwarnings(False) #計測回数 self.num = 1 # HIGH or LOWの時計測 def _pulseIn(self, start=1, end=0): if start==0: end = 1 t_start = 0 t_end = 0 # ECHO_PINがHIGHである時間を計測 while GPIO.input(self.ECHO_PIN) == end: t_start = time.time() while GPIO.input(self.ECHO_PIN) == start: t_end = time.time() return t_end - t_start # 距離計測 def calc_distance(self): for i in range(self.num): # TRIGピンを0.3[s]だけLOW GPIO.output(self.TRIG_PIN, GPIO.LOW) time.sleep(0.3) # TRIGピンを0.00001[s]だけ出力(超音波発射) GPIO.output(self.TRIG_PIN, True) time.sleep(0.00001) GPIO.output(self.TRIG_PIN, False) # HIGHの時間計測 t = self._pulseIn() # 距離[cm] = 音速[cm/s] * 時間[s]/2 distance = (33150 + 60*24) * t/2 return distance # ドキドキ感を演出するLEDライトアップ class HeartBeatLED: def __init__(self): self.pixels = neopixel.NeoPixel( board.D12 , 13 ) def lightup_chargingLED(self): for i in range (0, 13, 1): self.pixels[i] = (0, 10, 10) time.sleep(0.015) self.pixels[i] = (0, 0, 0) time.sleep(0.015) def lightup_standbyLED(self): for i in range (0, 13, 1): self.pixels[i] = (0, 10, 10) # 青色LEDを順次点灯→そして順次消灯(全然ドキドキしてないとき) def _lightup_blueLED(self): for j in range (5): for i in range (0, 13, 1): self.pixels[i] = (0, 0, 64) time.sleep(0.03) for i in range (12,-1, -1): self.pixels[i] = (0, 0, 0) time.sleep(0.03) # 赤色LEDを順次点灯→そして順次消灯 def _lightup_redLED(self): for j in range (20): for i in range (0, 13, 1): self.pixels[i] = (64, 0, 0) time.sleep(0.02) for i in range (12,-1, -1): self.pixels[i] = (0, 0, 0) time.sleep(0.02) # 心拍数に応じたLEDライトアップ&音声メッセージ再生 class HeartRateMonitor: INTERRUPT_PIN = 13 MAX_DETECTED_TIMES_COUNT = 20 MAX_PULSE_INTERVAL = 2.0 def __init__(self): GPIO.setmode(GPIO.BCM) GPIO.setup(self.INTERRUPT_PIN, GPIO.IN) self.standbyflag=0 self.count = 0 self._init_array() def _init_array(self): self.detected_times = deque([]) def _calc_heart_rate(self): return 1200.0 / (self.detected_times[-1] - self.detected_times[0]) def _interrupt(self): self.detected_times.append(time.time()) heartbeatLED = HeartBeatLED() if self.standbyflag == 0: heartbeatLED.lightup_chargingLED() if self.standbyflag == 1: heartbeatLED.lightup_standbyLED() if(len(self.detected_times) == 1): return interval = self.detected_times[-1] - self.detected_times[-2] heart_rate = -1 if(len(self.detected_times) >= self.MAX_DETECTED_TIMES_COUNT): heart_rate = self._calc_heart_rate() self.detected_times.popleft() self.standbyflag = 1 ultrasound = UltraSound() distance = ultrasound.calc_distance() if heart_rate != -1 and self.count == 10: payload = {"Heart Rate":heart_rate} requests.post("http://harvest.soracom.io", data=json.dumps(payload)) #print("data transfer harvest") self.count = 0 print(self.count) self.count += 1 if heart_rate != -1 and distance < 20: # ドキドキしてないとき if heart_rate < 80: command = "sudo aplay %s"%("/home/yoyo/soracomug/音声データ/余裕.wav") subprocess.Popen(shlex.split(command)) heartbeatLED = HeartBeatLED() heartbeatLED._lightup_blueLED() # めっちゃドキドキしてるとき if 80 <= heart_rate: command = "sudo aplay %s"%("/home/yoyo/soracomug/音声データ/心拍急上昇.wav") subprocess.Popen(shlex.split(command)) heartbeatLED = HeartBeatLED() heartbeatLED._lightup_redLED() self.standbyflag = 0 print("HeartRate: {heart_rate}, Interval: {interval}".format(heart_rate = heart_rate, interval = interval)) def execute(self): print('Please ready your heart rate monitor.') time.sleep(1) while True: GPIO.wait_for_edge(self.INTERRUPT_PIN, GPIO.RISING) self._interrupt() if __name__ == '__main__': # ピン番号をGPIOで指 monitor = HeartRateMonitor() monitor.execute() ``` # SORACOMの設定 [公式ページ](https://soracom.jp/recipes_index/5413/#SORACOM_Lagoon)を参考にHarvestにデータを貯めていき、Lagoon可視化できるようにしました!  # ドキドキLTの実践 実際のLTにて、作り方を説明しながら、ドキドキを共有する発表をしました。 平常時の心拍数がだいたい60~85くらい、LT中の心拍数がだいたい75~110くらい!15~25くらい高い結果が出ました。    ドキドキ感を共有しながら発表したことで、すごく温かく見守ってくれて本当に思い出に残るLTができました! 頭の中のアイデアを具体化し、実践できたことはとても貴重な経験でした。 これからもInternet of Feeling、自分の気持ちや感情を伝えるような作品を作っていければと思っております。 # 余談 実は初号機があって、初号機はシリアルLEDでなくてシグナルタワーで作りました。 大型だったので会場にダンボールで持ち運んで、また電源の確保とか大変だったりして、今のキーホルダ版になっています。 初号機はこちらです。 @[youtube](https://www.youtube.com/watch?v=XgSBRFSUKxQ)