0
1

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 5 years have passed since last update.

sakura.ioのincoming-webhookを利用してデバイスにデータ送信

0
Last updated at Posted at 2019-02-09

概要

外部から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」を選択します。

2019-02-09_11h21_26.png

incoming-webhook の設定

名前とSecretを設定します。ここでは、名前を「Webhookでデバイスにデータ送信」、Secretを「1234」とします。

なお、Secretは、HTTPリクエストメッセージが改ざんされていないことを確認したり、誰かが勝手にデバイスに対してデータを送信することを防ぐために使う値です。運用時には別の文字列を使い、他人には知られないようにしてください。なお、Secretを空欄にすることもできます。

2019-02-09_11h32_13.png

HTTPリクエストを送信する宛先URLの確認

連携サービスの一覧から、先ほど作成したincoming-webhookの設定にアクセスします。

2019-02-09_11h29_52.png

URL欄とToken欄を確認しておきます。後ほど、このURLに対して所定のJSON形式でHTTP POSTリクエストメッセージを送信することになります。

2019-02-09_11h35_542.png

データ送信テスト

HTTPリクエストを送るプログラムを作成しなくとも、ブラウザを使って送信テストをすることができます。

Secretを設定したままではテストが面倒なので、ここでは、一旦、Secretを空欄にして保存ボタンを押して、Token欄のテキストをコピーしてから「Incoming Webhook APIドキュメント」をクリックしてください。

2019-02-09_11h54_22.png

移動したら、Token欄にコピーしておいたテキストをペーストします。また、module欄にモジュールIDを設定します。このモジュールIDは、sakura.ioコントロールパネルのモジュールカテゴリの以下の画面から確認できます。

2019-02-09_12h01_29.png

その他、次のように設定します(チャンネル1に98という整数値を送信する例になっています)。

2019-02-09_12h03_55.png

設定ができたら、先に作成しておいた受信側のプログラムを起動してから「Try it out!」のボタンを押します。次のようにResponse Codeが200になっていれば成功で、デバイス側では、コンソールに{'channel': 1, 'type': 'i', 'data': [98, 0, 0, 0, 0, 0, 0, 0], 'offset': 673}のような出力がされていると思います。

2019-02-09_12h14_01.png

うまくいかない?

Incoming WebhookにSecret「1234」を設定したままであったり、X-Sakura-SignatureにSecret値をそのまま設定している場合は、Response Codeが「401」で失敗します。X-Sakura-Signatureにすべき値(文字列)は、Secret と RequestBody を使ってHMAC-SHA1というハッシュ計算を行なった結果の文字列になります。

2019-02-09_12h19_24.png

送信プログラムの作成

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
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?