M5StickCからBLEで送信された温湿度をラズパイでグラフ化してLINEで表示 | そう備忘録

M5StickCからBLEで送信された温湿度をラズパイでグラフ化してLINEで表示

グラフ化してLINEで表示

以前にM5StickCに接続した温湿度気圧センサー(BME280)で測定した情報をBLE(Bluetooth Low Energy)でラズパイに通信する記事を書いた

当時の構成図は以下の通り。

BLEでアドバタイジング全体構成図

この時はラズパイのコンソールに受け取った温湿度気圧を表示しただけだったが、今回はデータを溜め込んでグラフ化してLINEに表示するまでのプログラムを作成してみた。

全体構成図

全体構成図は以下の通り。

M5StickCはWi-Fiや電源が確保できない場所に設置する事も可能な一方、RaspberryPiはWi-Fi、電源を確保できる場所に設置するイメージで構成した。

温湿度、気圧、電圧の推移をグラフ化してラズパイ経由でLINEに送信
  • M5StickCのHY2.0-4P端子に温湿度・気圧測定センサー(BME280)を取り付ける
  • センサーで取得した値をBLEで10秒間アドバタイジング(ブロードキャスト)する
  • 数十メートル離れた場所でラズパイ4にてBLEで信号をスキャンして受信した情報をローカルのCSVファイルに蓄える
  • 蓄えたデータが一定量になったらmatpoltlibでグラフ化してLINE Notifyで表示する

尚、M5StickCのBluetoothのバージョンはBluetooth 4.2なので見通しの良い場所であれば到達距離が約100mである。

今回30m程の距離でテストをしたが問題無く通信が出来た。

必要な機器

  • M5StickC
  • Raspberry Pi 4 Model B
  • 温湿度、気圧センサー(BME280)
  • Grove汎用ケーブル
  • ジャンパーワイヤーセット

を用意した。

詳細については以前の「M5StickCとラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事の必要な機器の章を参照して欲しい。

2021年11月 追記

M5StickC は下記の M5StickC Plus に置き換わっている。

スイッチサイエンスでは M5StickC(本体のみ)がまだ販売されていた。

使用した工具

  • はんだごて
  • はんだ
  • スタンドルーペ
  • 電工ペンチ

を用意した。

詳細については「M5StickCラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事の使用した工具の章を参照して欲しい。

組み立て

  • BME280のはんだ付け
  • コネクタの作成

を行った。

詳細については「M5StickCラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事の組み立ての章を参照して欲しい。

配線図

M5StickCとBME280のI2C接続の配線図は以下の通り。

M5StickCとBME280との配線

購入したGrove汎用ケーブルの黄色と白の色が逆転している(SDAを黄色、SCLを白にしたかった)がまぁ良しとする。

環境構築

M5StickCとRaspberryPi 4に対して事前の環境構築を行う。

M5StickC側

最初にM5StickC側の環境構築。

プログラムの開発環境であるWindows10マシンに、

  • M5StickCの開発環境のArduino IDEのインストール
  • Arduino IDEに必要なライブラリー(BME280関連)のインストール

を行った。

Windows10マシンへのArduino IDE(統合開発環境)のインストール方法は以前の「Arduino IDEで簡単なプログラム(C++)を作成してM5StickCで実行してみた」の記事を参照して欲しい。

またArduino IDEへのBME280関連のライブラリーのインストールについては「M5StickCラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事の環境構築の章を参照して欲しい。

ラズパイ側

続いてRaspberryPi 4側の事前の環境構築を行う。

LINE Notifyでアクセストークンの発行

LINEにグラフを送信するためにLINE Notifyにログインをしてアクセストークンを発行する。

LINE Notify

LINEアプリに設定したメールアドレスとパスワードでログインをする。

LINEアプリに設定したメールアドレスとパスワードでログインをする

右上のメニューから「マイページ」を選択する。

マイページを選択する

アクセストークンの発行(開発者向け)の「トークンを発行する」をクリックする。

トークンを発行する
  • トークン名:温湿度・気圧・電圧
  • 1:1でLINE Notifyから通知を受け取る

を選択して「発行する」ボタンをクリックする。

とーくn

トークンが発行されるのでコピーをしたら閉じる。

トークンは後ほど、プログラムで使用するので保存しておく。

トークンが発行されるのでコピーする

連携中サービとして表示される。

連携中サービとして一覧に表示される

bluepyのインストール

  • ラズパイにBluetoothデバイスを操作するためのモジュール(bluepy)のインストール

