SENSYN ROBOTICS(センシンロボティクス)の中山です。
Webアプリやそのインフラ周り、Web側とドローンの接続を行うデバイスドライバ的なモジュールを担当しています。
今回は、ドローン内部で動かすソフトウェアにGoを使った話です。上記の担当範囲から外れているように見えますが、そういうこともあります。
なんのために?
ドローンといっても色々あるのですが、今回はARM64のCPU(NVIDIA TX1)が載ったドローンです。CPUの上ではUbuntu 16.04が動いています。具体的にはSENSYN DRONE HUBというドローンです。
このドローンは、あらかじめ設定しておいた時刻になると自動的に基地から離陸し、事前に設定しておいたルートを飛行して動画・静止画を撮影し、基地に返ってきます。返ってきたら自動的に充電して次回の飛行に備えつつ、撮影したデータを自動的にクラウドにアップロードします。
つまり、ドローンの存在を意識せずにドローンによる設備点検や警備・監視ができるソリューションです。
今回、Goを使ったのはデータをクラウドにアップロードするモジュールで、ドローンの内部で動いています。
なぜGoなのか?
当初はGoを使うつもりはなく、Pythonで書く予定でした。ただ、Ubuntu上には既にドローンの制御機構が構築されており、そちらが一部にPythonを使っている都合上、既存の環境への干渉は絶対に避ける必要があります。ところが事前に入っているUbuntuにはpipすら入っておらず、環境分離ツールを入れること自体が難しい状況です。そのため、既存の環境を汚さずに新しいモジュールを入れるのは不可能ではないにしても、非常に面倒でした。
Pythonが使えないとなると…と悩んだ末、Goならシングルバイナリにできるので既存環境への干渉を避けたいときに理想的ではないか、と気づいて採用を決定しました。ARM64 Linux対応、というのも必須条件ですが、Goならこれも満たしています。
上記の理由だけならC++でも良かったのですが、Goの方がメンテできるひとが将来的に多くなりそう、というのもあります。
実際にやってみると?
環境を汚したくないので、コンパイル環境をドローンの中に作るわけにはいきません。そうなると、必然的にクロスコンパイルが必要になります。手元の開発環境で試行錯誤すると後が怖いので、Dockerを使いました。
docker run \
-v $(pwd):/go \
--rm \
-it \
dockercore/golang-cross:1.12.7 \
sh -c 'GOOS=linux GOARCH=arm64 CGO_ENABLED=1 go build src/main.go'
(少し前の話なので、バージョンは古めです)
# runtime/cgo
gcc_arm64.S: Assembler messages:
gcc_arm64.S:27: Error: no such instruction: `stp x19,x20,[sp,'
gcc_arm64.S:28: Error: no such instruction: `stp x21,x22,[sp,'
gcc_arm64.S:29: Error: no such instruction: `stp x23,x24,[sp,'
gcc_arm64.S:30: Error: no such instruction: `stp x25,x26,[sp,'
gcc_arm64.S:31: Error: no such instruction: `stp x27,x28,[sp,'
gcc_arm64.S:32: Error: no such instruction: `stp x29,x30,[sp,'
gcc_arm64.S:33: Error: too many memory references for `mov'
gcc_arm64.S:35: Error: too many memory references for `mov'
gcc_arm64.S:36: Error: too many memory references for `mov'
gcc_arm64.S:37: Error: too many memory references for `mov'
gcc_arm64.S:39: Error: no such instruction: `blr x20'
gcc_arm64.S:40: Error: no such instruction: `blr x19'
gcc_arm64.S:42: Error: no such instruction: `ldp x29,x30,[sp],'
gcc_arm64.S:43: Error: no such instruction: `ldp x27,x28,[sp],'
gcc_arm64.S:44: Error: no such instruction: `ldp x25,x26,[sp],'
gcc_arm64.S:45: Error: no such instruction: `ldp x23,x24,[sp],'
gcc_arm64.S:46: Error: no such instruction: `ldp x21,x22,[sp],'
gcc_arm64.S:47: Error: no such instruction: `ldp x19,x20,[sp],'
エラーになりました…。
色々調べて、
FROM dockercore/golang-cross:1.12.7
RUN apt-get update \
&& apt-get install -y gcc-aarch64-linux-gnu
というDockerfileを作り、このイメージでgo build
しても、同じ結果…。
更に調べ回って、上記のDockerイメージに加えてCC=aarch64-linux-gnu-gcc
をコマンドラインに足した
docker run \
-v $(pwd):/go \
--rm \
-it \
my-golang-image \
sh -c 'GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build src/main.go'
で最終的に通るようになりました。
結論
- 環境を汚さないためにGoを使うのは有効
- クロスコンパイルはできるようになれば簡単けど、そこまでがちょっと大変
- Dockerを使うとトライ&エラーを気楽にできて便利
- ARM64 Linuxをターゲットにクロスコンパイルするなら
gcc-aarch64-linux-gnu
を入れて、CC=aarch64-linux-gnu-gcc
を環境変数に設定する