16
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SORACOM Orbitでボタンのデータを触ってみた(AssemblyScript編)

Last updated at Posted at 2020-12-20

この記事は

SORACOM Advent Calendar 2020の21日目の記事で、SORACOM OrbitLTE-M Buttonのデータを触ってみて、実装の際にAssemblyScriptを使ってみたという内容になります。

Orbitを使ってみたという記事はQiitaにもいくつかあるんですがいずれもC++で、AssemblyScriptでやったという記事がほとんど見当たらず、実際にやってみたらちょっと躓いたところもあったのでその辺りを書こうと思います。

なお、対象となるボタンはLTE-M Button for Enterprise(しろボタン)またはLTE-M Button plus(ひげボタン)となります。

SORACOM Orbitとは

SORACOM Orbitは「インラインプロセッサ」と呼ばれるサービスです。デバイスからSORACOMプラットフォームに渡されたデータが、Beam/Funk/Funnel/Harvestなどの各種SORACOMサービスに渡る前にデータを加工することができるサービスで、その加工する処理を自作することができます。とても賢いバイナリーパーサを自作できる、と言うとSORACOM好きの人には伝わるでしょうか?

処理するプログラム(soraletと呼ばれます)はユーザがWASMで作成し、それがSORACOMプラットフォーム上で動くという、FaaSっぽいかんじなのが個人的には痺れます。

SORACOM Orbitの開発環境を整える

公式ドキュメントに従って進めます。環境の構築自体は特にハマるところはないかと思います。

構築できたらdev-Container内のassemblyscript/assembly/index.tsを参考に修正していきます。

入力データをJSONにする

(2021/5/31追記)
2021年5月のバージョンアップで、この入力データ用のクラス作成作業は不要になりました。2021年5月以降のバージョンのSDKであれば、

let data: JSON.Obj = <JSON.Obj>(JSON.parse(getInputBufferAsString()));

で取得できるようになっています。

2021年5月以前のSDKで準備されてるJSONDecoderで入力データ(デバイスからSORACOMプラットフォームに渡されたデータ)をJSONに変換して扱うには、まず受信するデータに合わせたクラスを作成します。

今回はボタンが対象となりますので、ボタンで送信されるデータに合わせて以下のようなクラスを定義します。index.tsの末尾に記載しましょう。
JSONHandlerクラスを継承して、メンバーで利用する型に合わせてメソッドsetXXを実装します。各メソッドの中では引数で渡されたメンバー名に従って、値を設定します。

TypeScript
class ButtonData extends JSONHandler {
  public clickType: i64;
  public clickTypeName: string;
  public binaryParserEnabled: boolean;
  public batteryLevel: f64;

  setInteger(name: string, value: i64): void{
    if(name == "clickType") {
      this.clickType = value;
    } else if(name == "batteryLevel") {
      this.batteryLevel = value as f64;
    }
  }

  setString(name: string, value: string): void {
    if (name == "clickTypeName") {
      this.clickTypeName = value;
    }
  }

  setBoolean(name: string, value: boolean): void{
    if (name == "binaryParserEnabled"){
      this.binaryParserEnabled = value;
    }
  }

  setFloat(name: string, value: f64): void {
    if (name == "batteryLevel") {
      this.batteryLevel = value;
    }
  }
}

そして、実際に入力データ(JSONをシリアライズした文字列)をこのクラスのインスタンスにデシリアライズするのは、サンプルのコードの通り以下のようになります。

Typescript
  const data = new ButtonData();
  const decoder = new JSONDecoder<ButtonData>(data);

  decoder.deserialize(getInputBuffer());

ここで私が最初うまく動かなかったのがbatteryLevelの取り扱いです。batteryLevelの値は0.25 / 0.5 / 0.75 / 1となるのでメンバーをfloat(f64)で定義し、値をセットするハンドラーとしてsetFloat()だけを準備していたですが、正常に値が設定されませんでした。

これは、入力で「1」という値が渡ってくるとJSONDecoderがこれを「integer」だと判断してsetInteger("batteryLevel",1)という形でハンドラーを呼び出すためです。ちなみに該当のコードはassemblyscript/assembly/lib/decoder.tsの254行目辺りです。

Typescript
    if (digits > 0) {
      if (numStr.indexOf(".") > 0) {
        this.handler.setFloat(this.state.lastKey, parseFloat(numStr) * (sign as f64));
      } else {
        this.handler.setInteger(this.state.lastKey, (parseInt(numStr) as i64) * sign);
      }
      return true;
    }

JSONDecoder.deserialize()は文字列で入ってきた入力値をパースしていって数値があった場合、小数点があればsetFloat()ハンドラーを、なければsetInteger()ハンドラーを呼んでいます。

