SORACOM Orbit試してみた4回目です。
SORACOM UG Online #1でOrbitで利用可能な言語の4つ目として登場したTinyGO
もともとGoでバッチ作ったり、CLIっぽいの作ったりしてたので、これはやらんとなー。と思いつつ、
年を越えてしまいました。(反省)
ソース
ベースにしたのは、
ソラコムさんのgithubにあるLTE-Mボタン用のサンプルになります。
https://github.com/soracom/orbit-sdk-tinygo/tree/master/examples/lte-m-button
コード自体は、
WASM モジュール開発環境のセットアップ
でダウンロードする開発環境構築用のzipファイルに含まれるTinyGo用のディレクトリ内の
src/main.go
に組み込む形にしています。
package main
import (
"strings"
"github.com/moznion/go-json-ice/serializer"
"github.com/moznion/jsonparser"
sdk "github.com/soracom/orbit-sdk-tinygo"
)
type Output struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Bat int64 `json:"bat"`
Rs int64 `json:"rs"`
Temp float64 `json:"temp"`
Humi float64 `json:humi"`
X float64 `json:"x"`
Y float64 `json:"y"`
Z float64 `json:"z"`
Type int64 `json:"type"`
Name string
}
type GpsMultiUnitErrorCode sdk.ErrorCode
const (
OkErrorCode GpsMultiUnitErrorCode = 0
ExecErrorCode = -1
)
// application entry point, but the orbit runtime never executes this.
func main() {
}
//export uplink
func uplink() sdk.ErrorCode {
inputBuffer, err := sdk.GetInputBuffer()
if err != nil {
sdk.Log(err.Error())
return -1
}
sdk.Log("Input Buffer: " + string(inputBuffer) + "\n")
output, err := convertInputToOutput(inputBuffer)
if err != nil {
sdk.Log(err.Error())
return sdk.ErrorCode(ExecErrorCode)
}
tagValueName, err := sdk.GetTagValue("name")
if err != nil {
sdk.Log(err.Error())
return -1
}
sdk.Log("Name: " + string(tagValueName) + "\n")
output.Name = string(tagValueName)
serializedOutput, err := MarshalOutputAsJSON(output)
if err != nil {
sdk.Log(err.Error())
return sdk.ErrorCode(ExecErrorCode)
}
sdk.SetOutputJSON(string(serializedOutput))
return sdk.ErrorCode(0)
}
func convertInputToOutput(input []byte) (*Output, error) {
lat, err := GetFloatForLocation(input, "lat")
if err != nil {
return nil, err
}
lon, err := GetFloatForLocation(input, "lon")
if err != nil {
return nil, err
}
bat, err := jsonparser.GetInt(input, "bat")
if err != nil {
return nil, err
}
rs, err := jsonparser.GetInt(input, "rs")
if err != nil {
return nil, err
}
temp, err := jsonparser.GetFloat(input, "temp")
if err != nil {
return nil, err
}
humi, err := jsonparser.GetFloat(input, "humi")
if err != nil {
return nil, err
}
x, err := jsonparser.GetFloat(input, "x")
if err != nil {
if strings.HasPrefix(err.Error(), "Value is not a number") {
x = 0
} else {
return nil, err
}
}
y, err := jsonparser.GetFloat(input, "y")
if err != nil {
return nil, err
}
z, err := jsonparser.GetFloat(input, "z")
if err != nil {
return nil, err
}
sendType, err := jsonparser.GetInt(input, "type")
if err != nil {
return nil, err
}
return &Output{
Lat: lat,
Lon: lon,
Bat: int64(bat),
Rs: int64(rs),
Temp: temp,
Humi: humi,
X: x,
Y: y,
Z: z,
Type: int64(sendType),
}, nil
}
func MarshalOutputAsJSON(s *Output) ([]byte, error) {
buff := make([]byte, 1, 210)
buff[0] = '{'
if float64(s.Lat) != 0 {
buff = append(buff, "\"lat\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Lat))
buff = append(buff, ',')
}
if float64(s.Lon) != 0 {
buff = append(buff, "\"lon\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Lon))
buff = append(buff, ',')
}
buff = append(buff, "\"bat\":"...)
buff = serializer.AppendSerializedInt(buff, int64(s.Bat))
buff = append(buff, ',')
buff = append(buff, "\"rs\":"...)
buff = serializer.AppendSerializedInt(buff, int64(s.Rs))
buff = append(buff, ',')
buff = append(buff, "\"temp\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Temp))
buff = append(buff, ',')
buff = append(buff, "\"humi\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Humi))
buff = append(buff, ',')
buff = append(buff, "\"x\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.X))
buff = append(buff, ',')
buff = append(buff, "\"y\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Y))
buff = append(buff, ',')
buff = append(buff, "\"z\":"...)
buff = serializer.AppendSerializedFloat(buff, float64(s.Z))
buff = append(buff, ',')
buff = append(buff, "\"type\":"...)
buff = serializer.AppendSerializedInt(buff, int64(s.Type))
buff = append(buff, ',')
buff = append(buff, "\"name\":"...)
buff = serializer.AppendSerializedString(buff, s.Name)
buff = append(buff, ',')
if buff[len(buff)-1] == ',' {
buff[len(buff)-1] = '}'
} else {
buff = append(buff, '}')
}
return buff, nil
}
// based on https://github.com/moznion/jsonparser/blob/master/parser.go#GetFloat
// GetFloat returns the value retrieved by `Get`, cast to a float64 if possible.
// The offset is the same as in `Get`.
// If key data type do not match, it will return an error.
func GetFloatForLocation(data []byte, keys ...string) (val float64, err error) {
v, t, _, e := jsonparser.Get(data, keys...)
if e != nil {
return 0, e
}
if t != jsonparser.Number {
return 0, nil
}
return jsonparser.ParseFloat(v)
}
実装してみて
GPSマルチユニットのGPS座標(latとlon)
困ったのが、GPSマルチユニットのGPS座標(latとlon)。
室内配置しているのですが、GPS座標がまあ取れない。
無効にすればいいんですが、そのままにしています。
(外持ち出したい時にONにするの忘れるからという理由)
ところが、それだと、エラーになる。
調べてみたところ、
JSONをパースする jsonparser
のGetFloatで、数値型じゃなかった場合は、エラーになっていた。
さて、どうしたものかと思って、
そのメソッドをベースにして、数値型じゃない場合は、0を返すメソッドを作りました(GetFloatForLocation)。
※最初はエラーメッセージ拾ってたけど、イケてなかったので、変更
プルリク送った方がいいかなーと
ちょっと思わなくもないので、ちょっと検討。
余談ですが、Funk経由で、Lambda側で、GoogleMapへのリンクを生成していますが、
このままだと、経度0度、緯度0度になるので、ヌル島という架空の島にいることになります。
ヌル島
それでもいいかなと思ったんですが、
outputする時に、
Lat、Lonは0の場合は、セットしないようにしています。
fmtパッケージが使えない
開発者ガイドにも記載がありますが、
「SORACOM Orbit の実行環境は TinyGo の JS ランタイム (syscall/js.*) に依存する WASM モジュールを実行できません。たとえば fmt パッケージを使用していると Soralet へアップロードする際にunknown import function: env.syscall/js.valueGet (SLM1011)
のようなエラーとなります。」
とあるように、fmtパッケージが使えないので、ロジック内でエラーを生成(fmt.Error)できないんですよね。
GetFloatForLocation
でエラー生成して返したかったんですが、諦めました。
もしかしたら、うまい手があるのかもしれませんが・・・。
次回
これは途中の段階なので、
これをさらに改良します。