MODE JAPAN Blog

IoTを活用したデジタルトランスフォーメーションのための情報、開発の現場から技術的な情報や取り組みについてお届けします。

新型コロナ後、IoTコンタクトトレーシングでビジネス停止のリスクを最小化する

MODE CEOの上田ガクです。

新型コロナウィルスのパンデミックは、MODEのあるサンフランシスコ周辺、東京とも一時期のピークが過ぎ、ビジネスは徐々に再開する方向に進んできています。日本では感染拡大が比較的抑えられたこともあり、外出自粛の期間はカリフォルニアより後に始まり、先に終わる形となりました。

どちらの地域でもビジネス再開に向けて動きは出てきていますが、ここで1つ思い出さないといけないことがあります。それはこれからしばらくの間は今までのように生活を元に戻すことはできないということです。外出自粛によって、社会での人と人の接触を大幅に減らすことで感染拡大を抑えることができたものの、ウィルスに対して根本解決ができているわけではないので、元に戻せば感染拡大が再燃してしまいます。

ビジネス再開に向けて

つまり、これからしばらくの間、1年なのか2年なのかはわかりませんが、ウィルスと共存しながらビジネスを行っていく必要があり、これは頭の痛い問題です。MODEの業務の大部分はクラウドソフトウェア開発で、もともとサンフランシスコと東京の2拠点間で頻繁にリモートにやりとりをしていたこともあり、在宅で業務を行うWFH(Work from home)でもあまり影響はなく、急いでオフィスをオープンする必要はありませんが、再開に向けての準備を慎重に進めています。御存知の通りアメリカは訴訟社会ですので、対策を怠ったままオフィスで感染が発生してしまうと訴訟リスクすらあります。

どんな業種でも、ビジネスを再開したあと、自社の従業員から感染者がでる可能性はゼロではありません。実際、物流配送センター、食肉工場などでクラスターが発生し、施設を丸ごと閉鎖しなければならない状況も出てきています。

サーモグラフィーで入り口で体温計測を行うという対策がよく取られていますが、無症状感染者がいることを考えれば、検知するだけではなく、感染者発生したあと、いかに影響範囲を最小限に留めるかが鍵です。

コンタクトトレーシングでビジネスリスクを最小化

ビジネス停止のリスクを回避するためにはどのような対策ができるでしょうか。

従業員をグループに分け、グループ間での接触をなくすことで感染者が出ても、一部のグループのビジネスを停止するたけで、残りの業務は継続することができることができます。

感染者発覚後に過去に遡って従業員同士の接触を洗い出し(コンタクトトレーシング)、濃厚接触者の洗い出しができれば、他のグループの従業員が接触していないことを担保でき、業務継続の確実性が上がります。

f:id:gakuueda:20200602081426p:plain

従業員バッジなどの形をした無線ビーコンと、作業場所、休憩所などに設置されたステーションといったいわゆるIoT技術を組み合わせることで、接触の有無を常時記録が可能になります。本来接触があるべきではないグループ間の接触があれば、時間・場所などを分けたり、従業員への促しを行うことで安全に業務が遂行することができます。

職場の安全を技術で守る、というのもIoT技術の一つの応用例です。

Sensor Cloud における TypeScript の型付け

MODE では収集したセンサーデータを可視化するためのクラウドサービスとして Sensor Cloud を提供しているのですが、サービスが生まれてから数年が経過し技術的に解消したい部分が目立つようになったため、実装のフルリプレイスを進めています*1

フロントエンドはもともと JavaScript による実装でしたが、コンパイル時に極力問題を検知できるようにすることと、以前に発表した TypeScript による Sensor Cloud ユーザー向け開発キット*2の成果を鑑み、このリプレイスでも引き続き TypeScript を採用しました。 一方で Sensor Cloud のフロントエンドは主に MODE Platform API との HTTP による通信をすることでその機能を実現しています。 本稿ではこの API コールに関する TypeScript の型という観点から、Sensor Cloud の実装について触れていきたいと思います。

MODE の Key-Value ストアと TypeScript

さてこの TypeScript の型付けにあたって少し工夫が必要となったのは、不定形なデータを扱う タイプの API コールの実装を DRY にすることです。any の利用は甘えなのでできる限りきっちり型を付けていきたいところですが、MODE Platform には任意のデータを格納できる Key-Value ストアがあります。この Key-Value ストアはSensor Cloud の一部機能における永続化データバックエンド*3として活用される他、ゲートウェイのデータ伝搬にも利用されています。

blog.tinkermode.jp

ところがここに格納される Value は前述の通り任意であるため、TypeScript において型を定義するときに少しだけ手間が発生するというわけです。

Key-Value に型を付けてみる

まずは理解のために、Key-Value ストアとの HTTP によるリクエスト・レスポンス例を見てみましょう。

;; Request
GET /homes/{homeId}/kv

;; Response payload
[
  {key”: “foo”,
    “value”:  {name”: “hoge},
    “modificationTime”: "2020-01-01T00:00:00.00Z"
  },
  {key”: “bar”,
    “value”:  {counts”: [1, 1, 2, 3, 5],
      “interval”: 42
    },
    “modificationTime”: "2020-01-02T00:00:00.00Z",
  }
]

このように value 属性の中身が一定ではありません。任意の値の挿入が可能なためです。ではこのレスポンスのペイロードにまずはラフに型をつけてみます。

type Kv = { key: string; value: any; modificationTime: string }[];

value の型としてはひとまず any を置いています。ここで Sensor Cloud に話を限定すると、この value に投入される値には Sensor Cloud が規定している JSON 構造のいずれかが来ることになり、完全に未知のデータがやってくるわけではありません。センサーのハードウェア情報であったり、ユーザーが作成したダッシュボードの状態などに対して、それぞれお決まりの構造が定義されているわけです。したがって、value の型は型パラメーターで表現することが可能となります。

