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

SORACOMAdvent Calendar 2021

Day 18

LTE-M CO2センサー RS-LTECO2 スターターキットをセットアップして各種アラートを飛ばしてみる

Posted at

この記事は、SORACOM Advent Calendar 2021 の18日目の記事です。


今回はだいぶ前に買ったんですが、色々とやっているうち、セットアップしたものの、放置しっぱなしになっていた(汗)、
LTE-M CO2センサー RS-LTECO2 スターターキットを使って、室内監視をしてみた話になります。

LTE-M CO2センサー RS-LTECO2 スターターキットとは。

ラトックシステム株式会社が製造しているCO2センサーです。
愛用しているGPSマルチユニットとは違い、リファンレスデバイスではなく、
ラトックシステム株式会社の委託販売商品となっているようです。

LTE-M CO2センサー RS-LTECO2 スターターキット

LTE-M版はSORACOMのIoTストアなどでしか買えない製品ですが、
Wifi版があり、そちらは、Amazon.comなどでも購入可能です。
それがこちらです。ちょっとだけお安い。
RS-WFCO2
あと、
PM2.5計測や気圧も計測できる上位機種もあるみたいですね。
こっちもLTE-M版出ないかなと思ったりしてます。
RS-WFEVS1

搭載している各種センサー系は、
スイスに本社を置く、センシリオン社製のセンサーを搭載しているとのこと。
CO2に関しては、PA方式(光音響方式)で精度の高いCO2濃度計測が可能だそうです。
PA方式って何って方は、センサーを作っているセンシリオン社に、まとまっていたので、ご覧ください(読んでも理解できてない人です)
PASens®技術

センサーの仕様については、こんな感じです。
スクリーンショット 2021-12-18 14.04.12.png

セットアップしてみる

実際の手順に関しては、こちらに詳細があるので、割愛します。

SORACOM IoT レシピ:IoTで、CO2と温湿度を計測し換気促進
手順にも書いてありますが、
SIMを押し込むのがかなり難しかったですね。

SIMを入れて、電源を入れ、ユーザーコンソールで、SIMグループの設定(バイナリパーサー設定、Harverst設定)
あとはセンサーデータが蓄積されていきます。
あら簡単です。

Hervest & Lagoonで可視化

まず送られるデータですが、

  • CO2(co2)
  • 温度(temp)
  • 湿度(humid)
  • 送信間隔(interval)

の4つです。

送信間隔については、設定値なので、それ以外の値を可視化することになります。

Harvest、Lagoonで可視化すると、こんな感じです。

Harvestで可視化

2021/12/17の0:00〜23:59までのグラフになります。

  • CO2
    スクリーンショット 2021-12-18 22.09.41.png

(これ活動時間がバレバレだな・・・。)

  • 温度
    スクリーンショット 2021-12-18 22.09.50.png

  • 湿度
    スクリーンショット 2021-12-18 22.10.01.png

まとめて出すこともできますが、CO2の値が他の値に比べて大きいので、分けています。
※大きすぎるため、温度と湿度がほぼ平行になってしまう。

GPSマルチユニットとの比較

ここで、他製品と比べてみます。
同じように手軽に温湿度(あと位置情報)を可視化することができる便利なデバイスとして、
GPSマルチユニット SORACOM Edition
があります。
_20211218_222634.JPG

上がGPSマルチユニット SORACOM Edition
その下にあるのが、CO2センサーです。

GPSマルチユニットのセンサー仕様ですが、以下のようになっています。(赤枠内)
※製造元の京セラさんの製品仕様書からの抜粋

GPSマルチユニットセンサー仕様

温度に関しては、最大値は同じですが、最低値は
GPSマルチユニットは-20℃、CO2センサーは5℃となっています。
自分が使う分には問題なさそう(マイナス値になることはそんなにない)ですが、
マイナス値もちゃんと取得したい場合は、
ただ、精度に関しては、GPSマルチユニットは、
±2.0℃、(バッテリー駆動中)、±3.5℃(USB給電中)、CO2センサーの方が±0.2℃なので、精度に関しては、
CO2センサーのほうが良さそうです。

