obnizで腹筋カウンター ＆ line bot

Proto out studioの秋葉原ツアーで購入したいくつかのセンサーから発想してみました。
[HC-SR04](http://akizukidenshi.com/catalog/g/gM-11009/)という超音波で距離を測ってくれるモジュールを利用した腹筋回数カウンターを作ります。
最近お腹出てきたので運動しなきゃなーということで腹筋。


# 作る

## まずは距離がちゃんと取れるか

腹筋するときの正面の壁にHC-SR04を設置、腹筋で体を起こすと体とセンサーとの距離が縮むので、それで腹筋した回数が取れるはず。
とりあえず設置して腹筋してみた。

```js
obniz.onconnect = async () => {
  const hcsr04 = obniz.wired('HC-SR04', {gnd:0, echo:1, trigger:2, vcc:3})
  hcsr04.temp = 27
  while(true) {
    const val = await hcsr04.measureWait()
    console.log(val)
  }
}
```

結果一部抜粋

```
2303.2190782456228
2282.5802928306207
2285.875561090158
2298.709763785202
2325.0719098615073
1219.5961263722302
1239.367735929459
984.0711634010286
2520.706783375141
1464.8334589504918
2345.3638249334003
2311.543966480245
2319.695419543313
2293.853578981672
2345.884130448064
2298.362893442092
2315.7064105975564
1335.2773857991754
1158.2000756418875
2560.2500024895994
2538.5706060452694
1988.6076770455052
601.6466101230471
1140.3362529717594
1365.4551056496825
2310.8502257940268
2311.023660965581
2304.0862541033957
2306.6877816767155
2336.865501527223
2315.0126699113384
1167.9124452489475
2516.370904086276
999.853764012501
838.3856192951309
```

寝た状態からスタートしているので
設置した向かいの壁までの距離→体を起こした時の距離の繰り返しになっているはずだけど、、
体を倒した状態でも2303 ~ 1167ぐらいの振れ幅がある。
まぁでも、値の大小を繰り返しているからこれで試してみる。

# 腹筋回数測定

上記の結果では体を起こした時の最大値が999なので、最大を1000として1000以下の時は体を起こしている、それ以上は倒しているとする。
また、体を倒しきる前に、体がセンサーからずれて背面の壁までの距離を返してしまうことがあるので、一度体を起こしきったら次の距離測定まで1.5秒（おおよその体を倒すまでの時間）待つという処理を入れる
ここまでのコード

```js
obniz.onconnect = async () => {
  const hcsr04 = obniz.wired('HC-SR04', {gnd:0, echo:1, trigger:2, vcc:3})
  hcsr04.temp = 27
  //体を起こした時の最大距離
  const minDistance = 1000
  const count = 0
  let isLean = true
  const detectLean = (distance) => {
    //体を倒した状態の時は常にtrue
    //体を起こしきって初めて距離の判定をする(isLean = falseになるので)
    return isLean || distance > minDistance
  }
  while(true) {
    const distance = await hcsr04.measureWait()
    isLean = detectLean(distance)
    currentAngle = distance > minDistance ? 90 : 0

    //体を起こしてから倒しきるまでは距離がminDistance以下であってもカウントしない
    if (distance && distance <= minDistance && isLean) {
      isLean = false
      console.log(++count)
      await obniz.wait(1500)
    }
  }
}
```

一旦これで、腹筋の回数は取れるようになった。

# line botと組み合わせる

この腹筋カウンターを、前回宿題で作った[英語学習支援ボット](https://qiita.com/karu/items/e865189a2524d98b1cba)と組み合わせてみる。
前回作成したボットでは一度翻訳した単語をもう一度聞くと「思い出して」と返して自力で思い出すよう促してくれる。
思い出せない時は「ごめん、教えてください」と言うと教えてくれる。
今回は、この「ごめん、教えてください」を送った時にすんなりと教えるのではなく
腹筋5回したら教えるという流れにする。(開発中なので5回です。本来であればもっと。。)

## 実装

- 前回作成のserver.js　*全文だと長くなるので腹筋
への流れのみ
- Fukkin.js（腹筋カウンター用）

**server.js**

```js
const Fukkin = require('./fukkin')

async function handleEvent(event) {
  const countFukkin = count => {
    pushMsg(userId, `${count}!`)
  }
  const finishFukkin = async count => {
    answer = parseAnswer(snapShot.test.answer)
    await pushMsg(userId, `おつ!`)
    updateTest(doc, nextAnswer)
    await pushMsg(userId, `ほれ!\n${answer}`).then(() => {
      return afterReplyAnswer(shuldUpdateExsam, nextAnswer, replyToken, current ? current.word : null)
    })
  }

  //腹筋スタート
  if (isGiveUp) {
    setTimeout(() => {
      pushMsg(userId, '腹筋スタート!')
      new Fukkin(5, countFukkin, finishFukkin)
    }, 2000)
    return pushMsg(userId, '腹筋5回やったら教えてあげる!')
  }

}

```

**Fukkin.js**

```js
const Obniz = require('obniz')
const obnizId = '{obnizのID}'
const distanceSencerName = 'HC-SR04'
const servoName = 'ServoMotor'

/**
 * @param {number} howMany 腹筋目標回数
 * @param {function} onCount カウント時のコールバック
 * @param {function} onFinish 腹筋目標回数達成時のコールバック
*/

class Fukkin{
  constructor(howMany, onCount, onFinish) {
    this.start(onCount)
    this.onFinish = onFinish
    this.howMany = howMany
  }

  start(onCount) {
    this.initObniz()
    this.obniz.onconnect = () => {
      this.startCount(onCount)
    }
    this.count = 0
  }

  initObniz() {
    const obniz = new Obniz(obnizId, {
      auto_connect: false,
      reset_obniz_on_ws_disconnection: false
    })
    obniz.connect()
    this.obniz = obniz
  }

  close() {
    this.obniz.close()
  }

  initSensor() {
    this.distanceSencer = this.obniz.wired(distanceSencerName, {gnd:8, echo:9, trigger:10, vcc:11})
    this.servo = this.obniz.wired(servoName, {gnd:0, vcc:1, signal:2})
    this.distanceSencer.temp = 26
  }

  async startCount(onCount) {
    this.initSensor()
    let currentAngle = 0
    let lastAngle = 0
    let isLean = true

    //体を起こした時の最大距離
    const minDistance = 1000

    const detectLean = distance => {
      //体を倒した状態の時は常にtrue
      //体を起こしきったあと(isLean = false)で初めて距離の判定を開始する
      return isLean || distance > minDistance
    }

    while(this.count < this.howMany) {
      const distance = await this.distanceSencer.measureWait()
      isLean = detectLean(distance)
      currentAngle = distance > minDistance ? 90 : 0

      console.log(distance)

      if (currentAngle !== lastAngle) {
        this.servo.angle(currentAngle)
        lastAngle = currentAngle
      }

      //体を起こしてから倒しきるまでは距離がminDistance以下であってもカウントしない
      if (distance && distance <= minDistance && isLean) {
        ++this.count
        isLean = false
        //カウント時のコールバック関数(ラインのメッセージpush)
        onCount(this.count)
        //体を倒しきる前に体がセンサーからづれて
        //倒したと判定してしまうので、1500msスリープさせる
        await this.obniz.wait(1500)
      }
    }
    this.servo.angle(90)
    //完了時のコールバック
    this.onFinish(this.count)
    this.close()
  }
}

module.exports = Fukkin
```

#こんな感じになりました

せっかくなので、サーボモーターも使って、一緒に腹筋してくれる君も作ってみました。
**[![IMAGE ALT TEXT HERE](http://img.youtube.com/vi/fdC3v8YLRjQ/0.jpg)](http://www.youtube.com/watch?v=fdC3v8YLRjQ)**

# まとめ
今回使ったセンサーは、ある程度範囲を決めてこの値以上であれば何かをするとかの、
アバウトな用途であれば問題ない気もするけど
返す数値にかなりぶれがあるので、正確に何かを測りたいといのには向いていない気がする。
今回の腹筋に関しても、使い方がまずいのかセンサーがまずいのかちょっと分からないが、寝っ転がってじっとしててもカウントしちゃったりとかしてて、微妙な位置調整とかしました。
とはいえ、初Iotなので自分の動きに対してボットが反応してくれるのは純粋に面白かった。他のセンサーもガンガン試していきたい。

# そういえば
今回APIと繋げてない。。
ということで、API絡ませたバージョンで腹筋シリーズ[もう一個](https://qiita.com/karu/items/af1d5fc60ab4882be930)作ってみました。