type Kv<T> = { key: string; value: T; modificationTime: string; }[];

// 汎用性を持たせるため少しリファクタする
// Array の表現はここに含めない
type Kv<T> = { key: string; value: T; modificationTime: string };

// 先程の HTTP レスポンス例を当てはめてみる
type Foo = Kv<{ name: string }>;
type Bar = Kv<{ count: number[]; interval: number }>;
// この場合レスポンスのペイロードは以下のように型付けできる
type KvPayload = (Foo | Bar)[];

これにより少々ヘテロジニアスな Array をジェネリックに表すことができました。

型付けされた Key-Value にアクセスする

型が付いたところで実際にアクセスしてみましょう。

// api.getKeyValues() で前述のKey-Value が取得できると仮定する
// なおこの変数 kv の型アノテーションは記事の分かりやすさのために書いている
const kv: KvPayload = await api.getKeyValues();

// しかしこれはコンパイルエラーになる
// なお kv[0] が undefined のケースもあるため . ではなく ?. でプロパティアクセスしている
const name = kv[0]?.value?.name;

先ほどのレスポンス例では最初の要素には name 属性が含まれていたにも関わらず、いざその name 属性にアクセスしようとするとコンパイルエラーとなります。これは kv[0] が Union 型 Foo | Bar のどちらにもなり得るため、もし Bar だったら name 属性が存在しないじゃないかとコンパイラが訴えかけてくるわけです。TypeScript を使ったことのある方であればこのあたりはすぐにピンとくる場所でしょう。

結論から言うとここではユーザー定義の Type Guard を使います。プリミティブであれば typeof が、クラスを new によりインスタンス化 (正しくは関数のコンストラクタ呼び出し*4)したのであれば instanceof が利用できます。しかし今回はそのどちらのケースにも当てはまらないため、ユーザーが自ら型を判定する関数として Tyep Guard を記述する必要があります。今回のケースに対して、最小の実装をしてみましょう。

// 数少ない any の使用箇所、それが Type Guard
const isFoo = (arg: any): arg is Foo => {
  return arg && (arg as Foo).key === "foo";
};

const isBar = (arg: any): arg is Bar => {
  return arg && (arg as Bar)?.key === "bar";
};

これにより先ほどのコンパイルエラーに対処をすることができます。

const kv: KvPayload = await api.getKeyValues();
const foo = kv[0];
if (isFoo(foo)) {
  // 変数 foo の 型が Foo に確定するのでコンパイルエラーにならず name にアクセスできる
  const name = foo.value.name;
}

Type Guard と Array の高階関数

なお、覚えておくと便利なのは Type Guard を Array.prototype.filter や Array.prototype.find と併用した場合にもきちんと型が推論されるということです。

const kv: KvPayload = await api.getKeyValues();
const foos = kv.filter(isFoo);
const bar = kv.find(isBar);

// コンパイルエラーにならない
console.log(foos[0]?.value?.name);
console.log(bar?.value?.interval);

おかげで高階関数によるアプローチもしやすいですね。

Sensor Cloud の Key-Value への応用

さて、TypeScript に詳しい方であればここまで読んで Key-Value の値に対して Type Guard は必ずしも必要ないのではないか? とお考えになったかも知れません。上記の例であれば key に対して Enum であったり文字列リテラルの Union 型を使えば表現できそうですよね。

// 文字列リテラルの Union による key の表現
// Enum でも代替可能
type Key = “foo” | “bar”;
type Kv<K extends Key, V> = { key: K; value: V; modificationTime: string };

type Foo = Kv<”foo”, { name: string }>;
type Bar = Kv<”bar”, { count: number[]; interval: number }>;
type KvPayload = (Foo | Bar)[];

const kv: KvPayload = await api.getKeyValues();
const foo = kv[0];
// Type Guard を使わなくてもよい
if (foo?.key === “foo”) {
  // 実はこれでもコンパイルが通る
  console.log(foo.value.name);
}

しかし Sensor Cloud においてはこのアプローチでは実現できないことがあります。まずは Sensor Cloud の Key-Value 例を(説明の都合上簡略化して)お見せすると以下のようなものです。

[
  {
    "key": "config",
    "value": {"TEMPERATURE":"C"},
    "modificationTime": "2019-06-14T13:38:54.542Z"
  },
  {
    "key": "sensorModule0107:ab1234cd5678",
    "value": {
      "gatewayId": "81321",
      "name": "Living room",
      "sensors": ["TEMPERATURE:0"]
    },
    "modificationTime": "2020-05-18T10:24:08.107Z"
  },
  {
    "key": "sensorModule0123:F0000001",
    "value": {
      "gatewayId": "81321",
      "name": "Dining room",
      "sensors": ["COUNT:0"]
    },
    "modificationTime":"2019-10-11T10:55:50.882Z"
  },
  {
    "key": "dashboard",
    "value": [
      {
        "duration":"1d",
        "panelType":"GRAPH",
        "sensorModule":"0107:ab1234cd5678",
        "sensorId":"TEMPERATURE:0",
        "w":3,
        "h":1,
        "x":0,
        "y":0
      }
    ],
    "modificationTime": "2020-05-15T18:16:15.896Z"
  }
]

注目すべきは key です。config、dashboard といった固定値に挟まれて存在しているのは sensorModule.+ というフォーマットのプレフィックスのみ固定されている key です。config や dashboard のように完全一致する key であれば先ほどの文字列リテラル Union 型を用いた型の判定ができますが、このようなプレフィックスを利用したものが混ざるとそうはいきません。そこで Sensor Cloud のリプレイスプロジェクトにおいては以下のような Type Guard を書きました。

type SensorModule = Kv<{ gatewayId: string; name: string; sensors: string[] }>;
type Config = Kv<{}>;
type Dashboard = Kv<{}>;
type KvPayload = (SensorModule | Config | Dashboard)[];