を行った。

sudo pip3 install bluepy

詳細については「M5StickCラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事の環境構築(ラズパイ)の章を参照して欲しい。

pandasのインストール

情報を蓄積する為のCSVの操作の為にpandasライブラリーを使用している。

pandasはデータ分析用のライブラリなのでcsv操作のみで使用するのはオーバースペック気味なのだが、扱いやすいのと今後プログラムを改修してより複雑な操作をすることもあるかと思い pandas を選択した。

下のコマンドでインストールを行う。

sudo apt-get install python3-pandas
pandasのインストール

日本語フォントのインストール

RaspberryPiに日本語フォントのインストールを行った。

ラズパイでの温湿度等のグラフ化にはmatplotlibを使用したのだが標準では日本語が表示できない。

グラフのタイトルや縦横軸の説明に日本語を使用したかったので、

  • IPA日本語フォントをインストール
  • matplotlibrcの設定ファイル(/etc/matplotlibrc)を編集して日本語IPAフォントを指定

を行った。

sudo apt install fonts-ipaexfont

/etc/matplotlibrc をエディターで編集して font.family と font.sans-serif に IPAexGothic を指定した(下記画面ショット参照)

詳細については「ラズパイに日本語フォントをインストールしてmatplotlibで日本語表示させた件」の記事を参照して欲しい。

IPAゴシックフォントの指定

プログラム

M5StickC側のプログラム(スケッチ)とラズパイ側のプログラムの両方がある。

M5StickC

今回、M5StickC側のプログラム(c++)は以前に作成したものと違いが無い。

  • BME280から温度、湿度、気圧の情報を取得する
  • BLE(Bluetooth Low Energy)で10秒間アドバタイジング(ブロードキャスト)する
  • その他の時間はDeep Sleepする

の処理を行っている。

詳細については「M5StickCラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事のプログラムの章を参照して欲しい。

ソースコード

ble_pub.ino

#include <M5StickC.h>
#include <BLEDevice.h> // Bluetooth Low Energy 
#include <BLEServer.h> // Bluetooth Low Energy
#include <BLEUtils.h> // Bluetooth Low Energy
#include <esp_sleep.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define T_PERIOD 10 // アドバタイジングパケットを送る秒数
#define S_PERIOD 290 // Deep Sleepする秒数

RTC_DATA_ATTR static uint8_t seq; // 送信SEQ
Adafruit_BME280 bme;

uint16_t temp; // 温度
uint16_t humid; // 湿度
uint16_t press; // 気圧
uint16_t vbat; // 電圧

void setAdvData(BLEAdvertising *pAdvertising) { // アドバタイジングパケットを整形する
    BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();

    oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | General Discoverable Mode
 
    std::string strServiceData = "";
    strServiceData += (char)0x0c; // 長さ(12Byte)
    strServiceData += (char)0xff; // AD Type 0xFF: Manufacturer specific data
    strServiceData += (char)0xff; // Test manufacture ID low byte
    strServiceData += (char)0xff; // Test manufacture ID high byte
    strServiceData += (char)seq; // シーケンス番号
    strServiceData += (char)(temp & 0xff); // 温度の下位バイト
    strServiceData += (char)((temp >> 8) & 0xff); // 温度の上位バイト
    strServiceData += (char)(humid & 0xff);  // 湿度の下位バイト
    strServiceData += (char)((humid >> 8) & 0xff); // 湿度の上位バイト
    strServiceData += (char)(press & 0xff); // 気圧の下位バイト
    strServiceData += (char)((press >> 8) & 0xff); // 気圧の上位バイト
    strServiceData += (char)(vbat & 0xff); // 電池電圧の下位バイト
    strServiceData += (char)((vbat >> 8) & 0xff); // 電池電圧の上位バイト

    oAdvertisementData.addData(strServiceData);
    pAdvertising->setAdvertisementData(oAdvertisementData);
}

