1 of 53

IoT With TinyGo

Go Conference 2022 Spring

2022/04/23

Masaaki Takasago

2 of 53

# お前だれよ?

  • Name: `Masaaki Takasago`
  • Go、 Perl、 C
  • Twitter: @sago35tk
  • Github: sago35
  • 車載組込ソフトエンジニア
    • ISO11898 (CAN)、 ISO15765-2、 ISO14229
  • TinyGo の中の人 (2020/08/19 〜)
  • Umeda.go、 kansai.pm、 Perl 入学式 in 大阪

3 of 53

Go の好きなところ

  • エディタを選ばない
  • シングルバイナリで配布しやすい
  • Windows でも非同期プログラム書きやすい
  • シンプルな構文
  • gopls / gofmt / goimports
  • Gopher かわいい
  • Go に入れば Go に従え
  • マイコン遊びが出来る ← New

↑ スライドはこちら

4 of 53

はじめに

今日の内容

  • TinyGo とは
  • IoT とは
  • Arduino 等の環境との違い
  • ネットワークにつなごう

↑ スライドはこちら

5 of 53

はじめに - マイコン環境の前提

  • このトークでは Wio Terminal を例として説明します
    • 2022 年時点でコストパフォーマンスがよく TinyGo サポートも進んでいるボード
  • 使い方等は、2021/11/13 Go Conference 2021 Autumn の TinyGo ハンズオンの資料を参照してください
  • ネットワークを使うには、 RTL8720DN Firmware の Updateが必要です

6 of 53

はじめに - つくるもの (HTTP)

  • HTTP アクセスがあったら以下のようなログを出力する
    • 「パソコン + Go」と「マイコン + TinyGo」の両方の環境で動くようにする

2022/04/23 16:32:38 {"message": "hello from Go"}

2022/04/23 16:32:02 {"message": "hello from TinyGo"}

7 of 53

はじめに - つくるもの (MQTT)

  • 以下のようなダッシュボード (のセンサー側) を作る
    • Wio Terminal から温度と湿度を取得し表示する

8 of 53

はじめに - つくるもの (MQTT) の構成

  • PC (ダッシュボード (Node-RED) を動かす)
  • Wio Terminal
  • BME280 (I2C 接続の温度、湿度、気圧センサー)

温度

湿度

I2C

MQTT

pub

MQTT

sub

※MQTT : 軽量プロトコル、マイコンなどの非力なデバイスで良く使われている

PC (Node-RED)

9 of 53

はじめに - おまけ (Node-RED)

  • Node-RED (https://nodered.org/) はブラウザベースのエディタでフローを作成できるツールです
    • node-red-dashboard を使うと、簡単なダッシュボードを作る事が出来ます

MQTT 受信

JSON 整形

流量を制限

Chart で描画

Gauge で描画

なぜ Node-RED ?

IoT で遊ぶ場合、必要となる技術/知識が広範囲になります。マイコン部を作りつつフロントエンド部も作るのはなかなか難しいので、使い始めやすいツールを活用すると良いです。

10 of 53

TinyGo とは

11 of 53

What is TinyGo

12 of 53

TinyGo の動き

  • TinyGo コンパイラは Go で書かれたソースコードを機械語に翻訳する
    • fmt や strconv など Go の標準 package を使用できる
      • ただし Reflection への対応等に制限がある
      • あるいはマイコンのメモリサイズの問題で動かしにくい package もある (image/jpeg など)
    • TinyGo で追加した標準 package もあり
      • マイコンボードの差異を吸収する machine package など

Go source code

Go SSA

LLVM IR

Machine code

13 of 53

TinyGo - Project Scope

14 of 53

TinyGo - ビルド後のバイナリサイズ

  • Hello world プログラムで約 2MB のプログラムサイズとなる Go に対し、 TinyGo は Windows 用で約 300KB、マイコン向けだと 15KB 程度と非常にサイズが小さい
    • 多くのマイコンは ROM サイズが 1MB に満たない
      • fmt package を import すると ROM が厳しいので println のみで頑張るケースもあり
        • 14KB → 8KB ぐらいに減る

15 of 53

TinyGo - ビルド後のバイナリサイズ

16 of 53

TinyGo と Go language feature

  • Concurrency
    • goroutine と channel はほぼ動作
    • 割込みと組み合わせ可能
  • Cgo
    • 完全ではないがサポートは進んできている
    • クロスコンパイルは TinyGo に内包する LLVM で実施するため Cgo のためのコンパイラ追加は不要
  • Reflection
    • まだ不完全だが、活発に作業が行われている
    • シンプルなケースでの encoding/json は使用可能

17 of 53

TinyGo と Go language feature

  • 未対応なもの
    • go:embed
      • パスワード等の保存に使えるので欲しい
    • generics
      • Go 1.18 対応が作業中ではあるが、現状未対応

18 of 53

TinyGo 開発環境の立ち上げ

  • 基本的には Go と TinyGo をインストールするだけ
    • TinyGo は PATH を通すだけで良い
    • 組込開発の環境立ち上げとしては非常にシンプル

Wio Terminal (ATSAMD51 マイコン) は上記のみで環境立ち上げ完了

もちろん、外部ツールが必要となるケースもある

例1) ESP32 環境に対しては xtensa-esp32-elf-ld と esptool のインストールが必要

