enebular cloudからtello(EDUじゃない)を動かしてみる

# 動機

- Obniz Cloudからtelloを動かせた。
- ということはenebularからnode-redでtelloを制御できるのでは？と考えた。

# 用意するのもの

| 物品（サービス） | 説明 | 備考 |
|:-:|:-:|:-:|
| tello | UDP経由で制御することもできる小型ドローン | https://store.dji.com/jp/product/tello?vid=38421 |
| enebular cloud | 無料でクラウドからnode-redを触れる環境  |  https://www.enebular.com/ja/ |
| Obniz Board 1Y  | Obniz Cloudを介してIoTできるESP32ボード  | https://obniz.com/ja/  |
|  M5Atom lite |  M5Stack社が出している小型の筐体付きESP32ボード | https://shop.m5stack.com/products/atom-lite-esp32-development-kit  |
| ジャンパー ｘ4本| オス-オスのジャンパ | 秋月とかで入手 |

あと、モバイルバッテリーなどObniz Boardに給電できるものがあればOK

# 構成
事前にM5Atom liteに対して[tello-UART-porter](https://github.com/henjin0/tello_UART_porter)を書き込んでおく。その後、
Obniz Board 1YとM5Atom liteをUART通信するために結線すればハードウェアの準備は完了。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271381/e5782124-fafd-50ce-ba3d-681c24bdbb78.jpeg" alt="UARTでObniz　BoardとM5Atomを結線" width="500">

## tello-UART-Porterについて
要はUARTから送られたコマンドを解釈してtelloへ制御指定を送ってくれるやつです。
インストール方法などはREADMEを参照する。

https://github.com/henjin0/tello_UART_porter

# enebular側の構成について。

マイコンのネット接続はObniz OSならびにObniz Cloudにおまかせする。
また、ネット上の処理はenebular Cloud上で済ます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271381/ede43cfe-74e7-1625-a668-817d04a7043f.png" alt="構成図" width="800">

enebularってなに？っていう方はこちら。

https://qiita.com/horihiro/items/d1f952be57627f12c9ce

Node−RED上からObnizを使う際にはこちらを使う。enebularのメニューから「パレットの管理」に入り、ノードの追加で下記ノードを追加する。執筆当時のバージョンは0.6.2。

https://flows.nodered.org/node/node-red-contrib-obniz

# フローについて

Obniz nodeにてUARTを使用する。また、UARTからtello-UART-Porterで使えるコマンドを流せば基本的に操作できる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271381/d890ce65-5b42-12be-e21c-9da51f54bf82.png" alt="フロー図見た目" width="800">

injectノードから文字列を投げて、changeノードでは改行コードをいれているだけ。それをobnizからUART経由でtello-UART-porterに流して終わり。

obnizのコードはこちら。
```javascript:obniz function
obniz.uart0.start({ tx: 11, rx: 10, baud: 115200 });

obniz.uart0.onreceive = function (data, text) {
    obniz.display.clear();
    obniz.display.print(text);
    msg.payload = data;

    // 非同期だからreturnで出力できないよ！
    node.send(msg);
}
```



<details><summary>flow.jsonはこちら</summary>

```json:flow.json
[
    {
        "id": "11f4d8766d32a0bc",
        "type": "change",
        "z": "26a1f1b0b488b867",
        "name": "CRLFをつけるところ",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "$join([msg.payload,\"\\r\\n\"],\"\" )\t",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 380,
        "y": 140,
        "wires": [
            [
                "8308cb75c0993286"
            ]
        ]
    },
    {
        "id": "709c86aac72e0336",
        "type": "inject",
        "z": "26a1f1b0b488b867",
        "name": "takeoff",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "takeoff",
        "payloadType": "str",
        "x": 110,
        "y": 40,
        "wires": [
            [
                "11f4d8766d32a0bc"
            ]
        ]
    },
    {
        "id": "d0a49b9015af0cc2",
        "type": "debug",
        "z": "26a1f1b0b488b867",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 810,
        "y": 140,
        "wires": []
    },
    {
        "id": "b9c2ef92620a21a0",
        "type": "inject",
        "z": "26a1f1b0b488b867",
        "name": "land",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "land",
        "payloadType": "str",
        "x": 110,
        "y": 100,
        "wires": [
            [
                "11f4d8766d32a0bc"
            ]
        ]
    },
    {
        "id": "b14dbd55baeb8013",
        "type": "inject",
        "z": "26a1f1b0b488b867",
        "name": "rotateclockwise 300",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "rotateclockwise 300",
        "payloadType": "str",
        "x": 150,
        "y": 180,
        "wires": [
            [
                "11f4d8766d32a0bc"
            ]
        ]
    },
    {
        "id": "0c55611bde4e9d7c",
        "type": "debug",
        "z": "26a1f1b0b488b867",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 610,
        "y": 300,
        "wires": []
    },
    {
        "id": "be7bbf59799caa20",
        "type": "inject",
        "z": "26a1f1b0b488b867",
        "name": "init",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 90,
        "y": 300,
        "wires": [
            [
                "7f4fce927cd14e7f"
            ]
        ]
    },
    {
        "id": "7f4fce927cd14e7f",
        "type": "obniz-function",
        "z": "26a1f1b0b488b867",
        "obniz": "e95927ee07d37b17",
        "name": "",
        "code": "obniz.uart0.start({ tx: 11, rx: 10, baud: 115200 });\n\nobniz.uart0.onreceive = function (data, text) {\n    obniz.display.clear();\n    obniz.display.print(text);\n    msg.payload = data;\n\n    // 非同期だからreturnで出力できないよ！\n    node.send(msg);\n}\n",
        "x": 360,
        "y": 300,
        "wires": [
            [
                "0c55611bde4e9d7c"
            ]
        ]
    },
    {
        "id": "8308cb75c0993286",
        "type": "obniz-function",
        "z": "26a1f1b0b488b867",
        "obniz": "e95927ee07d37b17",
        "name": "",
        "code": "obniz.uart0.send(msg.payload);\n",
        "x": 600,
        "y": 140,
        "wires": [
            [
                "d0a49b9015af0cc2"
            ]
        ]
    },
    {
        "id": "77fed006dc46d48b",
        "type": "comment",
        "z": "26a1f1b0b488b867",
        "name": "実際にコマンドを送るところ",
        "info": "",
        "x": 340,
        "y": 40,
        "wires": []
    },
    {
        "id": "5f0cca9ffd9047e5",
        "type": "comment",
        "z": "26a1f1b0b488b867",
        "name": "UART初期化するところ",
        "info": "",
        "x": 120,
        "y": 240,
        "wires": []
    },
    {
        "id": "e95927ee07d37b17",
        "type": "obniz",
        "obnizId": "xxxx-xxxx",
        "deviceType": "obnizboard1y",
        "name": "",
        "accessToken": "",
        "code": ""
    }
]
```
</details>

ね、簡単でしょ？...と言いたいところだけど、実を言うと改行コードで丸一日ハマっていました。tello-UART-porterで使っているSerialCommandでは改行コードがCRLF(Windowsとかでよくあるやつ)になっていて、enebularで"\n"を送ってもLFしかできないので動作しないのです。なので、文字列の末尾にはCRを表す"\r"も合わせて付与しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271381/003aed5a-75b3-d165-95d8-2107ed392c6c.png" alt="CRLF" width="400">

とはいえ、命令が抽象化されているって便利。

# 動かしてみた

<iframe width="560" height="315" src="https://www.youtube.com/embed/2cY3VI0IOoY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# おわりに

- クラウド環境からドローン動くのいいよね。
- 一応別のストリームで動画も取得できるっぽい。データ量とかを考えるとラズパイあたりを使って制御したほうがいいんかなぁ。
- **改行コードには気をつけよう！**