const isSensorModule = (arg: any): arg is SensorModule => {
  return (
    arg &&
    typeof arg.key === "string" &&
    arg.key.startsWith("sensorModule")
  );
};

これならばプレフィックスのみ固定されている key であっても、Type Guard による型の判定が可能となります。

const kv = await api.getKeyValues();
const sensorModules = kv.filter(isSensorModule);
console.log(sensorModules[0]?.value?.gatewayId);

API コール時におけるエラーの表現

API コールは JavaScript ランタイムの外側に作用するものですので、ランタイム内で完結する他の処理と比べてエラーハンドリングについては少々シビアに考えておいた方がよいでしょう。こうした場合、TypeScript においてはどのような手段でアプローチをすべきでしょうか。

throw、try、catch の課題点

API コールに限った話ではないのですが、TypeScript においてどのようにエラーを表現するかというのは悩ましい問題です。伝統的かつ async/await でも利用可能な手段として throw、try、catch がありますが、これらは TypeScript のコンパイラによるエラー検知にはあまり寄与してくれません。

// 通常の try catch
try {
  const kv = await api.getKeyValues();
  console.log(kv[0]?.key);
} catch (e) {
  // カスタムエラーの場合は e のキャストが必要になる
  // しかし api.getKeyValues のシグネチャにはそのカスタムエラーの情報が含まれない
}

// try catch は必須ではないため try の外側でもコンパイルは通る
const kv2 = await api.getKeyValues();
console.log(kv2[0]?.key);

コード中のコメントをご覧ください。まず 1 つめ目の課題として、MODE Platform API ではエラーレスポンスの構造が規定されているためその構造を使ったカスタムエラーを定義したくなりますが、その場合は catch 節中でエラー変数 e をそのそのカスタムエラーにキャストする必要があります。ところがそのカスタムエラーの情報は api.getKeyValues の関数シグネイチャには含まれないため、プログラマは頭の中でカスタムエラーについて把握をしていなければなりません。

続いて 2 つ目の課題として、アクセスした Key-Value ペアが偶然消されていて 404 が返った場合などを考えてエラーハンドリングを強制したい場合も出てくる可能性がありますが throw されたものを try-catch するかどうかは任意であるため強制はできません。

以上 2 つの課題を考えると throw、try、catch は若干の表現力不足を感じてしまいます(これは当然ながら全てにおいて使うべきではないという主張ではありません。これらで十分な表現力があれば導入しても問題はないと考えます)。

Union 型による API エラーの表現

関数型に慣れている人は Either を使いたくなってくる頃かも知れませんが、TypeScript には今のところ代数的データ型もパターンマッチもなく、またライブラリや自前実装で Either を導入したとしても通常の TypeScript とかなり世界観が変わってしまうためチームでの継続的な製品開発に導入するのは躊躇してしまいます。というわけでそのどちらでもなく Union 型で API コールエラーを表現することとしました。これならば TypeScript が元々備えている仕組みの上に乗りつつ、前述の 2 つの課題をクリアすることができます。

// エラーをクラスで定義する例
class ApiError {
  public getReason() {}
}

class Api {
  // インタフェース定義
  public async getKeyValues(): Promise<KvPayload | ApiError> {}
}

const kv = await api.getKeyValues();
if (kv instanceof ApiError) {
 // さらにここでエラーを表すオブジェクトが getReason を持つことは保証済み 
  console.error(kv.getReason());
} else {
  // この instanceof による判定を入れないと kv.find でエラーになる
  console sensorModule = kv.find(isSensorModule);
  console.info(sensorModule?.gatewayId);
}

KvPayload の型定義は (Sensor | Config | Dashboard)[] のように Array でしたので一見 Array.prototype.find は呼び出すことができるように見えますが、変数 kv は KvPayload | ApiError という Union 型を持つため、ApiError である可能性があるうちは kv.find はエラーとなります。ApiError は Array ではないためです。また if 節の中、kv が ApiError クラスのインスタンスであることが保証される世界では、ApiError クラスのメソッド(今回の例では getReason)をキャストなしに呼び出すことが可能となります。

Union 型によるエラーチェックをすり抜けるケース

先ほどの例でエラーハンドリングを忘れた場合には、API から取得した(レスポンスをディシリアライズした)オブジェクトのプロパティアクセスにおいてコンパイルエラーが起きました。見方を変えると、こうしたプロパティアクセスが発生しないのであれば、エラーハンドリングを忘れたとしてもコンパイルエラーが起きることはありません。具体的には POST、PATCH、DELETE といった write 系の HTTP メソッドでリクエストをするような場合ですね。

const result = await api.setKeyValues({
  key: “sensorModule123”,
  value: { gatewayId: “456}
});

// result のプロパティにアクセスする必要がないことも多いため
// エラーハンドリングなしで次の処理に進んでもコンパイラは何も検知しない
console.log(“go ahead!”);

また、上記 ApiError と Array が同形のメソッド呼び出しできる場合はエラーチェックは必須となりません。以下は Array.prototype.some と ApiError のメソッドが合致している場合です。

class ApiError {
  // Array.prototype.some と合致している (引数の型は簡略化している)
  public find(f: Function): boolean {}
}

const kv = await api.getKeyValues();
// この場合もエラーチェックを免れて呼び出せすことは可能
kv.some(x => x);

これはダックタイピング的な思想においては良しとされると筆者は認識していていますし良し悪しは一概には論じ難いですが、上記 2 例をもって「Union 型によるエラー表現はエラーチェックを100%強要することはできない」という点を実装者は頭の片隅に置いておくべきだと思います。

最後に

本稿では以下の 2 点において Sensor Cloud における TypeScript の型付けに触れました。

  1. Key-Value ストアに対する型付け
  2. Union 型による API コールエラーの表現