void setup() {
    M5.begin();
    setCpuFrequencyMhz(80); // CPU周波数80以上にしないと無線は使用できない
    M5.Axp.begin(false,false,false,false,true); // 省電力の為、DCDC3をオフ
    M5.Axp.ScreenBreath(7); // 画面の輝度を下げる

    Wire.begin(); // I2Cの初期化
    while (!bme.begin(0x76)) { // BME280の初期化
        break;
    }
 
    temp = (uint16_t)(bme.readTemperature() * 100); // 温度の取得(100倍して小数点以下を整数部へ)
    humid = (uint16_t)(bme.readHumidity() * 100); // 湿度の取得(100倍して小数点以下を整数部へ)
    press = (uint16_t)(bme.readPressure()/100); // 気圧の取得(pa→hPaに変換」)
    vbat = (uint16_t)(M5.Axp.GetVbatData() * 1.1 / 1000 * 100); // バッテリーの電圧

    BLEDevice::init("blepub-01"); // デバイスを初期化
    BLEServer *pServer = BLEDevice::createServer();  // サーバーを生成

    BLEAdvertising *pAdvertising = pServer->getAdvertising(); // アドバタイズオブジェクトを取得
    setAdvData(pAdvertising); // アドバタイジングデーターをセット

    pAdvertising->start(); // アドバタイズ起動
    delay(T_PERIOD * 1000); // T_PERIOD秒アドバタイズする
    pAdvertising->stop(); // アドバタイズ停止

    seq++; // シーケンス番号を更新
    delay(10);
    esp_deep_sleep(1000000LL * S_PERIOD); // S_PERIOD秒Deep Sleepする
}

void loop() {
}

ラズパイ4

データを受け取ってLINEに表示するRaspberry Pi 4Bのプログラム(Python)について説明する。

ディレクトリ構造

プログラムは /opt/ 配下に temp-sns というディレクトリを作成して格納した。

ubuntu の説明では /opt は主にサードパーティ製のプログラムや自作のプログラムを格納する場所とあったのでこのディレクトリにしている。

2021年04月29日 追記

今回は /opt/ 配下に格納したが、特定のユーザからしか起動しないプログラムなので /usr/local/ への格納でも良かったのかも知れない。

├─opt
│  │      
│  ├─temp-sns
│  │  │  temp_humi_sns.py
│  │  │  initial.json
│  │  │
│  │  ├──backup
│  │  │    envYYYY-MM-DD-HH-mm-SS.csv
│  │  │        ・
│  │  │        ・
│  │  │
│  │  ├──cert
│  │  │    tempsns_cert.json
│  │  │
│  │  ├──csv
│  │  │    env.csv
│  │  │
│  │  ├──images
│  │  │    pres_volt.png
│  │  │    temp_humi.png
│  │  │
│  │  └──log
│  │       error.log
│  │
temp-sns

temp_humi_sns.py

プログラム本体(ソースコードは後述

initial.json

初期設定ファイル

{
	"env_log_csv": "./csv/env.csv",
	"backup_drive": "./backup/",
	"cert_file":"./cert/tempsns_cert.json",
	"temp_humi_img":"./images/temp_humi.png",
	"pres_volt_img":"./images/pres_volt.png",
	"max_line_count":24
}

env_log_csv

M5StickCからの温湿度、気圧、電圧等の情報を一時的に蓄えるためのCSV。

情報が一定量(下記max_line_countで指定した行数)蓄積されたらグラフ化してLINEに送信してbackup_driveにファイルをバックアップしてから削除される。

backup_drive

env_log_csvの過去データのバックアップ場所。

cert_file

LINEのアクセストークンなどの認証情報を記録したファイルの保存場所。

temp_humi_img

温湿度のグラフ画像ファイルの一時保存場所

pres_volt_img

気圧・電圧のグラフ画像ファイルの一時保存場所

max_line_count

env_log_csvにこの行数格納されたらLINEにメッセージを送信する。

例)

M5StickCにて5分毎の測定を行った場合、24を設定すると5分×24=120分(2時間)分のグラフが送信されることになる。

backup

測定した温湿度、気圧・電圧の情報をLINE送信後にバックアップの意味合いでこのディレクトリに保存する。

ファイル名はenvYYYY-MM-DD-HH-mm-SS.csvの形式で保存する。

cert

認証情報が保存されているディレクトリ。

line_token

LINE Notifyでアクセストークンの発行」の章で発行したLINEのアクセストークン

csv

温湿度、気圧・電圧の情報をLINEに送信する前にローカルディスクに一時的保存するファイルを格納するディレクトリ。

env.csvのファイル名で保存している。

log

各種ログを保存する為のディレクトリ。

Pythonのプログラム中で何らかの例外が発生した時にerror.logのファイル名でエラーメッセージ等を保存している。

プログラムの異常終了時にエラーの原因を究明する為に記録している。

ソースコード

temp_humi_sns.py

# -*- coding: utf-8 -*-
"""
Created on Sun Feb  7 12:55:50 2021
・M5StickCからBLEで受信した温度、湿度、気圧、電圧の情報をcsvに保存する
・一定量(max_line_count)超えたらmatplotlibでグラフ化してLINE NotifyでLINEに送信する

@author: Souichirou Kikuchi
"""