湿度に関しては、測定レンジ、精度含めて同じですね。実は同じセンサーなのかな?

Lagoon上で2つのデータを比べてみます。
CO2センサーは、アンテナ上の部分にセンサーがついている(あれアンテナだと思ってた人)ので、
上の写真の通り、大体同じような位置になる(無理があるかな)ので、近しい値になるかと思いますが・・・如何に。

今回は、先ほどと同じく2021/12/17ですが、GPSマルチユニットの稼働時間が、10:00〜20:00(最終送信は20:03頃)なので、
10:00〜20:59までにしています。

  • 温度

スクリーンショット 2021-12-18 22.14.15.png

薄い青がGPSマルチユニット、濃い青がCO2センサーの値になります。
概ねGPSマルチユニットは1度ほど高い値を示しているようです。
精度を考えると、大体同じになるのかな???

  • 湿度

スクリーンショット 2021-12-18 22.14.36.png

薄いオレンジがGPSマルチユニット、濃いオレンジがCO2センサーの値になります。
GPSマルチユニットが最大6%ぐらい高い値を示すという結果になりました。
無論、逆に低い時もあるので、この辺もセンサー精度の差かもしれませんね。

置き方を変えると数値が変わるとかあるのかなと思いますが、この辺は別途検証するかもしれません。

通知を飛ばす

今回ここがメインです。

このデバイスからのデータを使ってアラート発砲したい場合は、以下の3つになるかなと思います。

  • 熱中症警告(夏季向け)
  • 乾燥注意(冬季向け)
  • CO2濃度警報

上の2つはGPSマルチユニットと同じですね。

ここで1つ問題が発生しました。
上記した通り、
このデバイスは、電源が入っている限りは、一定間隔でデータを送信します。
※電源落とせばいい話ですが、それは人力なので一応避けます。
そして、現状、CO2濃度が高まるのは、夜間寝ている時間なのです。

正直寝ている時に通知がこられても困ります。
以前、このデバイスの勉強会があった際に、時間制御できないものかという質問もしましたが、
残念ながらできないという回答をいただきました。

なので、今回は、SORACOM FunkからAWS Lambdaを呼び出して、
Lambda内で計算および通知(送信制御含む)を行いたいと思います。(前置き長い)

なお、GPSマルチユニットを使う際はSORACOM Orbitを使って、
Orbit内で、温湿度の値を元に計算して、Lagoonから通知を行うようにしています。
詳細はGPSマルチユニットを使って、そろそろ冷房つけたらを通知するをご覧ください。

間隔を変えてみる

まず、現状5分の通知感覚を変えてみます(Lambdaの実行回数を減らそうかと・・・w)
方法は、SORACOM Airのメタデータサービスを有効にして、SIMのタグintervalというキーに、通知間隔値(分)をセットします。

  • SORACOM AirのメタデータサービスはSIMグループにて設定します。
    メタデータサービス

  • SIMのタグの設定で、名前に interval に、値に間隔値を設定します。 例では 15 にしてます。

タグ設定

AWS Lambdaの実装

SORACOM Funkから呼び出すAWS Lambda関数の実装自体はこんな感じです。(TypeScript)
突貫工事で作ったので、ちょっと微妙なところは直して、後日Githubにて公開します。
なお、最近多用しているAWS CDKのv2が最近出たので、それでデプロイしています。

処理として行っていることは、

  • Line Notify用のToken取得
  • 通知時間チェック
  • 熱中症警告チェック(WBGT値チェック)通知可否
  • 乾燥チェック(絶対湿度チェック)通知可否
  • WBGT値計算
  • 絶対湿度計算
  • LINEへのメッセージ生成
    • 通常メッセージ
    • CO2濃度警告通知
    • 熱中症警告通知
    • 乾燥警告通知
  • LINEにメッセージ送信

