M5Stackの光センサーでパトライトの点灯を検知してラズパイに送信してみた | そう備忘録

M5Stackの光センサーでパトライトの点灯を検知してラズパイに送信してみた

M5Stackの光センサー

M5Stack社の光センサーでパトライトの点灯を検知してラズパイに BLE(Bluetooth Low Energy)で点灯情報を送るプログラムを作成してみた。

ラズパイ側では受信した情報を元にどこのパトライトが点灯したのかをコンソールに表示する。

尚、BLEは4.2なので、1Mbps の通信速度で見通しの良い所では最大 100m 飛ぶ規格だ。

ラズパイ側のWi-Fiは使用しないので、この構成であれば LAN(有線、無線)の設置されていない場所でも利用できるので活用範囲が広がる。

M5Stackの光センサーとATOM LiteとRaspberry Pi 4BでBLEで通信

全体構成図

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

光センサーでパトライトの点灯を検知の全体構成図
  • M5Stack用の光センサーのフォトレジスター部でパトライトの点灯を検知する
  • 検知結果(デジタル、アナログ)を M5Stack ATOM Lite で受け取る
  • 検知情報を BLE(Bluetooth Low Energy)で10秒間アドバタイズ(ブロードキャスト)する
  • Raspberry Pi 4B でBLE を受信してコンソールに表示する

用意した機器・部品・工具

M5Stack用光センサーユニット

フォトレジスター部で光を検知してデジタルとアナログの両方の信号を出力する。

デジタル値は点灯(0)または消灯(1)を表し、しきい値は可変抵抗で調整をすることが出来る。

アナログ値は光の強さを(明)0~4095(暗)の数値で表す。

また Grove互換端子で M5Stack シリーズに接続が可能。

M5Stack ATOM Lite

今回は M5Stack ATOM Lite で光センサーユニットと接続した。

ATOM Lite は M5Stackシリーズの中では最小のサイズで価格も現時点(2021年5月)では一番安価になっている。

尚、ATOM Lite はスイッチサイエンスで購入した。

M5Stick-C等と比較して電池を内蔵していないので電源を確保(USB-Type-Cで供給)する必要がある。

  • サイズ:24×24mm
  • チップ:ESP32-PICO-D4
  • Wi-Fi、Bluetooth 4.2
  • 4MBフラッシュメモリ
  • RGB LED
  • 赤外線LED
  • ボタン×2
  • GPIOピン×6
  • Grove互換インターフェース
  • 電源入力:5V/500mA(USB Type-C)
M5Stack ATOM Lite 裏側

ピン

背面には向かって左側に5ピン、右側に4ピン、中央にGrove互換端子が並んでいる。

左側

上から、

  • 3V3:Out
  • G22:In/Out
  • G19:In/Out
  • G23:In/Out
  • G33:In/Out(アナログ)
右側

上から、

  • G21:In/Out
  • G25:In/Out
  • 5V:Out
  • GND

Grove互換端子

左から、

  • GND
  • 5V:Out
  • G26:In/Out
  • G32:In/Out(アナログ)

前面

前面には電源供給用のUSB Type-Cのコネクタと中央にボタンAと LED、左側にリセットボタンがある。

M5Stack ATOM Lite 表側

尚、今回は ATOM Lite に接続したがその他の M5Stack シリーズや M5Stick-C と接続することも可能だ。

自分は手持ちのM5Stick-Cにも接続をしたが問題なく動作した。

2021年11月 追記

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

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

Raspberry Pi

ATOM Lite からの BLE信号を受信して情報をコンソールに表示する為に Raspberry Pi 4B を用意した。

尚、Raspberry Pi 4B の Bluetooth は Ver5.0(125kbpsで距離が400mまで) に対応しているが ATOM Lite 側のBluetooth のバージョンが4.2の為、4.2ベースでの通信となる。

配線図

簡単な配線図を以下に示しておく。

光センサーとM5Stack ATOM Liteとの配線

プログラム

