概要
外部からsakura.ioのデバイス(SCM-LTE-01)に向けてデータを送信します。色々と方法はありますが、ここでは、incoming-webhookを使用し、PC上のプログラム(Python/F#)からデバイスに向けてデータを送ってみたいと思います。
これを行なうためにやるべきことは、次の3つです。
- デバイス側で実行する受信プログラム(Python)を作成する
- sakura.ioプラットホームでincoming-webhookサービスを有効化する
- PC側で実行する送信プログラム(F#/Python)を作成する
受信プログラムの作成・実行
受信側は「SCM-LTE-01(sakura.io)」+「Raspi HAT(SCO-RPI-01)」+「RaspberryPi 3 model B+」という構成を想定します。プログラムは、SakuraIOSMBus というライブラリを使用してPython3.6で書いていきます。
基本的には、(1) 受信キュー内にデータが届いているかをget_rx_queue_lengthで調べて、(2) データがあればdequeue_rx_raw()によりそれを取り出すという流れになります。なお、dequeue_rx_raw()の代わりにpeek_rx_raw()を使うとキューに残したままデータを取得することができます。
import time
from sakuraio.hardware.rpi import SakuraIOSMBus
sakuraio = SakuraIOSMBus()
if sakuraio.get_is_online() :
while True:
if (sakuraio.get_rx_queue_length()).get('queued') :
print(sakuraio.dequeue_rx_raw())
time.sleep(1)
else :
print(f'デバイスがLTE回線に接続できていません。')
sakura.ioプラットホームの設定
PCからデバイスに対して、直接、データを送信することはできません。ここでは、sakura.ioプラットホームが提供する incoming-webhook というサービスを経由してPCからデバイスに向けてデータを送信する方法をとります。この方法では、incoming-webhook サービスにより指定されるURLに対して、デバイスに送りたいデータを含んだ HTTP POST リクエストメッセージを送れば、あとはsakura側でデバイスまでデータを届けてくれます。
連携サービスにincoming-webhookを追加
ブラウザからsakura.ioコントロールパネルにアクセスして、プロジェクトを選択して「連携サービスの追加」を行ないます。連携サービスとして次のように「incoming-webhook」を選択します。
incoming-webhook の設定
名前とSecretを設定します。ここでは、名前を「Webhookでデバイスにデータ送信」、Secretを「1234」とします。
なお、Secretは、HTTPリクエストメッセージが改ざんされていないことを確認したり、誰かが勝手にデバイスに対してデータを送信することを防ぐために使う値です。運用時には別の文字列を使い、他人には知られないようにしてください。なお、Secretを空欄にすることもできます。
HTTPリクエストを送信する宛先URLの確認
連携サービスの一覧から、先ほど作成したincoming-webhookの設定にアクセスします。
URL欄とToken欄を確認しておきます。後ほど、このURLに対して所定のJSON形式でHTTP POSTリクエストメッセージを送信することになります。
データ送信テスト
HTTPリクエストを送るプログラムを作成しなくとも、ブラウザを使って送信テストをすることができます。
Secretを設定したままではテストが面倒なので、ここでは、一旦、Secretを空欄にして保存ボタンを押して、Token欄のテキストをコピーしてから「Incoming Webhook APIドキュメント」をクリックしてください。
移動したら、Token欄にコピーしておいたテキストをペーストします。また、module欄にモジュールIDを設定します。このモジュールIDは、sakura.ioコントロールパネルのモジュールカテゴリの以下の画面から確認できます。
その他、次のように設定します(チャンネル1に98という整数値を送信する例になっています)。
設定ができたら、先に作成しておいた受信側のプログラムを起動してから「Try it out!」のボタンを押します。次のようにResponse Codeが200になっていれば成功で、デバイス側では、コンソールに{'channel': 1, 'type': 'i', 'data': [98, 0, 0, 0, 0, 0, 0, 0], 'offset': 673}のような出力がされていると思います。
うまくいかない?
Incoming WebhookにSecret「1234」を設定したままであったり、X-Sakura-SignatureにSecret値をそのまま設定している場合は、Response Codeが「401」で失敗します。X-Sakura-Signatureにすべき値(文字列)は、Secret と RequestBody を使ってHMAC-SHA1というハッシュ計算を行なった結果の文字列になります。
送信プログラムの作成
Incoming Webhook に対して、次のようなHTTP POSTリクエストメッセージを送信することができれば、Pythonでも、C#でも、JavaScriptでも、どんなプログラム言語でも送信プログラムを作成することができます。プログラムを作成しなくとも、コマンドの組合せで送信することもできます。
POST https://api.sakura.io/incoming/v1/xxxxxxxx-xxxxxxxxx-xxxx-xxxxxxxxxxxx HTTP/1.1
Accept: application/json
Content-Type: application/json
Content-Length: 104
Host: api.sakura.io
{"type":"channels","module":"uxxxxxxxxxxx","payload":{"channels":[{"channel":0,"type":"i","value":10}]}}
以降は、sakuraコントロールパネルで、Secretを「1234」に戻したという想定でのプログラムになります。
送信プログラム Python3.6版
import hmac
import hashlib
import http
import json
import requests
def genHMACSHA1(key,msg):
key = key.encode()
msg = msg.encode()
return hmac.new(key,msg,hashlib.sha1).hexdigest()
Token = 'xxxxxxxx-xxxxxxxxx-xxxx-xxxxxxxxxxxx' # 要変更
ModuleID = 'uxxxxxxxxxxx' # 要変更
Secret = '1234' # 要変更
channelData = list()
channelData.append( {'channel':0,'type':'i','value':10} ) # 要変更
channelData.append( {'channel':1,'type':'i','value':20} ) # 要変更
postBody = dict()
postBody['type'] = 'channels'
postBody['module'] = ModuleID
postBody['payload'] = {'channels':channelData}
jsonString = json.dumps(postBody)
# print(jsonString)
headers = dict()
headers['Accept']='application/json'
headers['Content-Type']='application/json'
headers['X-Sakura-Signature'] = genHMACSHA1(Secret,jsonString)
ApiURL = f'https://api.sakura.io/incoming/v1/{Token}'
resMsg = requests.post(ApiURL, jsonString, headers=headers)
resMsgStatusLine= f'{resMsg.status_code} '
resMsgStatusLine+= f'{http.client.responses[resMsg.status_code]} HTTP/'
resMsgStatusLine+= '.'.join(list(str(resMsg.raw.version)))
resMsgHeader = '\n'.join(f'{k}: {v}' for k, v in resMsg.headers.items())+'\n'
resMsgBody = resMsg.text;
print(resMsgStatusLine)
print(resMsgHeader)
print(resMsgBody)
実行結果
200 OK HTTP/1.1
Server: nginx
Date: Sat, 09 Feb 2019 04:36:43 GMT
Content-Type: application/json
Content-Length: 15
Connection: keep-alive
{"status":"ok"}
送信プログラム F#版(RestSharp利用)
open System
open System.Text
open System.Security
open RestSharp
open Newtonsoft.Json
open System.Runtime.Serialization
open Microsoft.FSharp.Collections
[<DataContract>]
type tChannelData = {
[<DataMember>] channel:int
[<DataMember(Name="type")>] type_:string
[<DataMember>] value:int
}
[<DataContract>]
type tPayload = {
[<DataMember>] channels:List<tChannelData>
}
[<DataContract>]
type tPostData = {
[<DataMember(Name="type")>] type_:string
[<DataMember(Name="module")>] module_:string
[<DataMember>] payload:tPayload
}
let genHMACSHA1 (key:string) (msg:string) =
let bKey = Encoding.UTF8.GetBytes(key);
let bMsg = Encoding.UTF8.GetBytes(msg)
let hmac = new Cryptography.HMACSHA1(bKey);
let bs = hmac.ComputeHash(bMsg);
do hmac.Clear()
BitConverter.ToString(bs).ToLower().Replace("-", "");
[<EntryPoint>]
let main argv =
let token = "xxxxxxxx-xxxxxxxxx-xxxx-xxxxxxxxxxxx" // 要変更
let moduleID = "uxxxxxxxxxxx" // 要変更
let secret = "1234"; // 要変更
let postBody = {
type_ = "channels"
module_ = moduleID
payload = {
channels = [ { channel=0; type_="i"; value=10 } // 要変更
{ channel=1; type_="i"; value=20 } ] // 要変更
}
}
let jsonString = JsonConvert.SerializeObject(postBody)
let signature = jsonString |> genHMACSHA1 secret
//printfn "%s" jsonString
let request = new RestRequest( Method.POST)
request.AddHeader("Accept", "application/json") |> ignore
request.AddHeader("X-Sakura-Signature",signature) |> ignore
request.AddParameter("application/json", jsonString, ParameterType.RequestBody) |> ignore
let ApiURL = "https://api.sakura.io/incoming/v1/" + token
let client = new RestClient(ApiURL)
let res = client.Execute(request)
printfn "%d %s" (int res.StatusCode) res.StatusDescription
let nl = System.Environment.NewLine
let rec headerText (headers:List<Parameter>) =
match headers with
| h::t -> h.Name+":"+(string h.Value)+nl+(headerText t)
| [] -> String.Empty
printfn "%s" (List.ofSeq(res.Headers) |> headerText )
printfn "%s" (res.Content)
Console.ReadKey() |> ignore
0
実行結果
200 OK
Server:nginx
Date:Sat, 09 Feb 2019 05:36:11 GMT
Connection:keep-alive
Content-Type:application/json
Content-Length:15
{"status":"ok"}
送信プログラムF#版(FSharp.Data利用)
- 2019/02/19追記
- FSharp.Dataを利用したほうがすっきりと記述できます。
open System
open System.Text
open System.Security
open Newtonsoft.Json
open System.Runtime.Serialization
open Microsoft.FSharp.Collections
open FSharp.Data
open FSharp.Data.HttpRequestHeaders
[<DataContract>]
type tChannelData = {
[<DataMember>] channel:int
[<DataMember(Name="type")>] type_:string
[<DataMember>] value:int
}
[<DataContract>]
type tPayload = {
[<DataMember>] channels:List<tChannelData>
}
[<DataContract>]
type tPostData = {
[<DataMember(Name="type")>] type_:string
[<DataMember(Name="module")>] module_:string
[<DataMember>] payload:tPayload
}
let genHMACSHA1 (key:string) (msg:string) =
let bKey = Encoding.UTF8.GetBytes(key);
let bMsg = Encoding.UTF8.GetBytes(msg)
let hmac = new Cryptography.HMACSHA1(bKey);
let bs = hmac.ComputeHash(bMsg);
do hmac.Clear()
BitConverter.ToString(bs).ToLower().Replace("-", "");
[<EntryPoint>]
let main argv =
let token = "xxxxxxxx-xxxxxxxxx-xxxx-xxxxxxxxxxxx" // 要変更
let moduleID = "uxxxxxxxxxxx" // 要変更
let secret = "1234"; // 要変更
let postBody = {
type_ = "channels"
module_ = moduleID
payload = {
channels = [ { channel=0; type_="i"; value=21 }
{ channel=1; type_="i"; value=37 } ]
}
}
let jsonString = JsonConvert.SerializeObject(postBody)
let signature = jsonString |> genHMACSHA1 secret
let ApiURL = "https://api.sakura.io/incoming/v1/" + token
async {
let! res = Http.AsyncRequest( ApiURL,
headers = [ ContentType HttpContentTypes.Json
Accept HttpContentTypes.Json
("X-Sakura-Signature",signature)] ,
body = TextRequest jsonString )
printfn "%d" res.StatusCode
for (KeyValue(key,value)) in res.Headers do
printfn "%s:%s" key value
} |> Async.Start
Console.ReadKey() |> ignore
0