TypeScript の型システムは表現力が高いため、ひょっとするとここで紹介したものよりも良い実装があるかも知れません。今後新たな学びがあれば Sensor Cloud の実装も改善できればと思います。そして洗練できたらゆくゆくは API コールの部分を MODE SDK として切り出して公開するという野望もあったりします。それがいつになるかの見通しはまだありませんが、そうして MODE を使った開発を顧客やサードパーティが活発にできる世界に近づけていきたいと思います。

*1:MODE には同じシステムを 3 回作るという哲学があります。ビジネスにおける発見や、初回実装時の反省点に基づいて適切な設計・実装に至るため、リリース後の再実装を厭いません。

*2:こちらは本文中にもある通り Sensor Cloud のユーザー向け開発キットという位置付けであり機能が部分的で、既存の Sensor Cloud を置き換えることはできませんでした。また、本体 Sensor Cloud と開発キット類似の 2 つのコードベースが生まれたことで運用の手間が増えているのも事実です。なので今回の書き直しはこの 2 つを統合しつつ完全に置き換えることを狙っています。

*3:https://dev.tinkermode.com/platform/how-to/key-value-pairs-homes

*4:現在では関数のコンストラクタ呼び出しは class 構文の中にすっかり紛れてしまいますね。

IoTとMQTTとMODE

どーも、MODEでソフトウェアエンジニアをやっている@banana-umaiです。

今日はIoTな開発をしようとした際に必ずといってよいほど出てくるMQTTというプロトコルとMODEの提供するIoTプラットフォームの関係について書いてみたいと思います。

TL;DR

  • MQTTはマシンリソース・ネットワークリソースに乏しい環境での動作を前提として設計された双方向の軽量なバイナリメッセージングプロトコルです。
  • MQTT Brokerを利用することで気軽にMQTTを使ったシステムを開発することは可能ですが、本番運用に耐えうる堅牢なシステムを構築するのは簡単ではありません。
  • MODEの提供するIoTプラットフォームはMQTTによるIoTデバイスとの接続方式をサポートするものの、MQTT Brokerを提供していません。
  • MODEではアプリケーションサーバーがMQTT通信レイヤーを持ち、アプリケーション固有の振る舞いをすることで、信頼性のあるIoTシステムの構築を容易にしています。

MQTTとは?

MQTT(Message Queuing Telemetry Transport)の特徴を http://mqtt.org/faq の概要説明から抜粋すると

  1. リソースが限定されるデバイスや劣悪なネットワーク環境(低帯域・高レイテンシ・低信頼性)での動作するようにデザイン。
  2. 極めてシンプルかつ軽量なメッセージングプロトコルで、Publish/Subscribeの双方向を想定。

というような特徴があげられます。

IoTにおいてIoTデバイスやゲートウェイなどのエッジマシンやその動作環境はまさに上記のような状況が多く、そのため、MQTTがIoT製品や多くのIoTプラットフォームで、クラウド <=> エッジ感の通信プロトコルとして多く用いられています。

デバイスから見た際に登り(Publish)と下り(Subscribe)の双方向の通信が可能なセッションを作成し、その上でバイナリフォーマットのメッセージのやりとりを行うことになります。最小のパケットサイズは2バイト(pingパケット)であり、ヘッダーサイズが小さい点、通信フットプリントはHTTPに比べると基本的には小さくしやすいという特徴があります。

MQTT同様にバイナリな双方向通信を行うWebSocketと比べた場合、MQTTはメッセージングプロトコルでもあるので、メッセージングに関する仕様が色々定義されています。一番わかり易い点としては・・・

  • トピックと呼ばれるメッセージのルーティングのための仕組みがある。
  • QoS(Quality of Service)というメタデータをメッセージのPublish/Subscribeの際に用いることでメッセージの配信に関しての精度をコントロールできる。

等の特徴が挙げられます。

MQTTを用いたシステム開発を行うといった場合、MQTT Brokerと呼ばれるサーバーソフトウェアを用いるのが典型的な。MQTT Brokerはその名の通り、IoTデバイスとアプリケーションサーバー郡の間でメッセージを仲介する役割を担います。

f:id:banana-umai:20200519161525p:plain

代表的なオープンソースソフトウェアとしては、Eclipse Mosquittoがあります。Mosquitoを使うと簡単にメッセージのPublishとSubscribeを試すことが可能です。

Mosquitto Serverを起動する。

$ docker run -it -p 1883:1883 -p 9001:9001

/foo トピックのメッセージをQoS 1で待ち受ける。

$ mosquitto_sub -t '/foo' -q 1

/foo トピックにメッセージをQoS 1で送信する。

$ mosquitto_pub -t '/foo' -m "hello" -q 1

アプリケーションのインストールとサーバーの起動がうまくいっていれば、mosquitto_subコマンドを起動しているshellの標準出力に"hello"と表示されるはずです。*1

本稿では、これ以上MQTT自体に深入りはしませんが、MQTTに関してより深く知りたい方は、以下のドキュメント等で補足して頂くことをおすすめします。

MODEプラットフォームとMQTT

MODEプラットフォームとIoTデバイス間での通信はMQTTを利用しています。ですが、MODEプラットフォーム自体はMQTT Brokerではありません。MODEプラットフォームでは、IoTデバイスがMODEプラットフォームの機能を利用するためのプロトコルとしてMQTTを用いています。

ざっくりと言うとMODEプラットフォームの利用方法は以下のようになります。

  • MODEプラットフォームとIoTデバイスの間の通信のみでMQTTを用います。IoTデバイスからMODEプラットフォームに対してデータを送信にMQTT Publishを、逆に、MODEプラットフォームからIoTデバイスに対して命令やデータを送信する際にはMQTT Subscribeを利用しています。
  • 一方、MODEプラットフォームとカスタマーのサーバーアプリケーションやスマートフォンアプリケーションの通信はREST APIやWebSocket等のより一般的なWEB技術を用います。MODEプラットフォームを経由してIoTデバイスにデータやコマンドを送信する場合には、MODEプラットフォームのREST APIを利用します。WebSocketを用いて、MODEプラットフォームからのIoTデバイスのデータを継続取得することも可能です。

