寝て起きて寝る

過ぎたるは及ばざるが如し

Sigfox Callbackで温度、湿度、気圧をSlackへ通知してみた

10月12日にSigfoxハンズオン広島(お土産付き)へ参加しArduino用Sigfoxシールド(Sigfox Shield for Arduino : UnaShield)を頂いたので、温度、湿度、気圧をSlackへ通知してみました。

sigfox.connpass.com

ArduinoからSlackへの通知する構成図

本当ならAWS IoT を使用するのだろうけど今回は手っ取り早く下記の方法で。

  1. UnaShieldで取得した温湿度・気圧センサーデータをSigfoxで送信

  2. Sigfox CloudのCallback機能機能を利用しAmazon API GatewayへPOST送信

  3. AWS Lambdaを使用してSlackへPOST送信し通知

f:id:yasu7ri:20181104100856g:plain

Sigfoxとは

フランスの通信事業会社Sigfox社が2009年から提供しているIoT向けに特化したLPWA通信規格(Low Power、Wide Area)です。国内では京セラコミュニケーションシステム株式会社(以下、KCCSと表記)が事業者としてサービスを提供しています。

第804回:SIGFOX とは - ケータイ Watch

Arduino用Sigfoxシールドの準備

ハンズオンでお土産で頂いたUnaShieldには温湿度・気圧センサ(BME280)が搭載されているのでこのセンサーを利用して 温度、湿度、気圧を取得します。

www.switch-science.com

UnaShieldを使用する前にSigfox Backend Cloudへの登録する必要があります。登録方法は取扱説明書に記載されているのでそれに従って登録します。

Sigfox Shield for Arduino(UnaShield V2 / V2S* )取扱説明書

Arduinoのプログラミング

今回はUnaShieldに標準で搭載されている温湿度・気圧センサ(BME280)から値を取得しSigfox Cloudへ送信する為のプログラムを作成し実行します。 温湿度・気圧センサ(BME280)を利用すりプログラムに関しては下記のサンプルを元に作成します。

github.com

30分毎に温度、湿度、気圧をSigfox Cloudへ送信します。(Arduinoのソース)

#include <Wire.h>
#include <SPI.h>
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme; // I2C

#include "SIGFOX.h"

static const String device = "NOTUSED";        //  Set this to your device name if you're using SIGFOX Emulator.
static const bool useEmulator = false;         //  Set to true if using SIGFOX Emulator.
static const bool echo = true;                 //  Set to true if the SIGFOX library should display the executed commands.
static const Country country = COUNTRY_JP;     //  Set this to your country to configure the SIGFOX transmission frequencies.
static UnaShieldV2S transceiver(country, useEmulator, device, echo);  //  Assumes you are using UnaBiz UnaShield V2S Dev Kit


void setup() {
  //  Initialize console so we can see debug messages (9600 bits per second).
  Serial.begin(9600);
  Serial.println(F("Running setup..."));
  //  Initialize the onboard LED at D13 for output.
  pinMode(LED_BUILTIN, OUTPUT);
  if (!bme.begin(0x76)) stop("Bosch BME280 sensor missing");  //  Will never return.
  //  Check whether the SIGFOX module is functioning.
  if (!transceiver.begin()) stop("Unable to init SIGFOX module, may be missing");  //  Will never return.
}

void loop() {
  //  Will be called repeatedly.
  //  Read the ambient temperature, humidity, air pressure.
  float filteredTemp = bme.readTemperature();
  float filteredPressure = bme.readPressure() / 100.0F;
  float filteredAltitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  float humidity = bme.readHumidity();

  Serial.println("---");
  Serial.print("filteredTemp = ");  Serial.print(filteredTemp);  Serial.println(" degrees C");
  Serial.print("filteredPressure = ");  Serial.print(filteredPressure);  Serial.println(" hPa");
  Serial.print("filteredAltitude = ");  Serial.print(filteredAltitude);  Serial.println(" metres above sea level");
  Serial.print("humidity = ");  Serial.print(humidity);  Serial.println(" %");
  Serial.println("---");

  //  Send temperature, pressure, altitude, module temperature as a SIGFOX message.
  //  Count messages sent and failed.
  static int counter = 0, successCount = 0, failCount = 0;
  Serial.print(F("\nRunning loop #")); Serial.println(counter);

  String msg = transceiver.toHex(filteredTemp) //  4 bytes
    + transceiver.toHex(filteredPressure)      //  4 bytes
    + transceiver.toHex(humidity);             //  4 bytes

  //  Send the message.
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED on (HIGH is the voltage level).
  if (transceiver.sendMessage(msg)) {
    Serial.println(F("successCount!"));
    successCount++;
  } else {
    Serial.println(F("failCount!"));
    failCount++;
  }
  digitalWrite(LED_BUILTIN, LOW);   // Turn the LED off (LOW is the voltage level).
  counter++;
  
  //  Wait a while before looping.
  //  ex:10000  milliseconds = 10 seconds.
  //     60000  milliseconds = 1  minute.
  //    900000  milliseconds = 15 minute.
  //   1800000  milliseconds = 30 minute.
  Serial.println(F("Waiting..."));
  delay(1800000);
}

