IoT With TinyGo
Go Conference 2022 Spring
2022/04/23
Masaaki Takasago
# お前だれよ?
Go の好きなところ
↑ スライドはこちら
はじめに
今日の内容
↑ スライドはこちら
はじめに - マイコン環境の前提
はじめに - つくるもの (HTTP)
2022/04/23 16:32:38 {"message": "hello from Go"}
2022/04/23 16:32:02 {"message": "hello from TinyGo"}
はじめに - つくるもの (MQTT)
はじめに - つくるもの (MQTT) の構成
温度
湿度
I2C
MQTT
pub
MQTT
sub
※MQTT : 軽量プロトコル、マイコンなどの非力なデバイスで良く使われている
PC (Node-RED)
はじめに - おまけ (Node-RED)
MQTT 受信
JSON 整形
流量を制限
Chart で描画
Gauge で描画
なぜ Node-RED ?
IoT で遊ぶ場合、必要となる技術/知識が広範囲になります。マイコン部を作りつつフロントエンド部も作るのはなかなか難しいので、使い始めやすいツールを活用すると良いです。
TinyGo とは
What is TinyGo
TinyGo の動き
Go source code
Go SSA
LLVM IR
Machine code
TinyGo - Project Scope
TinyGo - ビルド後のバイナリサイズ
TinyGo - ビルド後のバイナリサイズ
TinyGo と Go language feature
TinyGo と Go language feature
TinyGo 開発環境の立ち上げ
Wio Terminal (ATSAMD51 マイコン) は上記のみで環境立ち上げ完了
もちろん、外部ツールが必要となるケースもある
例1) ESP32 環境に対しては xtensa-esp32-elf-ld と esptool のインストールが必要
例2) AVR 環境に対しては gcc-avr と avr-libc と avrdude のインストールが必要
まとめると
ソースコード - 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 標準のものが使われている
マイコンへの書き込み
以下のコマンドでマイコンに書き込みできます。マイコンボード (-target で指定) によっては、外付けの書き込みハードが必要です。
$ tinygo flash -target wioterminal ./src/examples/blinky1
ソースコード - Hello World
いわゆる Hello World はこちら。 `-target wioterminal` でビルドした場合は 7696 byte でした。
TinyGo のデフォルト出力先は (多くの場合) シリアルポートとなっています。
package main
import "time"
func main() {
for {
time.Sleep(1 * time.Second)
println("hello world")
}
}
IoT とは
IoT とは
Wikipedia によると以下のような定義
あまり詳しくは定義されていないので、どんな構成でも自分が IoT だと言えば IoT になる(と思っている)
モノのインターネット(物のインターネット、英: Internet of Things、IoT)とは、様々な「モノ(物)」がインターネットに接続され(単に繋がるだけではなく、モノがインターネットのように繋がる)、情報交換することにより相互に制御する仕組みである。
Arduino 等の環境との違い
組込開発のメイン言語は C/C++
マイコンそれぞれのメーカーオフィシャル開発環境やArduino などは C/C++ の環境であることがほとんど。
組込開発に TinyGo を使用する
C/C++ 環境に比べて以下の特徴があります
LSP を動かすには少し作業が必要です。この資料の最後のおまけを参照してください。
ネットワークにつなごう
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 (技適無) などがこの構成に該当 (日本国内では技適無の無線機は違法になる可能性があります)
ネットワークを使う前の初期化
マイコンでネットワークを動かす場合は、ハードウェアの初期化等を実行する必要があります。
例えば Wio Terminal でネットワークを使用するためには以下のようなコードが必要です。
ネットワークの初期化
初期化コードを毎回書くのは面倒なので、専用の 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)
}
}
TinyGo でネットワークにつなぐための package
tinygo.org/x/drivers/net に以下の実装があります。
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
}
net/http を使う - HTTPS
HTTPS を使う場合、 root_ca を用意する必要があります。 root_ca を用意しても鍵長が大きい場合等、マイコンスペックの問題、あるいは package 側の問題で正常に通信できないケースもあります。
サンプルコードは以下の通り。
https://github.com/tinygo-org/drivers/blob/v0.19.0/examples/rtl8720dn/tlsclient/main.go
ネットワークにつなごう (HTTP)
HTTP での通信を試す
以下のような構成でソフトを作成します。その際、 Client 側をまずは「①パソコン + Go」で作成し、その後実際に「②マイコン + TinyGo」で動かします。 (C/C++ 環境ではなかなかこうはいかない)
Server
(パソコン + Go)
① Client
(パソコン + Go)
② Client
(マイコン + TinyGo)
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() する
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"}
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” に書き換える
② ネットワークの初期化コードを追加する
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
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 で分岐した
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
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
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"}
ネットワークにつなごう (MQTT)
MQTT を使う
MQTT は大まかには以下の特徴があり、マイコンから使いやすい。
以下の構成の場合、マイコンは MQTT ブローカーにメッセージを publish するのみで良いためシンプル
MQTT
pub
MQTT
sub
PC
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())
}
}
MQTT を使う - 完成品
MQTT を使う - 手元で試すために
まとめ
今日の内容
TinyGo プロジェクトはまだまだ発展途上です。コントリビュート大歓迎です。コントリビュートに向けて、あるいは TinyGo の分からない点等は、 twitter : @sago35tk に聞いていただければサポート可能です。
Links
おまけ - TinyGo で LSP を動かす
TinyGo + `VSCode or Vim (もしくはそれ以外の LSP 対応エディタ)` で gopls 連携する方法 を参照してください。
基本的には以下の通りです。
おまけ - 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}'