概要
fitbit(alta HR)で心拍数をモニタリングして、心拍数が0になったらSNSに「死にました」と投稿するスクリプトを書きました。
リポジトリ
僕はほぼ24時間fitbitを付けっぱなしにしているので、fitbitごと壊れる死に方でない限り大体カバーできるはずです。
fitbit alta HRとは
- リストバンド型ウェアラブル端末の1つで、常時心拍数などを測れます
- Pure Pulseという技術で従来のデバイスより正確に測れるらしいです
- APIが充実しています
Fitbit APIで心拍数を監視する
Fitbitアプリを登録する
- こちらから登録できます
- 心拍数データを取るためには「OAuth 2.0 Application Type」を「Personal」にする必要があります
心拍数を取得する
- python-fitbitを使わせてもらいました。使い方は他に多くの解説記事があるので割愛
- access_tokenには有効期限があるので、fitbit.Fitbitをコールする時、refresh_cbを指定して自動更新されるようにしておく
- 時間と心拍数を分けて取り出してるのは、後述の回帰分析で使うためです
class FitbitClient(object):
def __init__(self):
self.client_id = os.getenv('FITBIT_CLIENT_ID')
self.client_secret = os.getenv('FITBIT_CLIENT_SECRET')
token_file_name = os.getenv('TOKEN_FILE_NAME')
tokens = open(token_file_name).read()
token_dict = literal_eval(tokens)
self.access_token = token_dict['access_token']
self.refresh_token = token_dict['refresh_token']
def refresh_call_back(token):
with open(token_file_name, 'w') as f:
f.write(str(token))
self.access_token = token['access_token']
self.refresh_token = token['refresh_token']
self.client = fitbit.Fitbit(
self.client_id, self.client_secret,
access_token=self.access_token, refresh_token=self.refresh_token,
refresh_cb=refresh_call_back
)
def request_heart_beats(self, date, limit=30):
data_sec = self.client.intraday_time_series('activities/heart', date, detail_level='1sec')
heart_sec = data_sec['activities-heart-intraday']['dataset']
heart_sec = heart_sec[-limit:]
seconds = map(self.fetch_sec, heart_sec)
beats = map(lambda data: data['value'], heart_sec)
return seconds, beats
@staticmethod
def fetch_sec(data):
t = time.strptime(data['time'], '%H:%M:%S')
return t.tm_hour * 3600 + t.tm_min * 60 + t.tm_sec
シャワーに行くたび死ぬのを防ぐ
心拍数を取得するところまでは簡単にできます。
あとはどうやって死んだことを確認するかですが、安直に「心拍数が0か、あるいはデータが返ってこなかったら死亡」とやってしまうと、シャワー浴びにちょっと外しただけで死にかねません。
そこで「直近30個のデータから回帰分析し、60秒後の心拍数が10未満と予測されたら死んだと見なす」というちょっとややこしいロジックで判定することにしました。
衰弱死でも病死でも失血死でも、だいたい死ぬ時は徐々に心拍数が下がってから死ぬだろう、という前提に基づいてます。
サンプルを取りすぎると、急激な運動の後のクールダウンでも誤判定してしまう弱点がありますが、まあ30データ(≒30〜60秒分のデータ)なら大丈夫でしょう。
class PhysicalHealthChecker(object):
def __init__(self, provider, event_emitter):
self.event_emitter = event_emitter
self.heart_beat_provider = provider
seconds, beats = self.heart_beat_provider.request_heart_beats('today')
expected_beat = np.polyval(np.polyfit(seconds, beats, 1), seconds[-1] + 60)
if self.is_alive(expected_beat):
print 'still alive.'
else:
timestamp_file_name = 'death_time.txt'
death_time = open(timestamp_file_name, 'r').read()
if not death_time:
print 'dead.'
self.event_emitter.emit('death')
with open(timestamp_file_name, 'w') as file:
file.write(datetime.now().strftime("%Y/%m/%d %H:%M:%S"))
@staticmethod
def is_alive(heart_beat):
return heart_beat >= 10
if __name__ == '__main__':
ifttt_key = os.getenv('IFTTT_WEB_HOOK_KEY')
fitbitHealthChecker = PhysicalHealthChecker(FitbitClient(), IftttEventEmitter(ifttt_key))
死んだらSNSに投稿する
IFTTTに「If {Webhook} Then {Twitter/Facebook POST}」なAppletを事前に作り、webhookをコールするだけです
class IftttEventEmitter(object):
def __init__(self, secret_key):
self.secret_key = secret_key
def emit(self, event_name):
requests.get('https://maker.ifttt.com/trigger/' + event_name + '/with/key/' + self.secret_key)
- あとはcronなりで回すだけです
既知の問題
- APIで当日分の心拍数しか取ってないので、日付をまたいで死んだら誤作動しそうです
応用編
- 闇の組織と交渉する時に「やめときな。俺が死んだら例のファイルは信頼できる仲間の所に転送される事になってる」というやつができます
- 僕には全く関係無い話ですが、死ぬ時いかがわしいファイルを自動で消したい!という用途にも使えそうです