Sigfox Cloud側のCALLBACK設定

Arduino用Sigfoxシールドからの送信されたデータを受信しそれをトリガーに起動されるCALLBACKを設定します。

まずは、送信してくるSigfoxシールドのDEVICEの「Device Type」を選択します。

f:id:yasu7ri:20181104131407g:plain

新規Collbackの作成します。

f:id:yasu7ri:20181104132143g:plain

今回は単純にPOST送信するだけなので「Custom collback」を選択します。

f:id:yasu7ri:20181104132423g:plain

「Custom collback」の設定

f:id:yasu7ri:20181104133432g:plain

  • Custom payload config:
temp::float:32:little-endian pressure::float:32:little-endian humidity::float:32:little-endian

Custom payload configの記述方法に関しては下記のサイトを参考にしました。 qiita.com

  • Url pattern:Amazon API Gatewayで公開されたURLを設定します

  • Use HTTP Method:POST

  • Content type:application/json

  • Body:POSTするJSONを定義します

{
    "device" : "{device}",
    "time" : {time},
    "seqNumber" : {seqNumber},
    "duplicate" : {duplicate},
    "data" : "{data}",
    "temp" : {customData#temp},
    "humidity" : {customData#humidity},
    "pressure" : {customData#pressure}
}

Slackの設定

Slackへメッセージを送信するためにIncoming Webhooksを使用します。 Slackのアプリケーション追加ページより incoming-webhooks を検索・選択しインストールします。

インストールを行ったら通知を行うチャンネルを選択します。Webhook URLはAWS Lambdaから通知を行う為に使用するURLになります。

f:id:yasu7ri:20181104140309g:plain

AWS Lambda、Amazon API Gatewayの設定

Amazon API Gatewayを使用しSigfox CollbackからAWS Lambdaを呼び出します。

今回はAWS LambdaのランタイムはGo 1.xを使用し作成しました。 f:id:yasu7ri:20181104141828g:plain

AWS Lambdaの関数になるソースコードです。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    "net/http"
    "strconv"
    "time"
)

const mySlackURL = "Slackで設定したIncoming WebhooksのWebhook URL"
const channel = "通知するSlackのチャンネル"

type SensorInfo struct {
    Device    string  `json:"device"`
    Time      int64   `json:"time"`
    SeqNumber int64   `json:"seqNumber"`
    Duplicate bool    `json:"duplicate"`
    Data      string  `json:"data"`
    Temp      float64 `json:"temp"`
    Humid     float64 `json:"humidity"`
    Press     float64 `json:"pressure"`
}

type SendSlack struct {
    Channel  string `json:"channel"`
    Username string `json:"username"`
    Text     string `json:"text"`
}

func slack(si SensorInfo) (SensorInfo, error) {
    jst := time.Unix(si.Time, 0).In(time.FixedZone("Asia/Tokyo", 9*60*60)).Format(time.RFC1123)
    message := jst +
        "\n温度:" + strconv.FormatFloat(si.Temp, 'f', 2, 64) + `℃, 湿度:` + strconv.FormatFloat(si.Humid, 'f', 2, 64) + `%` +
        "\n気圧:" + strconv.FormatFloat(si.Press, 'f', 2, 64) + `hPa`

    sendms := SendSlack{
        Channel:  channel,
        Username: "百葉箱",
        Text:     message}

    jsonBytes, err := json.Marshal(sendms)
    if err != nil {
        fmt.Println("JSON Marshal error:", err)
        return si, nil
    }
    fmt.Println(string(jsonBytes))

    req, err := http.NewRequest(
        "POST",
        mySlackURL,
        bytes.NewBuffer(jsonBytes),
    )

    if err != nil {
        fmt.Println(err)
        return si, nil
    }

    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)

    if err != nil {
        fmt.Println(err)
        return si, nil
    }
    fmt.Println(resp)

    defer resp.Body.Close()
    return si, nil
}

func main() {
    lambda.Start(slack)
}

作成したAWS Lambdaを呼び出せるようにAmazon API Gatewayの作成・設定を行います。 API GatewayにPOSTメソッドを追加し先ほど作成したLambda 関数を指定します。

f:id:yasu7ri:20181104142518g:plain

最後に

Arduinoを電源に繋ぐと30分毎にSlackに気温、湿度、気圧の通知が来るようになります。 (画像は15分毎ですが。。。)

f:id:yasu7ri:20181104143022g:plain

参考URL