LoginSignup
2
0

More than 3 years have passed since last update.

SORACOM Orbit試してみた 〜TinyGOでWASM作ってみた〜

Posted at

SORACOM Orbit試してみた4回目です。


SORACOM UG Online #1でOrbitで利用可能な言語の4つ目として登場したTinyGO
thumbnail

もともと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 でエラー生成して返したかったんですが、諦めました。
もしかしたら、うまい手があるのかもしれませんが・・・。

次回

これは途中の段階なので、
これをさらに改良します。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0