f:id:banana-umai:20200519162858p:plain

MODEプラットフォームのアプリケーションサーバーはMQTTプロトコルでIoTデバイスからの接続を受付け、IoTデバイスがPublish/Subscribeするトピックに応じて、所与の動作を行います。

IoTデバイスがPublish可能なトピックとして「Event」トピックと「BulkData」トピックという2種類の代表的なトピックがあります。詳細は省きますが、リアルタイムに処理したい軽量なデータについてはEventトピックに対してデータを送信する、比較的データ容量が大きかったり、リアルタイムで処理必要はないがデータロストを防ぎたいような場合にはBulkDataトピックにデータを投げる、といった形で使い分けることができます。

一方、IoTデバイスがSubscribe可能なトピックとしては「Command」トピックと「KeyValue」トピックという2種類のトピックがあります。ユーザーアプリケーションはMODEプラットフォームのそれぞれのトピックに対応したREST APIを通じてIoTデバイスに対して命令やデータを送信できます。こちらも詳細は省きますが、Commandトピックはその名の通りデバイスに実行させたい「命令」を送信するために用います。一方、「KeyValue」トピックはデバイスとMODEプラットフォーム間でキーバリューストアを共有・同期するために用います。

アプリケーションサーバーがMQTTプロトコルを実装する価値

前項に書いた通り、MODEプラットフォームではMQTTを利用していますが、MQTT Brokerを利用するのではなく、アプリケーションサーバーがMQTTプロトコルを使ってIoTデバイスと通信します。そして、特定のMQTTトピックに対して、特定の機能(役割)を持っています(REST APIがリソースごとに機能があるのと同様)。

現状、Subscribe/Publish可能なトピックはそれぞれ2種類しかないので、一見すると、MQTTブローカーのような自由度がないように見えるかと思います。では、このようなアプローチのメリットはあるのでしょうか。

例えば、MQTT Brokerを利用した場合、以下のようなケースが存在します。

f:id:banana-umai:20200519163206p:plain

IoTデバイスからMQTT Brokerへのデータ送信が成功するものの、そこからApp Serverへのデータ通知(Subscribeを通じて同一トピックのメッセージをApp Serverが受信する)がサーバーやネットワークの不具合等の理由により失敗する、という可能性があります。*2

MQTTをご存じの方は、Retention flagをメッセージに付与すればよいのではないか?あるいは、Persistent Sessionを使えばよいのではないか?というご意見もあるかもしれません。筆者の理解する限りでは、もちろんこれらの機能を適切に使えば堅牢なシステムを構築することも可能ではあると思われますが、どちらも銀の弾丸ではなく、Retention Flagの場合には、1トピックあたり、1メッセージに限定される、また、Persistent Sessionを使った場合も、当然メッセージを保持する容量はサーバーリソースに限定されるので、すべてのデータを確実に保持することができるわけではない、という制限が付きます。ですので、すべてのTopicに対してこのような処理を行うかどうか、どのようなTopicに対してどのようなメッセージ戦略を取るか、どのようにサーバーリソースを確保するか、等適切なシステム設計が必要になると思われます。

一方、MODEプラットフォームでは、アプリケーションサーバーが直接MQTTを介してIoTデバイスと通信をしており、特定のトピックに対してPublishされたメッセージに対してQoSに応じて、ある種の処理が確実に行われることを保証します。QoSが1であればPubackを返しますが、この際に、トピック毎に定義されているアプリケーション的な動作を行った上で、Pubackを返しています。例えば、先に紹介したBulkDataトピックにメッセージがPublishされた場合、MODEプラットフォームのMQTTサーバーはデータを内部的なBlobストレージにデータを保存しつつ、後続処理を行うために内部的なメッセージキューにエンキューを行います。なお、過去のブログエントリーでデータの到達保証の難しさについて書かせて頂きましたが、弊社Gateway製品におけるデータ到達保証はこのBulkDataトピックを利用して実現しています。

最後に

その他にもアプリケーションサーバーが直接MQTTをしゃべることで実現することは他にもあるのと、それを実現するための技術的なトリックもいくつかあるのですが、それは別の機会に書かせて頂ければと思います。

近年では、オープンソースのMQTT Brokerアプリケーションも色々とありますし、Hive MQ等のMQTT Brokerサービスや、AWS IoTのようにMQTT Brokerを含む様々なIoT管理機構をあわせたプラットフォームが存在し、気軽に利用できるようになっていると思われます。ただし、メッセージングシステムは本質的に分散システムならではのアプリケーション設計の難しさをはらむことに注意しなければなりません。もちろん、エンジニアとして腕がなる部分でもあるので挑戦してみるのも楽しいと思います!一方、このような努力をすることよりも、本質的なIoTビジネスを実現するための開発に注力したいという方はMODEプラットフォームがその一助になるのではないかとも思います。MODEのIoT製品開発プログラム MODE Labs という形で、導入サポートも含めて対応可能ですので、ご興味ある方はぜひご覧ください。

その他業種ごとのデータ収集・分析ソリューションについては下記の事例集をご覧ください。

https://www.tinkermode.jp/customers

*1:ツールのインストールに関してはMosquitto ServerのインストールMosquitto Clientのインストールを参考にして下さい

*2: QoSはBrokerに対するMQTT BrokerとMQTT クライアントの間でのメッセージ到達保証についてのパラメーターです。したがって、特定のトピックに対してのQoS 1でPublishが成功したとしても、同一に対してのSubscriberが存在し、そのメッセージに対しての適切な処理を行ったことを保証するものではありません。

5G時代に求められるデータ活用プラットフォーム構築の勘所

5Gの実用化によって大容量、低遅延なデータ通信があらゆる場所で利用できるようになろうとしています。それによって生まれる新たなビジネスのためには5Gという通信インフラのみでは構築できず、5Gの性能をフル活用できる強力なクラウドプラットフォームの存在が不可欠です。