光センサーの値を取得して BLE でアドバタイジングする ATOM Lite 側の開発環境は Windows 10 に Arduino IDEをインストールして、言語は C++ で作成した。

一方、データを受信してコンソールに表示する Raspberry Pi 側は Python で作成している。

ATOM Lite側

環境構築

Arduino IDE のダウンロードとインストールの詳細は以前の記事を参照して欲しい。

  • Arduino IDE ダウンロード
  • インストール
  • 追加ソフトウェアのインストール
  • ボードマネージャーの追加

を行っている。

ライブラリーのインストール

今回使用する M5Atom 用のライブラリーをインストールする。

Arduino IDE のメニューからスケッチー>ライブラリーをインクルードー>ライブラリーを管理から検索欄で “M5Atom” を検索して表示されるライブラリーをインストールする。

現時点(2021年5月)で最新の0.0.2をインストールした。

M5Atomライブラリーインストール

ツールの設定

Arduino IDE のメニューのツールの設定は以下の通り。

  • ボード:ESP32 Arduino→M5Stack-ATOM を選択する(詳細は後述
  • Upload Speed:1500000(通信エラーが出るような速度を落とす)
  • Partition Schema:初期値
  • Core Debug Level:なし
  • シリアルポート:COMn(nは数値、詳細は後述

を指定する。

ツールの設定一覧
ボードの管理

Arduino IDEのメニューからツールー>ボードー>ESP32 Arduinoで一覧に M5Stack-ATOM が表示されない場合はボードマネージャーから esp32 のアップデートを行う。

ツールー>ボードー>ボードマネージャーを選択する。

タイプで “アップデート可能” を選択するとアップデート可能なボードだけに絞り込まれる。

esp32のバージョン1.0.4では M5Stack-ATOM が一覧に表示されなかったので「バージョンを選択」欄を最新の(2021年5月現在)1.0.6に変更して「更新」ボタンを押す。

ボード esp32 のバージョンを1.0.6にアップデートすると一覧に M5Stack-ATOM の他にも M5Stack-Core2 などが表示されるようになった。

COMポートの確認

USBケーブルでパソコン(Type-A)とATOM Lite(Type-C)を接続する。

シリアル接続された機器として自動的にCOMポートが割り当てられるので Arduino IDE のメニューから指定する。

シリアル接続している機器がひとつの場合は Arduino IDE のメニューからツールー>シリアルポートを選択すると、COMポートが一つだけ表示されるのでそれを選択する。

複数表示される時のCOMポートの確認方法(Windows 10の場合)は以下の通り。

Windowsのスタートメニューで右クリックー>デバイスマネージャを選択する。

デバイスマネージャが表示されるのでポート(COMとLPT)を表示させながらUSBケーブルを抜き差しすれば新しく追加表示されるCOMポートが ATOM Lite のCOMポートと分かる。

デバイスマネージャーでOCMポートの確認

ソースコード

optical-sensor-atom.ino

/**
 *  Created on 2021-04-27
 *  
 *  光センサーユニットでパトライトの点灯を検知する
 *  検知したらLEDを赤に変更後、一定秒数アドバタイジング(送信)
 * 
 *  @author: Souichirou Kikuchi
 */
#include "M5Atom.h"
#include <BLEDevice.h> // Bluetooth Low Energy 
#include <BLEServer.h> // Bluetooth Low Energy
#include <BLEUtils.h> // Bluetooth Low Energy

#define T_ID 3101 // 端末IDを示す4桁の数字
#define T_PERIOD 10 // アドバタイジングパケットを送る秒数
#define A_PIN 32 // アナログピン番号
#define D_PIN 26 // デジタルピン番号
#define LED_GREEN 0xf00000 // 緑LED
#define LED_RED 0x00f000 // 赤LED
#define LED_BLUE 0x0000f0 // 青LED
#define LED_WHITE 0x707070 // 白LED

RTC_DATA_ATTR static uint8_t seq; // 送信SEQ

uint16_t analogRead_value = 0; // アナログ値(小:明るい、大:暗い)0~4095
uint16_t digitalRead_value = 0; // デジタル値(0:明るい、1:暗い)可変抵抗でしきい値は変更可能
uint16_t terminalid = T_ID; // 端末IDをセット

void setAdvData(BLEAdvertising *pAdvertising) { // アドバタイジングパケットを整形する
  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
  oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | General Discoverable Mode

  std::string strServiceData = "";
  strServiceData += (char)0x0a; // 長さ(10Byte)
  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)(analogRead_value & 0xff); // アナログ値の下位バイト
  strServiceData += (char)((analogRead_value >> 8) & 0xff); // アナログ値の上位バイト
  strServiceData += (char)(digitalRead_value & 0xff);  // デジタル値の下位バイト
  strServiceData += (char)((digitalRead_value >> 8) & 0xff); // デジタル値の上位バイト
  strServiceData += (char)(terminalid & 0xff); // 端末IDの下位バイト
  strServiceData += (char)((terminalid >> 8) & 0xff); // 端末IDの上位バイト

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

void setup() {
  M5.begin(true, false, true); // UART初期化、I2C初期化、LED初期化 
  delay(50);
  M5.dis.drawpix(0, LED_GREEN); // LEDの色変更
  pinMode(D_PIN, INPUT_PULLUP); // デジタル入力に設定
}

void loop() {
  analogRead_value = analogRead(A_PIN); // アナログ値
  digitalRead_value = digitalRead(D_PIN); // デジタル値
 
  if (digitalRead_value == 0){ // パトライトが光った時
    M5.dis.drawpix(0, LED_RED); // LEDの色変更
    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++; // シーケンス番号を更新
    M5.dis.drawpix(0, LED_GREEN); // LEDの色変更
  }
  delay(100);
}

補足説明

詳細はソースコードのコメントを参照して欲しいのだが少しだけ補足をしておく。

パトライトが光ったどうかは光センサーのデジタル値(digitalRead_value)で判定しているがアナログ値(analogRead_value)で判定しても構わない。

パトライトの種類によって光量が違うので光センサー本体の可変抵抗で調整するか、アナログのしきい値で調整するのだが、今回は可変抵抗で調整する方を選んだ。

また29行目の setAdvData にてアドバタイジング用のデータを組み立てている。

データレイアウトの詳細は「M5StickCとラズパイ4とのBLE(Bluetooth Low Energy)通信を試してみた。」の setAdvData の章を参照して欲しい。

会社IDはテスト用の 0xff×2 を指定している。

メッセージサイズの上限は31Byteなので、それ程大きなサイズのデータを送ることは出来ない。

アドバタイジングしている項目は、

  • アナログ値
  • デジタル値
  • 端末ID(光センサー毎の固有の値)

の3項目を送っている。

プログラムの書き込み

Arduino IDE のメニューからスケッチー>マイコンボードに書き込むを選択するか、Ctrl+U を押下するとコンパイルした後、プログラム(スケッチ)の実行ファイルを ATOM Lite に書き込む。

書き込みが正常に行われると「ボードへの書き込みが完了しました」のメッセージが表示される。

コンパイルしてマイコンボードに書き込む

ラズパイ

データを受信する側のラズパイのプログラム構成は以下の通りとなっている。

ディレクトリ構造

以下のディレクトリ構成にしている。

├─$HOME/.local/
│  │      
│  ├─optical-sensor
│  │  │  optical_sensor_catch.py
│  │  │
│  │  ├──log
│  │  │  error.log
│  │  │

プログラム類は $HOME/.local/ 配下にプログラム用のディレクトリーを作成して保存した。

error.log は何らかの原因で例外が発生した時にエラーログを格納するファイル。

ソースコード

optical_sensor_catch.py

# -*- coding: utf-8 -*-
"""
Created on Wed Apr 28 16:20:42 2021

・M5Stack ATOM LITEからBLEでデータが送られてくる値をコンソールに表示する

@author: Souichirou Kikuchi
"""

from bluepy.btle import DefaultDelegate, Scanner, BTLEException
import sys
import struct
import os
import csv
from datetime import datetime as dt

class ScanDelegate(DefaultDelegate):
    def __init__(self): # コンストラクタ
        try:
            DefaultDelegate.__init__(self)
            self.lastseq = None
            self.lasttime = dt.fromtimestamp(0)
        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    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()
                            (analog, digital, terminal_id) = struct.unpack('<hhh', bytes.fromhex(value[6:])) # hは2Byte整数(3つ取り出す)
                            print('アナログ= {0} 、 デジタル= {1} 、 端末ID = {2} '.format( analog, digital, terminal_id))
        except: # 例外時
            ex, ms, tb = sys.exc_info()
            self.put_error_log(type(ms)) # エラーログ処理

    def put_error_log(self, message): # エラーログファイルを出力する
        ERR_LOG_FILE = './log/error.log'
        if (os.path.isfile(ERR_LOG_FILE)): # ファイルが存在しているとき
            f = open(ERR_LOG_FILE, 'a') # 追記モードで読み込み
        else: # ファイルが無ければヘッダーを作成
            f = open(ERR_LOG_FILE, 'w') # 書き込み「モードで読み込み
        writer = csv.writer(f)
        writer.writerow(['{0:%Y-%m-%d %H:%M:%S.%f}'.format(dt.now()), message])
        f.close()

if __name__ == "__main__":
    scanner = Scanner().withDelegate(ScanDelegate())
    try:
        print('Optical Sensor Catch Start') # プログラム開始
        while True:
            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)
        pass
    finally:
        print('Optical Sensor Catch End') # プログラム終了

補足説明

詳細はプログラム中のコメントを参照して欲しい。

スキャンを繰り返して BLE の信号を見つけると ScanDelegate クラスの handleDiscovery メソッドが実行される。

handleDiscovery メソッドでは会社IDのチェックの他に10秒間連続してアドバタイジングされるので最初に受信したデータのみを対象とする様にしている。

実行

尚、プログラムは BLE モジュールにアクセスする関係で sudo を付けて管理者権限で実行する必要がある。

cd ~/.local/optical-sensor
sudo python optical_sensor_catch.py

実行結果は以下の通り。

光センサーに光を当てるとラズパイ側のコンソールに光センサーの値が表示される。

実行結果(ラズパイのコンソール)

終わりに

パトライト

パトライトと光センサーの位置は現地で合わせて可変抵抗で調整をする必要がある。

現場のラインは約20m程あるのだがBLE4.2(Bluetooth Low Energy)でも問題なく信号が到達することが確認できた。

また今回の記事のラズパイのプログラムはコンソールに情報を表示するだけだが実際には各センサーから受信した情報を元に音声でエラー状態を知らせる仕組みを構築している。

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

最後に

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

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

souichirou

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

おすすめ

4件のフィードバック

  1. 谷口 翔磨 より:

    こんにちは
    いつもこちらのサイトを拝見しています。
    こちらの「M5Stackの光センサーでパトライトの点灯を検知してラズパイに送信してみた」のサイトを参考にしながら「人感センサがオンになれば、センサがオンになったとラズパイにコンソールする」プログラムをつくっているのですが、どうもうまくいきません。誠に恐れ入りますが、それらのプログラムをご教授いただけないでしょうか。

    お忙しいところ誠に恐れ入りますが、宜しくお願い致します。

    • souichirou より:

      谷口 さん
      こんにちは。
      人感センサーを使っているとの事ですがM5Stackの人感センサーでしょうか?
      機器構成が分からないので何とも言えないのですが、
      1)人感センサーが検知してくれない
      2)検知はしているがラズパイに送信できない
      3)ラズパイで受信できない
      4)ラズパイで受信は出来ているが表示ができない
      のどこがうまくいっていないのでしょうか。
      ひとつひとつ状況を切り分ける所から始めるのが良いと思います。

谷口 翔磨 へ返信する コメントをキャンセル

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