sakura.ioとArduinoとGROVEでクラウド温度計を作る(2)

ペット見守り用クラウド温度計の作り方その2です(その1

前回、温度センサーから取得した温度・湿度のデータをさくらインターネットsakura.ioでクラウドに送信してその結果をsakura.ioコンソールのWebSocket画面で確認しました。

今回はsakura.ioのOutgoing Webhookを使って受け取った温度・湿度のデータをSlackチャンネルに自動投稿する機能を作ります。


Slack APPの設定

まず正常時と異常時に投稿するslackの2つのチャンネルを作成します。正常時のチャンネルは通知をOFFにします。

2つのチャンネルに投稿するためにSlack APPを新規登録してチャンネルに投稿する権限を付与します。

設定が完了すると「OAuth Access Token」が発行されるのでメモしてください。config.inc.phpの「SLACK_API_TOKEN」の部分で利用します。


Outgoing Webhookからデータを受け取ってSlackに投稿するslack_api.phpの内容

<?php

//---PHP設定用のファイル
require_once("./config.inc.php");

//---sakura.ioのOutgoing Webhookを受信
getOutgoingWebhook();

///=======================================================================
//   +sakura.ioのOutgoing Webhookを受信
///=======================================================================

function getOutgoingWebhook() {

	//---POSTデータを受信する
	$json = file_get_contents('php://input');

	//---JSONをPHP連想配列に変換
	$json_ary = json_decode($json , true);

	//---すべてのレスポンスヘッダを取得(Apacheのみ)
	$header_ary = getallheaders();

	//---リクエストヘッダ配列のすべてのキーを小文字に変換
	$header_ary = array_change_key_case($header_ary);

	//---------------------------------------------
	//   +JsonデータとSecretとヘッダーの署名が必須
	//---------------------------------------------

	if($json && defined('SAKURAIO_SECRET') && !empty($header_ary['x-sakura-signature'])){

		//---受信した署名と比較
		if($header_ary['x-sakura-signature'] === hash_hmac('sha1' , $json , SAKURAIO_SECRET)){

			$msg_ary = array();
			$data_ary = array();

			//---必要なデータが含まれていたら
			if(!empty($json_ary['payload']['channels'][0])){

				//---配列を展開する
				foreach ($json_ary['payload']['channels'] as $value_ary){

					$channel = $value_ary['channel'];
					$value = $value_ary['value'];

					//---datetimeを変換する
					$time = datetimeToTime($value_ary['datetime']);

					//---温度
					if($channel == 0){

						$data_ary['time'][] = $time;
						$data_ary['temperature'][] = $value;

						//---湿度
					}elseif($channel == 1){

						$data_ary['humidity'][] = $value;
					}
				}
			}

			$ondo_ok_flag = true;

			//---正常時の温度の範囲と比較する
			foreach ($data_ary['temperature'] as $key => $temperature){

				//---温度が異常
				if($temperature <= $GLOBALS['slack_cfg_ary']['temperature_alert_min'] || $temperature >= $GLOBALS['slack_cfg_ary']['temperature_alert_max']){

					$ondo_ok_flag = false;
				}

				$date = date("G:i" , $data_ary['time'][$key]);
				$msg_ary[] = "{$date} 温度{$temperature}℃ 湿度{$data_ary['humidity'][$key]}%";
			}

			$msg_data = implode("n" , $msg_ary);

			//---異常時に投稿するslackチャンネル名
			if($ondo_ok_flag === false){

				$channel = $GLOBALS['slack_cfg_ary']['channel_alert'];

			//---正常時に投稿するslackチャンネル名
			} else{

				$channel = $GLOBALS['slack_cfg_ary']['channel'];
			}

			//---データ配列
			$data_ary = array(
			"token" => SLACK_API_TOKEN ,
			"channel" => $channel ,
			"text" => "{$msg_data}" ,
			);

			$url = "https://slack.com/api/chat.postMessage";

			//---CURLでOAuthでPOST接続
			$result = curlOAuthPost($url , SLACK_API_TOKEN , $data_ary);
		}
	}
}

///=======================================================================
//   +datetimeをUNIXタイムスタンプに変換する
///=======================================================================
function datetimeToTime($p_datetime){

	$result = 0;

	//---マイクロタイム7桁以上でエラーとなるので無視する
	$p_datetime = mb_ereg_replace(".[0-9]{1,}Z$" , ".000Z" , $p_datetime);

	$d = new DateTime($p_datetime);
	$d->setTimeZone(new DateTimeZone('Asia/Tokyo'));

	$result = $d->getTimestamp();

	//---------------------
	//   +返り値
	//---------------------

	return $result;
}

///=======================================================================
//   +CURLでOAuthでPOST接続
///=======================================================================

function curlOAuthPost($url , $token , $data_ary) {

	//---POSTデータ形式に変換
	$data = http_build_query($data_ary , "" , "&");

	//---CURL初期化
	$curl = curl_init();

	//---------------------
	//   +オプション指定
	//---------------------

	$header_ary = array(
	"Authorization: OAuth {$token}" ,
	"Content-Type: application/x-www-form-urlencoded"
	);

	curl_setopt($curl , CURLOPT_HTTPHEADER , $header_ary);
	curl_setopt($curl , CURLOPT_URL , $url);
	curl_setopt($curl , CURLOPT_RETURNTRANSFER , 1);
	curl_setopt($curl , CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1);
	curl_setopt($curl , CURLOPT_CUSTOMREQUEST , 'PATCH');
	curl_setopt($curl , CURLOPT_POSTFIELDS , $data);

	//---接続実行
	$result = curl_exec($curl);

	//---接続エラー時
	if(curl_errno($curl)){

		$error = curl_error($curl);
		error_log($error);
	}

	//---接続を閉じる
	curl_close($curl);

	//---------------------
	//   +返り値
	//---------------------

	return $result;
}

?>

config.inc.php の内容

<?php

//---文字コード
ini_set('default_charset', 'UTF-8');

//---MB内部文字エンコーディング
mb_language('Japanese');
mb_internal_encoding("UTF-8");
mb_regex_encoding('UTF-8');

//---タイムゾーン
date_default_timezone_set('Asia/Tokyo');

//---Slack API Botのトークン
define('SLACK_API_TOKEN' , "xxxxxxxxxx");

//---sakura.io Outgoing WebhookのSecret
define('SAKURAIO_SECRET' , "xxxxxxxxxx");

//---Slackのチャンネル名と正常な温度の範囲を指定
$slack_cfg_ary = array(
	'channel' => 'ondo',
	'channel_alert' => 'ondo_alert',
	'temperature_alert_min' => 15,
	'temperature_alert_max' => 25,
);

?>

※sakura.io Outgoing WebhookのSecretは任意の文字列です。

※温度が15~25℃の範囲であればslackチャンネルの「ondo」に投稿して、それ以外の異常時には「ondo_alert」に投稿します。

※このサンプルの場合、通常時のチャンネル「ondo」の通知はOFFにします。


PHPファイルをレンタルサーバー上に設置

上記のPHP「slack_api.php」と「config.inc.php」を文字コードUTF-8で保存してレンタルサーバ上にアップロードします。

今回は、さくらインターネットが提供する、さくらのレンタルサーバのスタンダードプランを利用しました(PHP 7.3.4で動作確認)


sakura.io Outgoing Webhookの設定

sakura.ioのコンソールよりサービス連携で「Outgoing Webhook」を追加します。

  • [名前] 任意のメモ
  • [Payload URL] 受信用プログラムのURL
  • [Secret] 任意の文字列

※[Payload URL] にさくらのレンタルサーバに設置した slack_api.php のURLを入力します。(http://*******/slack_api.php)

※[Secret]には「config.inc.php」の[SAKURAIO_SECRET]と同一の文字列を入力します。


実行結果をSlackで確認

Outgoing Webhookの設定とslackの設定に問題がなければ、5分毎に上記の温度データがconfig.inc.phpの「channel」で指定したSlackの通常チャンネルに自動投稿されます。

温度が指定した範囲から外れるとconfig.inc.phpの「channel_alert」で指定した緊急用のチャンネルに投稿します。

次回は温度計デバイスが停電などでオフラインになった場合に備える監視設定を行います。


(追記1) 2019-8-27

リクエストヘッダのキー文字列の大文字小文字が統一されていない事があるので、「x-sakura-signature」などを小文字に統一する以下の処理を追加

//---リクエストヘッダ配列のすべてのキーを小文字に変換
$header_ary = array_change_key_case($header_ary);

if($header_ary['x-sakura-signature'] === hash_hmac('sha1' , $json , SAKURAIO_SECRET)){
    //正しい署名(小文字)
}