「MODE CLOUD COMPONENTS」は様々なクラウドサービスと連携可能な高速データ収集・配信向けのクラウドパーツ群です。AWS等でIoTサービス等を構成する場合に高速データ収集に特化したMODEのクラウドパーツとメンテナンスフリー性能を重視したエッジ・インテリジェント・ゲートウェイである「MODE SENSOR GATEWAY」を利用することで、ボトルネックのない5Gベースのクラウドサービスを迅速に構築することが可能です。

5Gのパフォーマンスをフル活用するシステム開発のコツ

5G通信の大きな特徴として大容量、低遅延というキーワードが挙げられます。これをITシステムで活用する場合に得られる最大のメリットは「あらゆる情報がリアルタイム化する」ということでしょう。旧来の無線通信技術ではデータ容量と通信速度は常にトレードオフの関係にありました。そのためそれをベースとしたITシステムは「少量データを高速に処理する」か「大量のデータを時間をかけて処理する」という2つの設計をケースに応じて実装してきました。

5Gの時代にはシステム設計に3つ目の戦略が求められます。いかに「大容量データを高速に処理」できるかということ。そしてそれを高いコストパフォーマンスで実現すること。この2つの要素を同時に実現することが5G時代のビジネスを成功に導くカギとなるでしょう。 現実世界からのデータ収集でボトルネックを生まない設計 5G通信がリアルタイムシステムを実現し、それが新たなビジネス価値を創造することは理解できました。一方でそのようなシステムを開発し実運用することは誰にでも可能なのでしょうか?5Gという新しい技術はすでに利用できますが、それを構成するITシステムの設計に新たな知見や技術は必要ないのでしょうか?

答えは必要です。最も大事なことを端的に説明すると「大規模なデータを高速に処理するために特化したシステムコンポーネントを選択しそれを最適に組み合わせる設計」が5Gの通信性能を最大限に活かせる新しいITシステムの設計と技術が必要となります。

5G時代低遅延を実現する技術としてMEC(Multi‐access Edge Computing)の活用が挙げられます。MEC上にセットアップできるMODE GATEWAY COMPONENTSを利用するとMECでのデータ収集とエッジ処理へのデータパイプラインを簡単に構築することが可能です。また、MODE GATEWAY COMPONENTSに搭載された「Firmware Bundle Updater」や「Log Requester」が多拠点のMECサーバーの自動アップデート、稼働ログ収集を行うのでMECサーバーの管理を効率化することができます。

MODEは創業以来5年間にわたってIoTリアルタイムデータ収集を専業してきた中で、さまざまな業種のリアルタイムITシステムの構築に携わってきました。またその中でMODE独自のリアルタイムデータ収集プラットフォーム「MODE PLATFORM」とその基盤機能群「MODE CLOUD COMPONENTS」を開発しサービス提供しています。

これら弊社のノウハウとコンポーネントを活用することで5G通信の大容量・低遅延通信に対してクラウド側でボトルネックを生まないシステムを構築可能です。よって現実的に5Gパフォーマンスをフルに活用したシステム運用が可能となります。

5G時代のリモートワークシステムの構成

例えば遠隔地の各拠点の機器を複数同時にリモート制御するリモートワークソリューションの構築を考えてみましょう。遠隔地の機器を操作するオペレーターもリモート勤務であり、本社の集中コントールルームにいるわけではありません。こういった近い将来に必要な業務のシステム構成には、高いリアルタイム双方向通信性能とそのスループットを妨げない高速なプラットフォーム・アプリケーションが必要です。

遠隔機器のリアルタイム制御には機器のカメラ画像のリモート共有や操作のフィードバック・ステータス、オペレーター間のコミュニケーションデータ等多くのデータがリアルタイムに複数の拠点を行き交います。さらにこれら行き交う情報を稼働実績データとして事後分析したり、AIによるリアルタイムサポートを行うためにはシステムにデータトラフィック制御を行いながら必要なデータを大規模に蓄積できる能力が求められます。

「MODE TIMSERIES DATABASE」、「MODE STREAM DATA SERVICE」などの高速性能に特化したデータ蓄積コンポーネントがニーズに応じたデータ配信と蓄積の同時制御を実現します。これらのコンポーネントはエッジデータ収集デバイスであるMODE GATEWAYと組み合わせることでリアルタイムなリモートワークソリューションを強力に実現します。

ビジネス企画からシステム開発までトータルサポート

MODEのデータ収集ソリューションは5G時代に求められるリアルタイム大容量のニーズに対して迅速に高コストパフォーマンスなサービスを提供させていただきます。

MODEのIoT製品開発プログラム「MODE Labs」は、IoT製品企画から開発コンサルティング、システム開発、マーケティングまでお客様のIoTビジネス成功のためフルサポートを月額定額料金で承っております。このプログラムを通じて、MODEでは豊富なIoTシステム構築ノウハウを活かした幅広いビジネス協業、サービス提供を行っております。

その他業種ごとのデータ収集・分析ソリューションについては下記の事例集をご覧ください。 www.tinkermode.jp

次世代ドライブレコーダーに必要な強固なクラウドバックエンドを構築する

近年ドライブレコーダーの急速な普及によって各開発メーカー間の競争も激しくなってきており、ドライブレコーダーのコストパフォーマンスとどのように上げていくかが課題となっています。その中でIoT技術を活かしてドライブレコーダーとクラウドをつなげ新しい付加価値とビジネスチャンスを創造するニーズが増加しています。

この章では、ドライブレコーダーをクラウドに接続するために必要なバックエンドの構築方法とそのポイントを解説します。

多拠点・大容量データを安定して収集するクラウドバックエンド