from bluepy.btle import DefaultDelegate, Scanner, BTLEException # BLE関係
import sys
import csv # CSVファイル
import json # JSONファイル
import struct # Packされたバイト列を操作
import os
import shutil # ファイル操作
from datetime import datetime as dt
import matplotlib.pyplot as plt # グラフ作成用
import pandas as pd # CSV用
from concurrent.futures import ThreadPoolExecutor # スレッド処理
import requests # LINEメッセージ


ERROR_LOG  = './log/error.log' # エラーログ
INITIAL_FILE = './initial.json' # 初期設定ファイル
LINE_URL = 'https://notify-api.line.me/api/notify' # LINEメッセージ用URL

class ScanDelegate(DefaultDelegate): # BLEのScanクラス
    def __init__(self): # コンストラクタ
        with open(INITIAL_FILE) as f: # 初期設定ファイルの読み込み
            __jsn = json.load(f)
            self.env_log_csv = __jsn['env_log_csv'] # 温湿度・気圧・電圧情報保存CSV
            self.backup_drive = __jsn['backup_drive'] # バックアップ保存用ドライブ
            self.cert_file = __jsn['cert_file'] # 認証情報格納場所
            self.temp_humi_img = __jsn['temp_humi_img'] # 温湿度グラフ画像の保存場所
            self.pres_volt_img = __jsn['pres_volt_img'] # 気圧、電圧グラフ画像の保存場所
            self.max_line_count = __jsn['max_line_count'] # 最大行数(この行数を超えたらグラフを作成してLINEに通知)
        f.close()
        DefaultDelegate.__init__(self)
        self.lastseq = None
        self.lasttime = dt.fromtimestamp(0)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        try:
            if isNewDev or isNewData: # 新しいデバイスまたは新しいデータ
                for (adtype, desc, value) in dev.getScanData(): # データの数だけ繰り返す
                    if desc == 'Manufacturer' and value[0:4] == 'ffff': # テスト用companyID
                        __delta = dt.now() - self.lasttime
                        # アドバタイズする10秒の間に複数回測定されseqが加算されたものは捨てる(最初に取得された1個のみを使用する)
                        if value[4:6] != self.lastseq and __delta.total_seconds() > 11:
                            self.lastseq = value[4:6] # Seqと時刻を保存
                            self.lasttime = dt.now()
                            (temp, humid, press, volt) = struct.unpack('<hhhh', bytes.fromhex(value[6:])) # hは2Byte整数(4つ取り出す)
                            # print('温度= {0} 度、 湿度= {1} %、 気圧 = {2} hPa、 電圧 = {3} V'.format( temp / 100, humid / 100, press, volt/100))
                            if (os.path.isfile(self.env_log_csv)): # ファイルが存在しているとき
                                __df = pd.read_csv(self.env_log_csv, header=0, encoding='UTF-8') # 読み込み 0行目がヘッダー(有り)
                            else: # ファイルが無ければヘッダーを作成
                                __df = pd.DataFrame(columns=['datetime', 'temp', 'humi', 'press', 'volt'])
                            __blelog = pd.Series( ['{0:%Y-%m-%d %H:%M:%S.%f}'.format(dt.now()), temp / 100, humid / 100, press, volt/100], index=__df.columns)
                            __df = __df.append(__blelog, ignore_index=True ) # dataframeを作成して行追加、ignore_indexで新たな行番号を振っている
                            __df.to_csv(self.env_log_csv, index=False) # Log書き込み indexは書き込まない
                            if len(__df) >= self.max_line_count: # この行数を超えたらLINEに通知
                                __executor = ThreadPoolExecutor(max_workers=5) # 同時実行は5つまでスレッド実行
                                __executor.submit(self.on_theread()) # 別スレッドで実行する
                                
        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    def on_theread(self): # グラフを作成してLINEに送信は時間がかかるので別スレッドで行う
        try:
            self.drawing_graph() # グラフを作成&ローカルディスク保存
            self.line_message() # グラフをLINE送信
    
            __file_time_stamp =  '{0:%Y-%m-%d-%H-%M-%S}'.format(dt.now()) # 日付時刻をセット
            __backup_file_path = self.backup_drive + 'env' + __file_time_stamp + '.csv' # ディレクトリ、ファイル名をセット
            shutil.move(self.env_log_csv, __backup_file_path) # ファイルをバックアップディレクトリに移動
        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    def drawing_graph(self): # グラフ表示後、ローカルディスクに保存
        try:
            dttm = [] # 日付初期化
            temp = [] # 温度初期化
            humi = [] # 湿度初期化
            pres = [] # 気圧初期化
            volt = [] # 電圧初期化
            fp = open(self.env_log_csv, 'r') # CSVファイルの読み込み
            reader = csv.reader(fp)
            next(reader) # Headerスキップ
            for row in reader: # 行数分だけ繰り返して配列の後ろからセットしてゆく
                dttm.append(row[0][0:19]) # 日付時刻を後ろに追加
                temp.append(float(row[1])) # 温度を後ろに追加
                humi.append(float(row[2])) # 湿度を後ろに追加
                pres.append(int(row[3])) # 気圧を後ろに追加
                volt.append(float(row[4])) # 電圧を後ろに追加
            fp.close()
        
            fig1 = plt.figure() # fig:温湿度のグラフを描画する領域
            ax1 = fig1.add_subplot(1, 1, 1) # 1行、1列、1場所に追加
            ax2 = ax1.twinx() # X軸を共有
            
            ax1.set_ylim([0, 100]) # 湿度の表示範囲(0~100%)
            ax1.set_ylabel('湿度') # Y軸のラベル
            ax1.grid(axis='y') # グリッド横表示
            ax1.set_xticklabels(dttm, rotation=70, ha='right') # X軸のラベルを傾ける
            ax1.bar(dttm, humi, color='b', label='湿度') # Blue
        
            ax2.set_ylim([-45, 85]) # 温度の表示範囲(-45~85度)
            ax2.set_ylabel('温度') # Y軸のラベル
            ax2.plot(dttm, temp, color='r', marker='o', label='温度') # Red circle marker
        
            h1, l1 = ax1.get_legend_handles_labels() # ラベルを取得
            h2, l2 = ax2.get_legend_handles_labels()
            ax1.legend(h1+h2, l1+l2, loc='upper left') # 凡例を表示
        
            plt.title('湿度・温度の推移') # グラフタイトル
            plt.savefig(self.temp_humi_img, bbox_inches='tight') # 温湿度のファイルを書き出し
    
            fig2 = plt.figure() # fig:気圧と電圧のグラフを描画する領域
            ax21 = fig2.add_subplot(1, 1, 1) # 1行、1列、1場所に追加
            ax22 = ax21.twinx() # X軸を共有
            
            ax21.set_ylim([0, 10]) # 電圧の表示範囲(0~100%)
            ax21.set_ylabel('電圧') # Y軸のラベル
            ax21.grid(axis='y') # グリッド横表示
            ax21.set_xticklabels(dttm, rotation=70, ha='right') # X軸のラベルを傾ける
            ax21.bar(dttm, volt, color='r', label='電圧') # Red
        
            ax22.set_ylim([0, 1200]) # 気圧の表示範囲(-45~85度)
            ax22.set_ylabel('気圧') # Y軸のラベル
            ax22.plot(dttm, pres, color='b', marker='o', label='気圧') # Red circle marker
        
            h1, l1 = ax21.get_legend_handles_labels() # ラベルを取得
            h2, l2 = ax22.get_legend_handles_labels()
            ax21.legend(h1+h2, l1+l2, loc='upper left') # 凡例を表示
        
            plt.title('電圧・気圧の推移') # グラフタイトル
            plt.savefig(self.pres_volt_img, bbox_inches='tight') # 気圧と電圧のファイルを書き出し
        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    def line_message(self): # グラフをLINE Notifyから送信
        try:
            with open(self.cert_file) as f: # 認証情報ファイルの読み込み
                __jsn = json.load(f)
                line_token = __jsn['line_token'] # LINE Notifyのアクセストークン
            f.close()
            __headers = {'Authorization' : 'Bearer ' + line_token}
            __message = '温度・湿度'
            payload = {'message' : __message}
            __files = {'imageFile': open(self.temp_humi_img, 'rb')} # 画像ファイル
            requests.post(LINE_URL, headers=__headers, params=payload, files=__files)
            __message = '気圧・電圧'
            payload = {'message' : __message}
            __files = {'imageFile': open(self.pres_volt_img, 'rb')} # 画像ファイル
            requests.post(LINE_URL, headers=__headers, params=payload, files=__files)

        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    def put_error_log(self, message): # エラーログファイルを出力する
        if (os.path.isfile(ERROR_LOG)): # ファイルが存在しているとき
            __df = pd.read_csv(ERROR_LOG, header=0, encoding='UTF-8') # 読み込み 0行目がヘッダー(有り)
        else: # ファイルが無ければヘッダーを作成
            __df = pd.DataFrame(columns=['ErrorData', 'ErrorMessage'])
        __errorlog = pd.Series( ['{0:%Y-%m-%d %H:%M:%S.%f}'.format(dt.now()), message], index=__df.columns)
        __df = __df.append(__errorlog, ignore_index=True ) # dataframeを作成して行追加、ignore_indexで新たな行番号を振っている
        __df.to_csv(ERROR_LOG, index=False) # Log書き込み indexは書き込まない

