姿勢が悪いので、自分の姿勢を強制させる装置を作ったら逆に肩が凝りまくった。

## これは将来が心配だ

皆さんは、姿勢が良いほうですか？
私は、最近どんどん悪くなっていることを感じています。
危機感を持っているけど、気が付けば猫背になっています。もう猫になりたいです:smiley_cat:

そんな中、会社の朝礼のラジオ体操にて腰に電撃が走ってしまい、さらに姿勢をよくすることを余儀なくされてしましました:sob:

ですので、手元にあるもので姿勢を正せるものを作れないか？と考えました。
いや、正すというよりも矯正させるものを作れないか？

**つまり、姿勢矯正装置です:exclamation:**

## 目指すもの

姿勢の状態を検知して、姿勢が悪くなったと判断したら何かで知らせるものを作ることにしました。
できれば、**持ち運び可能**でどこでも使用できるものにしたいのと、どこでも使えることを前提に周りに迷惑が掛からないよう音とか光ではない知らせ方を模索したいなと思いました！



## 姿勢矯正装置の完成デモ

試行錯誤した末にできた装置です。
どこにでも持ち運べるということで、**帽子に装置をつけることにしました。**

### 仕組み

帽子に付いている距離センサーで、現在の距離を取得します。その距離をLINEから設定し、装置をスタートさせると設定された距離から現在距離が動いた分サーボモーターが動作し、割り箸が私の視界を邪魔します。
視界をクリアにするには、姿勢を元に位置に戻し、設定距離に近づける必要があります。
装置の稼働時間がきたら、LINEにて、姿勢悪くなったときに加算されるアウトポイントが通知されます。

**見た目は、かっこよい！はず！**

https://twitter.com/UmeUmeTanTanR/status/1581309668218920960

### 完成デモ

https://twitter.com/UmeUmeTanTanR/status/1581494689370628099

https://twitter.com/UmeUmeTanTanR/status/1581505183351128065