です。

通知可否のチェックとして、
そもそもの通知可否は時間、
熱中症警告チェック(WBGT値チェック)と乾燥チェック(絶対湿度チェック)は、月でチェックするようにしています。
それら期間はLambdaの環境変数に持たせて、制御できるようにしてます。ああ、昔より便利になったわ・・・(トオイメ)
月だけ、年跨ぎするようになってたりしていますが、他にいい実装あるかも。
CO2濃度、熱中症警告チェック(WBGT値チェック)と乾燥チェック(絶対湿度チェック)は、
閾値を、同じくLambdaの環境変数に持たせて、その閾値と比較して、出す文字列を変えています。

LINEへの通知部と通知で使うLINE Notify用のToken取得部は、
JAWS PANKRATION用に開発したLambda関数で使っているものの流用です。
https://github.com/Kenichiro-Wada/amazon-location-service-geofence-system

const moment = require('moment-timezone');
moment.tz.setDefault('Asia/Tokyo');
const AWSXRay = require('aws-xray-sdk');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
AWS.config.update({ region: 'ap-northeast-1' });
const ssm = new AWS.SSM();
const axios = require('axios');
const qs = require('querystring');
AWS.config.logger = console;

/**
* 定数
*/
const alertSendTimeRange: string = String(process.env.ALERT_TIME_RANGE);

const wbgtAlertSendMonthRange: string = String(
 process.env.WBGT_ALERT_MONTH_RANGE
);

const absHumidAlertSendMonthRange: string = String(
 process.env.ABS_HUMID_ALERT_MONTH_RANGE
);

const co2WarningLevel: number = Number(process.env.CO2_WARNING_LEVEL);
const co2AlertLevel: number = Number(process.env.CO2_ALERT_LEVEL);

const wbgtWarningLevel: number = Number(process.env.WBGT_WARNING_LEVEL);
const wbgtAlertLevel: number = Number(process.env.WBGT_ALERT_LEVEL);

const dryWarningLevel: number = Number(process.env.DRY_WARNING_LEVEL);
const dryAlertLevel: number = Number(process.env.DRY_ALERT_LEVEL);

exports.sendNotificationHandler = async function (event: any, context: any) {
 console.log('event:', JSON.stringify(event, null, 2));
 console.log('context:', JSON.stringify(context, null, 2));
 const lineNotifyToken = await getLineNotifyToken();

 const co2 = event.co2;
 const temp = event.temp;
 const humid = event.humid;

 const now: any = moment();

 // 通知可否チェック
 const isAlert = await checkAlertTime(now);
 const isAlertWbgt = await checkWbgtAlertMonth(now);
 const isAlertDry = await checkDryAlertMonth(now);

 let messageArray: string[] = [];
 messageArray.push(await createMessageSencorData(temp, humid, co2));
 if (isAlertWbgt) {
   const wbgt: number = await calcWbgt(temp, humid);
   console.log('wbgt:' + wbgt);
   messageArray.push(await createMessageWbgt(wbgt));
 }
 if (isAlertDry) {
   const absHumid: number = await calcAbsHumid(temp, humid);
   console.log('absHumid:' + absHumid);
   messageArray.push(await createMessageAbsHumid(absHumid));
 }
 messageArray.push(await createMessageCo2(co2));
 if (isAlert) {
   const message: string = messageArray.join('\n');
   const result: boolean = await sendNotifyMessage(lineNotifyToken, message);
   if (result) {
     return {
       statusCode: 200,
       body: JSON.stringify(
         {
           message: 'Line Notify Send Successful.',
         },
         null,
         2
       ),
     };
   } else {
     return {
       statusCode: 500,
       body: JSON.stringify(
         {
           message: 'Line Notify Send Error.',
         },
         null,
         2
       ),
     };
   }
 } else {
   return {
     statusCode: 200,
     body: JSON.stringify(
       {
         message: 'Nothing Alert.',
       },
       null,
       2
     ),
   };
 }
};
/**
* SSMからLINE Notify用トークン取得
* @returns トークン
*/
async function getLineNotifyToken(): Promise<string> {
 const ssmRequest = {
   Name: process.env.LINE_NOTIFY_TOKEN,
   WithDecryption: true,
 };
 const response: any = await ssm.getParameter(ssmRequest).promise();
 return String(response.Parameter.Value);
}
/**
* WBGT計算
* @param temp
* @param humid
* @returns wbgt値
*/
async function calcWbgt(temp: number, humid: number): Promise<number> {
 return Math.round(
   0.735 * temp + 0.0374 * humid + 0.00292 * temp * humid - 4.064
 );
}
/**
* 絶対湿度計算
* @param temp
* @param humid
* @returns 絶対湿度
*/
async function calcAbsHumid(temp: number, humid: number): Promise<number> {
 const ePow = (7.5 * temp) / (temp + 237.3);
 const e = 6.1078 * Math.pow(10, ePow); // 飽和水蒸気圧
 const a = (217 * e) / (temp + 273.15); // 飽和水蒸気量
 const rhP = humid / 100;
 const absHumid = a * rhP; // 絶対湿度
 return Math.round(absHumid);
}