例2) AVR 環境に対しては gcc-avr と avr-libc と avrdude のインストールが必要

19 of 53

まとめると

  • Go 言語でマイコン遊びができる
    • Goroutine / Channel を用いた並行処理サポート
    • 型有り
    • ライブラリ豊富
      • ただし、 net や encoding/json や reflect など、現時点で動かない package も多い
  • Go 言語で WASM 遊びができる
  • 開発環境立ち上げが簡単

20 of 53

ソースコード - L チカ

マイコン版 Hello World の L チカ (LED をチカチカさせる)は以下。

package main

import (

"machine" // マイコンやボード等の定義を含む TinyGo のパッケージ

"time"

)

func main() {

led := machine.LED // ボード毎に定義されている

led.Configure(machine.PinConfig{Mode: machine.PinOutput}) // led 制御ポートを出力に変える

for {

led.Low()

time.Sleep(time.Millisecond * 500)

led.High()

time.Sleep(time.Millisecond * 500)

}

}

machine.LED 等ボード毎の定義は machine package 内に定義されている。この時、ボードの分岐は buildtag で実施

time package は Go 標準のものが使われている

21 of 53

マイコンへの書き込み

以下のコマンドでマイコンに書き込みできます。マイコンボード (-target で指定) によっては、外付けの書き込みハードが必要です。

$ tinygo flash -target wioterminal ./src/examples/blinky1

22 of 53

ソースコード - Hello World

いわゆる Hello World はこちら。 `-target wioterminal` でビルドした場合は 7696 byte でした。

TinyGo のデフォルト出力先は (多くの場合) シリアルポートとなっています。

package main

import "time"

func main() {

for {

time.Sleep(1 * time.Second)

println("hello world")

}

}

23 of 53

IoT とは

24 of 53

IoT とは

Wikipedia によると以下のような定義

あまり詳しくは定義されていないので、どんな構成でも自分が IoT だと言えば IoT になる(と思っている)

モノのインターネット(物のインターネット、英: Internet of Things、IoT)とは、様々な「モノ(物)」がインターネットに接続され(単に繋がるだけではなく、モノがインターネットのように繋がる)、情報交換することにより相互に制御する仕組みである。

https://ja.wikipedia.org/wiki/モノのインターネット

25 of 53

Arduino 等の環境との違い

26 of 53

組込開発のメイン言語は C/C++