if __name__ == '__main__':
    scanner = Scanner().withDelegate(ScanDelegate())
    while True:
        try:
            scanner.scan(5.0) # スキャンする。デバイスを見つけた後の処理はScanDelegateに任せる
        except BTLEException:
            ex, ms, tb = sys.exc_info()
            print('BLE exception '+str(type(ms)) + ' at ' + sys._getframe().f_code.co_name)

補足説明

細かい部分はソースコード中のコメントを確認してほしい。

以前の「M5StickCとラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の記事のプログラムとの相違点を主に記述する。

init

初期設定ファイル(initial.json)ファイルから各種設定情報を読み込む。

handleDiscoveryにてスレッド

./csv/env.csv の行数が max_line_count を超えた時にスレッド処理を呼び出している。

蓄積データのグラフ化とLINE Notifyによる送信は時間がかかる可能性があるのでM5StickCからのデータを取りこぼす事の無いように別スレッドにて処理する。

on_theread

グラフ作成関数とLINE送信関数を呼び出した後でファイルをバックアップする。

drawing_graph

CSVデータを元に matplotlib でグラフを作成する。

グラフは温度・湿度のグラフと気圧・電圧の2種類のグラフを作成してそれぞれpngファイルにして保存する。

また一つのグラフで左右で2軸(温度と湿度)を表示する為に add_subplot で軸を追加している。