現時点でバス・タクシー等の業務用車輌のドライブレコーダー普及率は8割を超え全国のあらゆる地域で無数のドライブレコーダーが日々稼働しています。そういった多数の機器からクラウドにデータを収集するのにIoT技術は欠かせません。

さらにドライブレコーダーという機器の特性上、温度センサなどの数値データと違って収集するデータが動画データというサイズが大きいデータを扱わなければならないのが収集システムを設計する場合の重要なポイントとなります。一般的に多拠点から少量データを収集するシステムは構築・運用ともに簡単ですが、多拠点から大容量データを収集するシステムの構築となるととたんに難易度が上がります。

ここに技術的に考察すべきポイントがあります。インターネットで利用されるプロトコルは多くが少数のサーバーから多拠点にデータを配信(ダウンリンク)することに特化しています。ですので、多拠点からクラウドストレージにデータを収集(アップリンク)しようとするとシステム側で様々な工夫が必要となります。

「MODE CLOUD COMPONENTS」はMODEが長年のIoTシステム構築の中で鍛え上げられたクラウドデータ収集に特化した機能群です。これらのクラウドコンポーネントを組み合わせることで強力なIoTデータ収集バックエンドを迅速かつ高コストパフォーマンスで構築することが可能です。

映像データをクラウドに送信するタイミング制御

運転中のすべての録画データをリアルタイムにクラウドに保存しようとすると、通信費用が高くなるのと同時にクラウドストレージの維持コストも大きくなるのでサービス価値を高くすることができません。

また、これはドライブレコーダーにかぎった話ではありませんが、機器の回路内と違って記憶領域と無線で通信するという点が重要です。通信状況に応じて不安定な場合の送信レートの制御、通信不能な場合のフォローアップ機能の実装を検討する必要があります。

これらはドライブレコーダーを活用したビジネスにおいては、そのビジネス戦略ごとに適した制御設計を行う必要があります。MODEでは、これらをニーズ毎に解決するためのクラウド機能群「MODE CLOUD COMPONENTS」を提供している他、ドライブレコーダーをIoT化する上で必要なハードウェア性能、制御に関する専門の技術者による技術ノウハウ指導、開発サポートを行っています。

ニーズに応じた通信技術を選択する

IoTシステムを運用する上でのランニングコストとして「通信費」と「ストレージ維持費」は重要なポイントです。特に通信費に関しては、コストパフォーマンスを上げるための多様な選択肢と誰がどのように担保するかを含めた戦略的考察が多岐にわたるため、最適解を捻出することが簡単ではありません。

MODEでは、各通信系企業とのオープンなパートナーシップの中と豊富な実装経験の中からお客様のビジネスニーズに応じた最適な通信技術についてのアドバイスを行っています。

ビジネス企画からシステム開発までトータルサポート

MODEのIoT製品開発プログラム「MODE Labs」は、IoT製品企画から開発コンサルティング、システム開発、マーケティングまでお客様のIoTビジネス成功のためフルサポートを月額定額料金で承っております。このプログラムは「ハードウェア製品開発、販売を得意としているが、IoT・クラウド等の技術ノウハウが不足している」という悩みをお抱えの企業様に好評いただいております。

その他業種ごとのデータ収集・分析ソリューションについては下記の事例集をご覧ください。 www.tinkermode.jp

感染症対策IoTソリューション構築のポイント

新型コロナウイルス感染症蔓延などであらゆる業態での感染症対策が事業継続、労働者の稼働連続性維持の観点から必須となってきました。施設入場時にサーマルカメラ等で入場者の体温を検知するシステムが一般的に利用されますが、カメラの視認などの業務負荷を避けるためデータのクラウド蓄積や既存業務システムとの連携が求められます。

この章ではサーマルカメラをIoT技術で業務システムと統合したオフィスオートメーション・システムとその構築時のポイントを解説します。

異常検知時の即応性を維持したままクラウドデータ収集を行う

カメラ等のデバイスをIoT化すると、すべてをクラウドで制御するため異常通知が遅延するというイメージを持たれる場合があります。実際システムの構成によってはそうなるケースもありますが、それを念頭においてシステム設計を行えば問題ありません。具体的には「エッジ処理」を盛り込むことで回避できます。

体温検知用のサーマルカメラをMODE Gatewayに接続する場合、Gateway自体に様々なエッジ処理を実装することが可能です。例えば入場者の異常体温を検知した場合に入場ゲートを閉じたり、警告ランプを点灯させるといった動作を実現する場合、MODE Gatewayを入場ゲートや警告ランプと接続することによって、体温データに基づいた各デバイスの制御が可能となります。

「MODE Gateway Local API」はこのような処理を簡単に構成できるようMODE Gatewayに実装されている汎用的なAPIです。Local APIを使うと上記のようなエッジ処理とデータ収集を組み合わせたカスタマイズがユーザーサイドで簡単に実装可能となります。これによりエッジデバイスに新しいデバイスとの連携が必要になったとき、アップグレードを迅速に行うことが可能となります。

また「MODE Firmware Bundle Update」を利用してGatewayの新しいアップデートプログラムをリモート操作ですべてのGatewayに自動展開できます。各エッジデバイスの稼働状況を「MODE Log Requester」で収集分析することが可能です。

MODE Platformは柔軟にカスタマイズ・アップデート可能なインテリジェントIoTゲートウェイ「MODE Gateway」とセキュアに接続された大規模・高速データ収集プラットフォーム「MODE Platform」で構成されています。この統合型IoTプラットフォームを利用してエッジ処理を組み合わせた高機能な自動化システム運用を迅速に開始することができます。

対策に必要なデータを複合的に収集して業務システムと連携する

施設内での感染症の蔓延を未然に防ぐ対策行いたい。それを検討する場合は「いかにして感染疑義を早期に特定するか」と「感染疑義が発生した場合に速やかに防衛対策に移行する」ことがポイントになります。これらを検討する場合はその施設・設備ごとの特徴ごとに手法が異なるため、必ずしもサーマルカメラによる体温検知がすべてのケースにおいて最適解ではないことに注意してください。