このため、ハンドラーとして呼び出されるsetInteger()メソッドとsetFloat()メソッドの両方にbatteryLevelの値を設定するコードを準備し、setInteger()ではcastしてデータを設定するようにする必要があります。

入力データを出力側にコピーする

まずは入力がそのまま出力に渡るように、先ほど作成したButtonData型に入力をJSONDecoderで取り込み、出力データのJSONEncoder型にコピーしてやります。

Typescript
  const encoder = new JSONEncoder();

  encoder.setInteger("clickType", data.clickType);
  encoder.setString("clickTypeName", data.clickTypeName);
  encoder.setFloat("batteryLevel", data.batteryLevel);
  encoder.setBoolean("binaryParserEnabled", data.binaryParserEnabled);

入力データを追加する

そして、いよいよデータをいじってみましょう。とりあえず今回はサンプルにもあるコードをそのまま使い、データに簡易位置情報を追加してみましょう。
簡易位置情報はそのままでも例えばBeamであればHTTPヘッダなどで受け取れますが、この処理をすることでリクエストボディに埋めることが出来ます。

Typescript
  const location = getLocation();
  encoder.setFloat("lat", location.lat);
  encoder.setFloat("lon", location.lon);

soraletをアップロードする

最終的なコードは以下のようになります。これをビルドして、wasmファイルをsoraletとして登録します。

Typescript
import {
  getInputBuffer,
  setOutputJSON,
  log,
  getLocation,
  getTagValue,
  getSourceValue,
  getTimestamp,
  getUserdata,
} from "orbit-sdk-assemblyscript";
import { JSONDecoder, JSONHandler } from "./lib/decoder";
import { JSONEncoder } from "./lib/encoder";

export function uplink(): i32 {
  const data = new ButtonData();
  const decoder = new JSONDecoder<ButtonData>(data);

  decoder.deserialize(getInputBuffer());

  const encoder = new JSONEncoder();

  encoder.setInteger("clickType", data.clickType);
  encoder.setString("clickTypeName", data.clickTypeName);
  encoder.setFloat("batteryLevel", data.batteryLevel);
  encoder.setBoolean("binaryParserEnabled", data.binaryParserEnabled);

  const location = getLocation();
  encoder.setFloat("lat", location.lat);
  encoder.setFloat("lon", location.lon);

  setOutputJSON(encoder.toString());

  return 0;
}

class ButtonData extends JSONHandler {
  public clickType: i64;
  public clickTypeName: string;
  public binaryParserEnabled: boolean;
  public batteryLevel: f64;

  setInteger(name: string, value: i64): void{
    if(name == "clickType") {
      this.clickType = value;
    } else if(name == "batteryLevel") {
      this.batteryLevel = value as f64;
    }
  }

  setString(name: string, value: string): void {
    if (name == "clickTypeName") {
      this.clickTypeName = value;
    }
  }

  setBoolean(name: string, value: boolean): void{
    if (name == "binaryParserEnabled"){
      this.binaryParserEnabled = value;
    }
  }

  setFloat(name: string, value: f64): void {
    if (name == "batteryLevel") {
      this.batteryLevel = value;
    }
  }
}

ボタンの所属するSIMグループでOrbitの設定をします。Airで簡易位置情報をONにし、Orbitでも簡易位置情報をONにしておきます。

実行してみる

それでは実行してみましょう。データがどういう形になったかを確認するために、HarvestDataをONにして、ボタンをポチッとします。

HarvestDataで確認すると、データはこんな感じになっています。

JSON
{
  "clickType": 1,
  "clickTypeName": "SINGLE",
  "batteryLevel": 1.0,
  "binaryParserEnabled": true,
  "lat": 3?.************,
  "lon": 13?.***************
}

OrbitをONにする前のデータと比較してみましょう。

JSON
{
  "clickType": 1,
  "clickTypeName": "SINGLE",
  "batteryLevel": 1,
  "binaryParserEnabled": true
}

Orbitによって簡易位置情報が追加されているのと、batteryLevelの値がFloatで取り扱っているので1.0になっているのが分かります。

まとめ

Orbitでassemblyscriptを使う場合、入力データの型を意識してちゃんとクラスを準備しないといけないよ、というお話でした。
実際の手順を簡単にまとめると

  • 入力に応じたメンバーを持ったクラスを、JSONHandlerクラスを継承して作成する
  • 各メンバーの型ごとのsetXXメソッドをオーバーライドする
  • Float(f64)型のメンバーは、setFloatだけでなくsetIntegerメソッドにもsetterを準備する

ということになります。今回はボタンでしたが、GPSマルチユニットや他のデバイスでも同様です。

これからOrbitを触ってみたい!という皆さんの参考になれば幸いです。

16
3
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
16
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?