温度・湿度グラフの表示例
電圧・気圧グラフの表示例
line_message

LINEのアクセストークンを使用してLINE Notifyからメッセージを送信する。

プログラムの実行

M5StickCのプログラムは電源ボタンの長押しの電源オンと同時に自動的に開始され、およそ5分間隔で温湿度、気圧、電圧の情報をアドバタイジング(ブロードキャスト)する。

Raspberry Pi 4BのプログラムはXLTerminalから以下のコマンドで実行する。

注意点としてはBluetoothでデバイスにアクセスする為にはroot権限が必要なのでsudoでプログラムを起動する必要がある。

sudo python temp_humi_sns.py

実行結果

M5StickCの電源をオンにするとアドバタイジングが始まり温湿度・気圧センサー(BME280)から取得した値をBLE(Bluetooth Low Enegy)でRaspberryPiに送信する。

Raspberry Piではデータがある一定量蓄積されたところで matplotlib でグラフして後、LINE NotifyでLINEに送信する。

プログラムの実行結果

最後に

以上で今回の記事は終了とする。

matplotlibを使えば比較的簡単にグラフ化できる事とLINE Notifyでテキストや画像をLINEに送信できることが確認できた。

また今回はラズパイから直接携帯端末へ情報をグラフ化して表示させたがAWSなどのクラウドにでデータをアップロードしてグラフ化(やデータベース化)する事も可能だ。

クラウドへのデータ送信については以前の「ラズパイゼロで温湿度と気圧、空気の汚れを検出してグラフ化するIoT機器」の記事を参照して欲しい。

最後に

この記事が何処かで誰かの役に立つことを願っている。

尚、当記事中の商品へのリンクはAmazonアソシエイトへのリンクが含まれています。Amazonのアソシエイトとして、当メディアは適格販売により収入を得ていますのでご了承ください。

souichirou

やった事を忘れない為の備忘録 同じような事をやりたい人の参考になればと思ってブログにしてます。 主にレゴ、AWS(Amazon Web Services)、WordPress、Deep Learning、RaspberryPiに関するブログを書いています。 仕事では工場に協働ロボットの導入や中小企業へのAI/IoT導入のアドバイザーをやっています。 2019年7月にJDLA(一般社団法人 日本デイープラーニング協会)Deep Learning for GENERALに合格しました。 質問は記事一番下にあるコメントかメニュー上部の問い合わせからお願いします。

おすすめ

質問やコメントや励ましの言葉などを残す

名前、メール、サイト欄は任意です。
またメールアドレスは公開されません。