/**
* 通知期間チェック
* @param now
* @returns
*/
async function checkAlertTime(now: any): Promise<boolean> {
 const hour: number = now.hour();

 const startTime: number = Number(alertSendTimeRange.substring(0, 2)); // 通知開始時
 const endTime: number = Number(alertSendTimeRange.substring(2)); // 通知終了時
 console.log(
   `現在時刻:${hour} 通知開始時間:${startTime} 通知終了時間:${endTime}`
 );
 if (startTime <= hour && hour <= endTime) {
   return true;
 } else {
   return false;
 }
}
/**
* WBGT値通知期間チェック(夏季)
* @param now
* @returns
*/
async function checkWbgtAlertMonth(now: any): Promise<boolean> {
 const month: number = now.month() + 1;

 const startMonth: number = Number(wbgtAlertSendMonthRange.substring(0, 2)); //通知開始月
 let endMonth: number = Number(wbgtAlertSendMonthRange.substring(2)); //通知終了月
 if (endMonth <= startMonth) { // 1003ってなった場合は12足してますが、こっちはいらないかも
   endMonth = endMonth + 12;
 }
 console.log(
   `現在月:${month} WBGT通知開始月:${startMonth} WBGT通知終了月:${endMonth}`
 );
 if (startMonth <= month && month <= endMonth) {
   return true;
 } else {
   return false;
 }
}
/**
* 絶対湿度通知期間チェック(冬季)
* @param now
* @returns
*/
async function checkDryAlertMonth(now: any): Promise<boolean> {
 const month: number = now.month() + 1;

 const startMonth: number = Number(
   absHumidAlertSendMonthRange.substring(0, 2)
 ); // 通知開始月
 let endMonth: number = Number(absHumidAlertSendMonthRange.substring(2)); // 通知終了月
 if (endMonth <= startMonth) { // こちらも1003ってなった場合は12足してます
   endMonth = endMonth + 12;
 }
 console.log(
   `現在月:${month} 絶対温度通知開始月:${startMonth} 絶対温度通知終了月:${endMonth}`
 );
 if (startMonth <= month && month <= endMonth) {
   return true;
 } else {
   return false;
 }
}
/**
* センサーデータ通知メッセージ
* @param temp
* @param humid
* @param co2
* @returns 通知メッセージ
*/
async function createMessageSencorData(
 temp: number,
 humid: number,
 co2: number
): Promise<string> {
 return `温度: ${temp} 湿度: ${humid} CO2濃度: ${co2}`;
}
/**
* CO2濃度警告通知メッセージ
* @param co2
* @returns 
*/
async function createMessageCo2(co2: number): Promise<string> {
 if (co2WarningLevel <= co2) {
   if (co2AlertLevel <= co2) {
     return `CO2濃度: ${co2} \n CO2濃度が危険なレベルになっています。換気などをして、適切な温度にしましょう!`;
   } else {
     return `CO2濃度: ${co2} \n CO2濃度が高くなっています。換気などをして、適切な濃度にしましょう!`;
   }
 }

 return '';
}
/**
* WBGT値通知メッセージ
* @param wbgt
* @returns 通知メッセージ
*/
async function createMessageWbgt(wbgt: number): Promise<string> {
 let levelStr: string = '適切な室温です。';
 if (wbgtWarningLevel < wbgt) {
   if (wbgtAlertLevel < wbgt) {
     levelStr =
       '熱中症の危険があります。冷房をつけるなどして、室温を適切に保ちましょう!';
   } else {
     levelStr =
       '熱中症に警戒しましょう。冷房をつけるなどして、室温を適切に保ちましょう!';
   }
 }

 return `WBGT値: ${wbgt} \n ${levelStr}`;
}
/**
* 絶対湿度通知メッセージ
* @param absHumid
* @returns
*/
async function createMessageAbsHumid(absHumid: number): Promise<string> {
 let levelStr: string = '適切な温度と湿度です。';
 if (absHumid < dryAlertLevel) {
   levelStr =
     '空気が乾燥し、ウィルスが蔓延しやすくなっています。換気したり、加湿器を使用するなどでして、温度と湿度を適切に保ちましょう!';
 } else {
   if (absHumid < dryWarningLevel) {
     levelStr =
       '少し乾燥しているようです。換気したり、加湿器を使用するなどでして、温度と湿度を適切に保ちましょう!';
   }
 }
 return `乾燥指数: ${absHumid} ${levelStr}`;
}
/**
* LINE Notify通知部
* @param lineNotifyToken
* @param message
* @returns
*/
async function sendNotifyMessage(
 lineNotifyToken: String,
 message: string
): Promise<boolean> {
 const lineNotifyUrl = 'https://notify-api.line.me/api/notify';
 // リクエスト設定
 const payload: { [key: string]: string } = {
   message: message,
 };

 console.log('payload:', JSON.stringify(payload, null, 2));
 const config = {
   url: lineNotifyUrl,
   method: 'post',
   headers: {
     'Content-Type': 'application/x-www-form-urlencoded',
     Authorization: `Bearer ${lineNotifyToken}`,
   },
   data: qs.stringify({
     message: message,
   }),
 };
 // メッセージ送信
 try {
   const result = await axios.request(config);
   console.log(result);
   if (result.data.message === 'ok') {
     return true;
   } else {
     return false;
   }
 } catch (error) {
   console.error(error);
   return false;
 }
}

実際動かしてみると、こんな感じで通知がきます。(めっちゃテストデータです。)
_20211218_230750.JPG

うーん、もうちょっと綺麗にしたい。

終わり

CO2センサーを設定し、Lagoonで可視化、Funk経由でLine Notifyまで通知するところまでやってみました。
無論、計算をSORACOM Orbitでやり、Lagoonでアラート送信をすることもできますので、
そっちの方が手軽かなという気もしますが、
変な時間に警告くるのは避けられない気がします。(と思っているけど、手段あるかも)

ただ、今のままだと、休日だろう(現に来てる)と、15分起きにLINEから通知くるので、ここまで要らんかなといきなり思っているので、
本当にやばい時だけ通知するに改修しようかなと思います。
こちら、ちょっと来年早々にも改修して、再度アップデートします。


明日は、ソラコム片山先生の2年ぶりの新作「Sim City 2021」です。期待しかないです!

6
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
6
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?