カメラを設置できない場所があり、カメラだけでは検出精度を担保できない環境も存在します。そして体温検知はあくまで感染疑義の早期特定に有益であり、感染疑義が発生した場合の対応手段ではありません。感染が発覚した場合に従業員に通知する、発覚に至るまでの経過データを記録、分析するためには業務システムとIoTシステムとの綿密な連携が必要となります。

これら複合的で複雑な要件に対応するため、MODEでは様々なシステム・アプリケーションと柔軟に連携可能なインターフェイスを持つクラウド・コンポーネントを提供しています。例えば「MODE Kinesis Smart Module」を使うと異常を検知したカメラから異常時前後の録画データを業務システムのストレージに配信することが可能です。

また、社員IDカード等から感染疑義者の施設内の移動履歴、接触疑義者の特定等を行うために「MODE TIMESERIES DATABASE」がそれら履歴データの収集と高速な集計処理を担い業務システムと連携することで業務システムに負荷がかからない迅速な分析・情報配信が可能となります。

MODEでは上記のようなIoTデータ収集・集計に特化したクラウド機能群「MODE CLOUD COMPONENTS」を提供することであらゆるデータ収集要件にお応えしております。すべてのコンポーネントは多くのシステムに接続可能なREST APIを提供しており社内業務システムやプライベートクラウド・アプリケーションなどと容易に接続・連動することが可能です。

ビジネス企画からシステム開発までトータルサポート

MODEのIoT製品群は感染症対策デバイスを開発・提供されているメーカー様にクラウドソリューションを提供させていただく他、感染症対策デバイスを施設内で有効活用するための自動化を行いたいユーザー様に対するシステム連動などのイングレーション、サポートを行っております。

MODEのIoT製品開発プログラム「MODE Labs」は、IoT製品企画から開発コンサルティング、システム開発、マーケティングまでお客様のIoTビジネス成功のためフルサポートを月額定額料金で承っております。このプログラムを通じて、MODEでは豊富なIoTシステム構築ノウハウを活かした幅広いビジネス協業、サービス提供を行っております。

その他業種ごとのデータ収集・分析ソリューションについては下記の事例集をご覧ください。 www.tinkermode.jp

シートセンサー 「スリープバスター」 のご紹介

MODE エンジニアの武田です。当社ではクラウドやゲートウェイのシステムを開発するだけではなく 日々いろいろなセンサーや機器を実際に試してみたり使ってみたりしています。 今回はその中からシート型のセンサーを使ってその人の体調を測定する機器「スリープバスター」をご紹介します。

体調の変化・疲労度・眠気を「見える化」

バスやトラックの安全運行の徹底については、運転時間の基準遵守、日常点検、運転者に対する指導・教育等に加え、 近年ではIT 機器を活用した運転者のリアルタイムの運行状況の確認や疲労状態の確認が広く普及してきています。

スリープバスターはそのような機器のひとつであり、座席に装着したセンサーパッドで測定した体表脈波を解析し、 体調の状態の推定と警告をリアルタイムで行う車載機器です。開発・製造は株式会社デルタツーリングですが 販売代理店から購入することができます。

スリープバスター販売代理店(一部、敬称略、順不同)

www.juki.co.jp

www.yagikuma.co.jp

クラウドとの連携

スリープバスターは単独で動作する機器であり、インターネット接続などは必要としません。 しかし、MODE モビリティクラウドと組み合わせて利用していただくことで、 測定した結果をクラウドに送信・記録蓄積し、運行状況の見える化や運転者に対する指導・教育の基礎データとして活用できるようになります。 スリープバスターとゲートウェイの間はUSBケーブルで接続します。スリープバスターの計測結果はゲートウェイを通してクラウドに送信・蓄積管理されます。

f:id:mode_t:20200401183856j:plain

オフィス施設等での活用

スリープバスターは車載機器として運転手のモニタリングを行うために開発された機器ですが、当社では開発元と連携をとりながら MODE センサークラウドと組み合わたオフィスや施設等での活用検討や検証も進めています。 具体的なユースケースとしては、着席・離席の検知による見守りやバイタルデータの収集、快適なオフィス空間作りのための集中度測定などが挙げられます。

実際の測定結果

実際に測定をしたときの結果をご紹介します。私がスノーボードに行ったとある1日の往復の記録です。 グラフ部分が判定結果の時系列推移であり、運転開始後から60分間程の結果を表示しています。 スリープバスターによる判定結果は13段階あり、平常、注意、警告の状態を表します。 なお、判定の確度はおよそ85%(開発元計測値)です。

往路の記録

往路は眠気や疲れの自覚が無い平常状態での運転となります。注意レベルである「軽い眠気」などが判定されていますが、概ね平常レベル(3以下)から注意レベル(6以下)に収まっています。

f:id:mode_t:20200401164703j:plain

帰路の記録

帰路は疲れを自覚した状態での運転となります。運転開始20分後から警告レベルである「覚低状態」(覚醒水準の低下)や眠気・疲労が複数回判定されています。

f:id:mode_t:20200401164729j:plain

以下は帰路の仮眠休憩後の記録となります。休憩後は体調が回復した自覚がありましたが、スリープバスターの結果としても、全体として平常レベル(3以下)が長く続いていることが見て取れます。

f:id:mode_t:20200401164804j:plain

今回の結果は一人・1回の運転記録を簡易的に見える化しただけですが、複数人・複数回のデータを蓄積・解析することで判定精度の向上や、運転手毎の傾向や体調の管理とそれに基づく指導・教育等を行うことができるようになります。

おわりに

今回は簡単ではありますが、スリープバスターの紹介をさせていただきました。スリープバスターは当社のMODE モビリティクラウドやMODE センサークラウドと組み合わせて、1台からご購入・お試しいただくことができます。お気軽に当社までお問い合わせください。