マイコンそれぞれのメーカーオフィシャル開発環境やArduino などは C/C++ の環境であることがほとんど。

  • ライブラリ間の依存解決が辛い (本当に辛い)
    • Dependency hell
  • プリプロセッサ (#ifdef や #include 等) が辛い (本当に辛い)
  • メモリ管理が辛い
  • オーバーロードが辛い (C++)
  • ライブラリのソースは何処にあるの? (とても分かりにくい)
  • 難しい (人による)

27 of 53

組込開発に TinyGo を使用する

C/C++ 環境に比べて以下の特徴があります

  • 言語自体が理解しやすい
  • Package 管理が楽 (go get / go mod / …)
  • LSP / gopls によりソースコードを追いかけやすい
    • どこまで読んでも Go で書かれている (場合が多い)
  • GC 有り
  • goroutine 気持ちいい
    • OS 無しでも気持ちいい
  • Client / Server の両方を (同じ言語で) 簡単に書ける

LSP を動かすには少し作業が必要です。この資料の最後のおまけを参照してください。

28 of 53

ネットワークにつなごう

29 of 53

TinyGo でネットワークにつなぐためのハード構成

2022/04 時点では以下の①、②の構成でネットワーク接続が可能です。日本国内で試しやすくスペック的にも遊びやすいのは Wio Terminal と PyPortal です。

No

メインマイコン

通信用サブマイコン

TinyGo での対応

(何でもよい)

ESP32

対応済み (※1)

(何でもよい)

RTL8720DN

対応済み (Wio Terminal (技適有) がこの構成に該当)

ESP32 など

なし

非対応 (要望は多いが、現状対応困難)

RTL8720DN

なし

マイコン自体が TinyGo で未サポート

※1 : Arduino MKR WiFi 1010 (技適有)、Adafruit PyPortal (技適有)、 Arduino Nano 33 IoT (技適無)、Arduino Nano RP2040 Connect (技適無) などがこの構成に該当 (日本国内では技適無の無線機は違法になる可能性があります)

30 of 53

ネットワークを使う前の初期化

マイコンでネットワークを動かす場合は、ハードウェアの初期化等を実行する必要があります。

例えば Wio Terminal でネットワークを使用するためには以下のようなコードが必要です。

  1. Wio Terminal の CHIP_PU ピンを操作して RTL8720DN をリセットする
  2. UART を初期化し所定のメッセージを送り RTL8720DN の通信部を初期化する
  3. net.UseDriver() により、 net package で RTL8720DN を使用するように設定する
  4. http.SetBuf() により、 HTTP 通信のためのバッファを設定する
  5. SSID / PASSWORD を用いてアクセスポイントに接続する

31 of 53

ネットワークの初期化

初期化コードを毎回書くのは面倒なので、専用の package を用意しました。以下のようなコードで http.Get() などが出来るようになります。

package main

import (

"github.com/sago35/tinygo-examples/wioterminal/initialize"

"tinygo.org/x/drivers/net/http"

)

func run() error {

err := initialize.Wifi(ssid, password)

if err != nil {

// エラー処理を行う

}

for {

resp, err := http.Get(url)

if err != nil {

// エラー処理を行う

}

scanner := bufio.NewScanner(resp.Body)

for scanner.Scan() {

fmt.Printf("%s\r\n", scanner.Text())

}

resp.Body.Close()

time.Sleep(10 * time.Second)

}

}

32 of 53

TinyGo でネットワークにつなぐための package

tinygo.org/x/drivers/net に以下の実装があります。

  • UDP
  • TCP
  • HTTP / HTTPS
  • MQTT
  • NTP (実際には net 以下ではなく examples/rtl8720dn/ntpclient 等にある)

33 of 53

net/http を使う

TinyGo からは現状 Go の net/http package を使用することはできません。代わりに tinygo.org/x/drivers/net/http を使用することができます。基本的な使い方は変わりませんが、実装されていない機能があることに注意が必要です。

JSON を POST する例は以下の通り。

import (

"tinygo.org/x/drivers/net/http"

)

func post(url string, temp, hum float64) error {

body := fmt.Sprintf(`{"Temperature": %.2f, "Humidity": %.2f}`, temp, hum)

_, err := http.Post(url, "application/json", strings.NewReader(body))

return err

}

34 of 53

net/http を使う - HTTPS

HTTPS を使う場合、 root_ca を用意する必要があります。 root_ca を用意しても鍵長が大きい場合等、マイコンスペックの問題、あるいは package 側の問題で正常に通信できないケースもあります。

サンプルコードは以下の通り。

https://github.com/tinygo-org/drivers/blob/v0.19.0/examples/rtl8720dn/tlsclient/main.go

35 of 53

ネットワークにつなごう (HTTP)

36 of 53

HTTP での通信を試す

以下のような構成でソフトを作成します。その際、 Client 側をまずは「①パソコン + Go」で作成し、その後実際に「②マイコン + TinyGo」で動かします。 (C/C++ 環境ではなかなかこうはいかない)

Server

(パソコン + Go)

① Client

(パソコン + Go)

② Client

(マイコン + TinyGo)

37 of 53

HTTP での通信を試す - Server 側

まずは Server 側。こんな感じでアクセスがあれば console に書き出す server を作成します。この辺りが簡単に書けるのが Go の良いところ。

package main

func handler(w http.ResponseWriter, r *http.Request) {

fmt.Fprintf(w, "ok")

body := r.Body

defer body.Close()

b, err := ioutil.ReadAll(body)

if err != nil {

panic(err)

}

log.Printf("%s\n", string(b))

}

アクセスがあったら body を log.Printf() する

38 of 53

HTTP での通信を試す - Client 側 (Go)

まずはシンプルに実装してみます。

実行結果 (Server 側の表示) は以下の通り。

package main

import (

"net/http"

"strings"

)

func main() {

body := `{"message": "hello from Go"}`

http.Post("http://192.168.1.102/", "application/json", strings.NewReader(body))

}

2022/04/23 16:42:16 {"message": "hello from Go"}

39 of 53

HTTP での通信を試す - Client 側 (TinyGo)

次に TinyGo でも動くようにしていきます。 TinyGo で動かすためには、少なくとも import を書き換える (①) 必要があります。また、ネットワークの初期化コードを足す (②) 必要があります。

package main

import (

"net/http"

"strings"

)

func main() {

body := `{"message": "hello world"}`

http.Post("http://192.168.1.102/", "application/json", strings.NewReader(body))

}

① ”tinygo.org/x/drivers/net/http” に書き換える

② ネットワークの初期化コードを追加する

40 of 53

HTTP での通信を試す - buildtag

TinyGo 内での分岐、あるいは TinyGo と Go の分岐を書く場合は buildtag を用いて書くのが定番です。どの buildtag が使えるかは、 `tinygo info wioterminal` などのコマンドで調べることができます。

//go:build wioterminal

// +build wioterminal

package main

// tinygo -target wioterminal 時のみ処理される

//go:build !wioterminal

// +build !wioterminal

package main

// 左記以外の時に処理される

$ tinygo info wioterminal

LLVM triple: thumbv7em-unknown-unknown-eabi

build tags: cortexm baremetal linux arm atsamd51p19a atsamd51p19 atsamd51 sam wioterminal tinygo

math_big_pure_go gc.conservative scheduler.tasks serial.usb

41 of 53

HTTP での通信を試す - Client 側 (TinyGo)

完成版はこちら。

package main

import (

"fmt"

"strings"

"time"

)

var message = "hello world"

func main() {

err := _init()

for err != nil {

fmt.Printf("error : %w\r\n", err)

time.Sleep(10 * time.Second)

}

body := fmt.Sprintf(`{"message": "%s"}`, message)

post("http://192.168.1.102/",

"application/json", strings.NewReader(body))

}

初期化を行う必要がある場合、 _init() 内に実装する。本来は init() に書きたい所だが、 init() 内からはハードウェアを扱うコードに制限があるため _init() を用意した。

TinyGo と Go で共通で使えるように post() の実装を buildtag で分岐した

42 of 53

HTTP での通信を試す - Client 側 (TinyGo)

Wio Terminal 用のコード (TinyGo) はこちら。

//go:build wioterminal

// +build wioterminal

package main

import (

"io"

"github.com/sago35/tinygo-examples/wioterminal/initialize"

"tinygo.org/x/drivers/net/http"

)

func _init() error {

message = "hello from TinyGo"

return initialize.Wifi(ssid, password)

}

func post(url, contentType string, body io.Reader) (resp *http.Response, err error) {

return http.Post(url, contentType, body)

}

ネットワーク周りの初期化

TinyGo 用の net/http を import

43 of 53

HTTP での通信を試す - Client 側 (TinyGo)

パソコン用のコード (Go) はこちら。

//go:build !wioterminal

// +build !wioterminal

package main

import (

"io"

"net/http"

)

func _init() error {

message = "hello from Go"

return nil

}

func post(url, contentType string, body io.Reader) (resp *http.Response, err error) {

return http.Post(url, contentType, body)

}

PC 用の net/http を import

44 of 53

HTTP での通信を試す

以上でパソコン (Go) とマイコン (TinyGo) の両方から動作させることが出来ました。

ソースコードはこちらにあります。

https://github.com/sago35/tinygo-examples/wioterminal/webclient

なぜ両方動くようにして開発するかというと、開発効率が良いから。マイコンの場合は、書き込む毎にネットワーク再接続となるため、 Wio Terminal の場合、起動後 HTTP アクセスが出来るようになるまでに 10 秒程度かかる。

2022/04/23 16:44:38 {"message": "hello from Go"}

2022/04/23 16:46:02 {"message": "hello from TinyGo"}

45 of 53

ネットワークにつなごう (MQTT)

46 of 53

MQTT を使う

MQTT は大まかには以下の特徴があり、マイコンから使いやすい。

  • Pub/Sub型の通信ができる
  • ヘッダーサイズが小さい (最小で 2 byte)

以下の構成の場合、マイコンは MQTT ブローカーにメッセージを publish するのみで良いためシンプル

MQTT

pub

MQTT

sub

PC

47 of 53

MQTT を使う

tinygo.org/x/drivers/net/mqtt を使うことで簡単に通信することができます。この package の内部では github.com/eclipse/paho.mqtt.golang を使っています。

import "tinygo.org/x/drivers/net/mqtt"

func publishing(temp, hum float64) {

body := fmt.Sprintf(`{"Temperature": %.2f, "Humidity": %.2f}`, temp, hum)

data := []byte(body)

token := cl.Publish(topicTx, 0, false, data)

token.Wait()

if token.Error() != nil {

println(token.Error().Error())

}

}

48 of 53

MQTT を使う - 完成品

  • 以下のようなダッシュボードが出来ました
    • Wio Terminal から温度と湿度を取得し表示

49 of 53

MQTT を使う - 手元で試すために

手元で試す場合は、以下のソースコード等を使ってください。

  1. https://github.com/sago35/tinygo-examples/wioterminal/mqtt/main.go
  2. https://gist.github.com/sago35/7af07ec6033a5fb12262694460180f93

50 of 53

まとめ

今日の内容

  • TinyGo とは
  • IoT とは
  • Arduino 等の環境との違い
  • ネットワークにつなごう

TinyGo プロジェクトはまだまだ発展途上です。コントリビュート大歓迎です。コントリビュートに向けて、あるいは TinyGo の分からない点等は、 twitter : @sago35tk に聞いていただければサポート可能です。

51 of 53

Links

52 of 53

おまけ - TinyGo で LSP を動かす

TinyGo + `VSCode or Vim (もしくはそれ以外の LSP 対応エディタ)` で gopls 連携する方法 を参照してください。

基本的には以下の通りです。

53 of 53

おまけ - MQTT をコマンドラインから試す

mosquitto_pub / mosquitto_sub コマンドをインストールすることで、 Wio Terminal が無くても MQTT の送受信が出来ます。

$ mosquitto_pub -h test.mosquitto.org -t sago35/tinygo/tx -m '{"Temperature": 24.45, "Humidity": 43.97}'