終了すると、姿勢が悪かったアウトポイントをプッシュしてくれます。
アウトポイントは設定距離に対する移動率%にて計算されます。
このあたりの計算は[ソースコード](#ソースコード)をご覧下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2756612/0dc224fd-a2ce-99af-7b0b-0653cdb5f7c2.jpeg" width="30%">

## 作り方

### 使用した部品

**obniz**
全体を制御する**Iot開発ボード**です。
今回は**LINE Bot**を使用するためにサーバー側としても動作させています。
事前設定などは、下の公式サイトに載っています。

https://obniz.com/ja/

**サーボモーター**
視界を邪魔するために使用しています。そのためトルク力の強いものを使用しました。

 > 使用した部品：https://akizukidenshi.com/catalog/g/gM-08913/

**超音波距離センサー**
姿勢の状態の変化を検知するために使用しています。

 > 使用した部品：https://akizukidenshi.com/catalog/g/gM-11009/


**ブレッドボード**
距離センサーを帽子の先端に設置するために使用しました。
 > 使用した部品：https://akizukidenshi.com/catalog/g/gP-05155/

### 使用する技術

**LINE Bot(LINE Messaging API)**
距離の設定と、稼働時間の設定等に使用しています。
最後は、姿勢が悪くなったアウトポイントをこちらに出力します。
詳細の作成方法は以前私が作成した記事にて書いてますので、そちらを参考にしてください。

https://qiita.com/UmeTanTan/items/d2329448990ea7cf0d53

 > 参考URL：[LINE Bot(LINE Messaging APIのドキュメント)](https://developers.line.biz/ja/)

### 出来上がった装置

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2756612/078f8be4-7103-b1b1-67ed-c13937401081.jpeg" width="50%"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2756612/cd3aae4a-8b8d-e92c-1672-d18df1a99788.jpeg" width="29%">


### 環境
- Node.js v16.16.0
- npm v8.11.0
- obniz v3.23.0
- line/bot-sdk v7.5.2
- axios v0.27.2
- express v4.18.1
- Visual Studio Code v1.71.2





### ソースコード
<details><summary><font color="Red">*****ご覧になりたい方はこちらを見てください*****</font ></summary><div>

``` javascript: positionCorrent.js
'use strict';
const express = require('express');
const line = require('@line/bot-sdk');

// ローカルポート3000で起動する
const PORT = process.env.PORT || 3000;

// ご自身の環境ごとに設定する必要があります
const config = {
    channelSecret: '***************************',
    channelAccessToken: '******************************************'
};
const client = new line.Client(config);
const Responses = Object.freeze({
    INIT: Symbol(0),
    DISTANCE: Symbol(1),
    TIME: Symbol(2),
    START: Symbol(3),
    END: Symbol(4),
});

let distance;
let setTimeData = 0;
let checkDistance = 0;
let Status = Responses.INIT

// pushメッセージ
const push = async (pushData) => {
    const messages = [{
        type: 'text',
        text: pushData
    }];
    try {
        const res = await client.broadcast(messages);
        console.log(res);
    } catch (error) {
        console.log(`エラー: ${error.statusMessage}`);
        console.log(error.originalError.response.data);
    }
}

// メッセージリプライ処理
async function handleEvent(event) {
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    let responsData = "準備中です。";
    
    // 現在の状態事に返答する内容を変更する
    switch (Status) {
        case Responses.DISTANCE:
            if (event.message.text.indexOf("OK") >= 0) {
                Status = Responses.TIME;
                responsData = "時間を設定してください";
                // console.log(event.message.text.indexOf("時間を設定する"))
            }
            else {
                checkDistance = distance;
                responsData = "現在の距離は" + checkDistance + "です。この距離で設定しますか？";
            }
            break;
        case Responses.TIME:
            if (setTimeData != 0 && event.message.text.indexOf("OK") >= 0) {
                TimerCount = 0;
                Status = Responses.START;
                responsData = "姿勢を正しくしてください";
            }
            else {
                setTimeData = Number(event.message.text);
                responsData = "設定距離は" + checkDistance + "mm" + "\n";
                responsData += "設定時間は" + setTimeData + "秒です。スタートしますか?";
            }
            break;
        case Responses.START:
            responsData = "落ち着いてください"
            break;
        case Responses.END:
            responsData = "終了しました"
            break;
        default:
            break;
    }
    console.log("responsData: " + responsData);

    // ユーザーにリプライメッセージを送ります。
    return client.replyMessage(event.replyToken, {
        type: 'text', // テキストメッセージ
        text: responsData // ← ここに入れた言葉が実際に返信されます
        // event.message.text には、受信したメッセージが入っているので、それをそのまま返信しています
        // ここを 'テスト' のように書き換えると、何を受信しても「テスト」と返すようになります
    });
}

// ここ以降は理解しなくてOKです
const app = express();
app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)'));
app.post('/webhook', line.middleware(config), (req, res) => {

    if (req.body.events.length === 0) {
        res.send('Hello LINE BOT! (HTTP POST)');
        console.log('検証イベントを受信しました！');
        return;
    } else {
        console.log('受信しました:', req.body.events);
    }
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

const Obniz = require('obniz');
// ご自身の環境ごとに設定する必要があります
const obniz = new Obniz('**********'); // Obniz_IDに自分のIDを入れます
let isEndSend = false;
let TimerCount = 0;
let Steps = 0;
let StepLimit = 0;
let OutPoint = 0;

// 距離判定処理
function rageCheck() {
    // 判定閾値を計算
    let firstStepUp = checkDistance * 1.1;
    let firstStepDown = checkDistance * 0.9;
    let secondStepUp = checkDistance * 1.2;
    let secondStepDown = checkDistance * 0.8;
    let thirdStepUp = checkDistance * 1.3;
    let thirdStepDown = checkDistance * 0.7;
    let fourthStepUp = checkDistance * 1.4;
    let fourthStepDown = checkDistance * 0.6;
    let fifthStepUp = checkDistance * 1.5;
    let fifthStepDown = checkDistance * 0.5;
    let sixStepUp = checkDistance * 1.6;
    let sixStepDown = checkDistance * 0.4;
    let sevenStepUp = checkDistance * 1.7;
    let sevenStepDown = checkDistance * 0.3;
    let eightStepUp = checkDistance * 1.8;
    let eightStepDown = checkDistance * 0.2;
    let nineStepUp = checkDistance * 1.9;
    let nineStepDown = checkDistance * 0.1;
    let tenStepUp = checkDistance * 2.0;
    let tenStepDown = checkDistance * 0;
    let endStep = NaN;

    // console.log(
    //     "firstStepUp: " + firstStepUp + "\n" +
    //     "firstStepDown: " + firstStepDown + "\n" +
    //     "secondStepUp: " + secondStepUp + "\n" + 
    //     "secondStepDown: " + secondStepDown + "\n" +
    //     "thirdStepUp: " + thirdStepUp + "\n" +
    //     "thirdStepDown: " + thirdStepDown + "\n" +
    //     "endStep: " + endStep + "\n"
    // )

    // 判定処理
    if(firstStepUp >= distance && firstStepDown <=  distance ){
        StepLimit = 0;
    }
    else if(secondStepUp >= distance && secondStepDown <=  distance ){
        StepLimit = 1;
    }
    else if(thirdStepUp >= distance && thirdStepDown <=  distance ){
        StepLimit = 2;
    }
    else if(fourthStepUp >= distance && fourthStepDown <=  distance ){
        StepLimit = 3;
    }
    else if(fifthStepUp >= distance && fifthStepDown <=  distance ){
        StepLimit = 4;
    }
    else if(sixStepUp >= distance && sixStepDown <=  distance ){
        StepLimit = 5;
    }
    else if(sevenStepUp >= distance && sevenStepDown <=  distance ){
        StepLimit = 6;
    }
    else if(eightStepUp >= distance && eightStepDown <=  distance ){
        StepLimit = 7;
    }
    else if(nineStepUp >= distance && nineStepDown <=  distance ){
        StepLimit = 8;
    }
    else if(tenStepUp >= distance && tenStepDown <= distance ){
        StepLimit = 9;
    }
    else {
        StepLimit = 10;
    }
    // 動いたときのポイントを計算する
    if (Steps > StepLimit){
        OutPoint += StepLimit;
    }
    else if (Steps < StepLimit){
        OutPoint += StepLimit;
    }
    else {
        
    }
    Steps = StepLimit;

}

// obnizと接続できたら入る
obniz.onconnect = async () => {
    // 超音波距離センサを利用
    const hcsr04 = obniz.wired('HC-SR04', {
        gnd: 7,
        echo: 6,
        trigger: 5,
        vcc: 4,
    });

    // サーボモータを利用
    const servo = obniz.wired('ServoMotor', { gnd: 0, vcc: 1, signal: 2 });

    // ディスプレイ表示（初期画面）
    obniz.display.clear();
    obniz.display.print('Hello obniz!');
    push("準備ができるまでお待ちください")

    setInterval(async () => {
        TimerCount += 2;
        // 距離を取得
        distance = await hcsr04.measureWait();
        distance = Math.floor(distance);
        // 距離(mm)をターミナルに表示
        console.log(distance + ' mm');
        console.log(TimerCount + ' 秒');
        // 状態ごとに動作処理を変更する
        switch (Status) {
            case Responses.INIT:
                if (TimerCount >= 1) {
                    Status = Responses.DISTANCE
                    push("準備が完了しました")
                    servo.angle(10);
                }
            case Responses.DISTANCE:
            case Responses.TIME:
            case Responses.END:
                break;
            case Responses.START:
                // 距離を判定
                rageCheck()
                // サーボモーターを動作
                servo.angle(10 + 16 * Steps);
                console.log("Steps: " + Steps)
                console.log("StepLimit: " + StepLimit)
                // 時間判定
                if (TimerCount >= setTimeData) {
                    Status = Responses.END
                    if (!isEndSend){
                        isEndSend = true;
                        push("終了しました。アウトポイントは" + OutPoint + "ポイントです")
                        servo.angle(10);
                    }
                }
                // push("時間が来るまでそのままの姿勢で仕事を続けてください")
                break;
            default:
                push("準備が完了しました?")
                break;
        }
    }, 2000);

};


```

</div></details>

## 使ってみた感想

上手く動いているような、そうではないような？笑
姿勢を正した状態で固定するのには役立ちそうなのですが、測定している起点が頭の上なので首すら動かせない拷問を受けているような気がする、、、:dizzy_face:

稼働時間が１分ほどでも首にダメージを負い、肩が凝りそう、、、今度は肩凝り解消装置が必要になるかも？

結論としては、まだまだ改善の余地はありそうです！
また、姿勢が悪くなったアウトポイントはただ通知しているだけなのですが、そのポイントを使ってさらなる**~~拷問~~**じゃなくて、動きもできそうだなと思いました！

とりあえず、視界を覆うのがなんだか割りばし一本だと心許ないので、もっと視界を邪魔するように先端に色々つけてやろうかと思っています！

最後までお読みいただきありがとうございました！

::: note warn
- 気になること

サーボモーターの動作を激しくすると、たまにobnizのWifiが切れてしまいます、、、サーボモーターの負荷が大きくなるからなのか、他に原因があるからなのか、、、どなたか知っている人がいれば教えてください！
:::