この記事はお客様に問い合わせを受けて、確かにマニュアルに詳しい記述が無いのと、解説した記事が無いと思って書いたものとなります。
SAP IoT services for SAP BTP for the Cloud Foundry Environmentではデバイスからセンサーの情報を受け取るだけではなく、デバイスに対して「コマンド」を送ることも可能です。
「コマンド」となっていますので誤解を受けるのですが、実際に送られるのは文字列となります。ですのでデバイス側ではその受け取った文字列から実際のデバイス側の制御を行うロジックが必要となります。
この機能に関してはゲートウェイとしてMQTTを使用した場合の解説は
こちらにあります。
こちらのマニュアルを読んでいただき、実践いただければ感じはつかめるでしょう。MQTTの場合を簡単に説明すると、SAP IoT servicesが提供する指定されたURL(これには送信先のデバイスIDを含んでいます)に指定された形式でコマンドを送信すると、それはTopicとしてMQTT Broker上でPublishされ、Subscriberはそれを受信することが出来ます。Subscriber(デバイス)側は受け取ったTopicの内容から何かアクションを起こすことになります。
ではRESTゲートウェイを使用した場合はどうなのか?についての解説が今回の記事となります。
セットアップ
MQTTの記事に習い、セットアップから始めることにします。とは言っても前述の記事でゲートウェイをMQTT GatewayではなくREST Gatewayを選ぶだけです。
もし既に
MQTTの方の解説記事の環境を作ってあるということであれば、その環境をそのまま利用して新規にデバイスを作成し、GatewayをREST Gatewayを指定、SensorとしてMQTTの環境で作成したCapabilitiesを指定したSensorを作成して設定するだけで良いでしょう。
コマンドの送信
次にコマンドを送信します。これはMQTTと同じ方法です。今回は前述の記事と同様にDevice Management API のDOCにある機能を利用してみます。
https://<HOST_NAME>/<INSTANCE_ID>/iot/core/api/v1/doc/
を開きます。このHOST_NAMEとINSTANCE_IDはInternet of Things Service CockpitのURL
https://<HOST_NAME>/<INSTANCE_ID>/iot/cockpit/#
に対応しています。Internet of Things Service Cockpitをブラウザで開いてiot以下を置き換えると楽でしょう。
開いたらCommandの場所まで移動し

Try it outボタンをクリックします。
各項目にパラメータを入力します。
ここで使用するDevice ID, Sensor ID,Capability IDはAlternate ID値では無いことに注意が必要です。

こちらのGUID値の方を使用してください。各パラメータを入力してExecuteボタンをクリックします。

Command Issued successfullyが返ってくることを確認してください。
RESTでのコマンド受信
それではこのコマンドをRESTで受信する場合の解説です。
MQTTで受信する場合と同様にDeviceの証明書は必要です。事前にそのDeviceの証明書をダウンロードしておいてください。今回はcURLをクライアントとして使用しています。
私はMacを使用していますのでpem形式でダウンロードしています。

ではcURLを使用してコマンドを受信します。
コマンドの取得先のURLは
https://<HOST_NAME>/iot/gateway/rest/commands/<DEVICE_ALTERNATE_ID>
です。こちらにはAlternate idを使用します。
cURLの使用方法はマニュアルに従うとして、以下のコマンドを実行します。
% curl -v -k -E ’<certificate.pem>:<secret key>’ https://<HOST_NAME>/iot/gateway/rest/commands/<DEVICE_ALTERNATE_ID>
|
間違いがなければ以下のような応答が返ってくるはずです。
% curl -v -k -E ’<certificate.pem>:<secret key>’ https://<HOST_NAME>/iot/gateway/rest/commands/<DEVICE_ALTERNATE_ID>
* Trying 3.121.203.15:443…
* Connected to <HOST_NAME> port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /Users/<Your user>/opt/anaconda3/ssl/cacert.pem
CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
* subject: C=US; ST=Pennsylvania; L=Newtown Square; O=SAP America Inc.; CN=*.eu10.cp.iot.sap
* start date: Feb 12 00:00:00 2021 GMT
* expire date: Feb 16 23:59:59 2022 GMT
* issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS RSA SHA256 2020 CA1
* SSL certificate verify ok.
> GET /iot/gateway/rest/commands/<ALTERNAME_DEVICE_ID> HTTP/1.1
> Host: <HOST_NAME>
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json;charset=UTF-8
< content-length: 113
< Strict-Transport-Security: max-age=16000000; includeSubDomains; preload;
<
* Connection #0 to host <HOSTNAME> left intact
[{“sensorAlternateId”:”10005″,”capabilityAlternateId”:”10000″,”command”:{“Speed”:50.0,”Buzzer”:true,”LED”:true}}] |
一番下のBODY部分を見ると先程送ったコマンドの内容が記載されています。
これはJSONとして返されますので、デバイス側ではこれをパースして何かアクションを起こすロジックを実行することになります。この例の場合でしたらモーターなどの回転数を調整して50にして、ブザーとLEDをONにするという処理を実行するロジックを記述する必要があるでしょう。
ポイント
RESTを使用する場合でポイントとなるのは仕様上デバイス側からコマンドが発行されていないかを取得しなければならないということです。つまり、デバイス側では定期的にポーリングをしてコマンドが発行されていないか確認するようなロジックが必要となります。
その他のポイント
例えばまず以下のパラメータでコマンドを発行し

次に続けて同じデバイスに以下のコマンドを発行するとします。

この後、コマンドが発行されていないかRESTで確認を行うと次のように返されます。
—-中略—–
* Connection #0 to host <HOST_NAME> left intact
[{“sensorAlternateId”:”10005″,”capabilityAlternateId”:”10000″,”command”:{“Speed”:50.0,”Buzzer”:true,”LED”:true}},{“sensorAlternateId”:”10005″,”capabilityAlternateId”:”10000″,”command”:{“Speed”:0.0,”Buzzer”:false,”LED”:false}}]%
|
このように未受信のコマンドも一緒に送られます。JSONでは配列として送られます。
私がテストした限りではこのJSON配列はコマンド発行順に並んでいるように見えます。しかし明確なドキュメントは見つかりませんでしたので、コマンドの前後関係が重要であったり、最新のコマンドだけを実行したいという要件がある場合、送信するコマンドにコマンド発行日時や番号などを付与して識別できるようにしたほうが良いかもしれません。
ちなみに、コマンドが発行されていない場合は以下のように返されます。
* Connection #0 to host <HOSTNAME> left intact
[] |
このように空のJSONが返されます。デバイス側ではこれらを考慮してコマンドの制御を行うようにしてください。
まとめ
SAP IoT services for SAP BTP for the Cloud Foundry EnvironmentでRESTでCommandを扱う場合は上記のような方法となります。コマンドを使うとデバイス制御もクラウド側から可能になりますのでぜひトライしてみてください。