West Gate Laboratory

人生を少しでも,面白く便利にするモノづくり

M5StackにGroveでセンサつないで気圧測定する

概要

M5Stackにオムロンの気圧センサ(2SMPB-02E)を使ったモジュールをつないで、気圧を計測した。

ついでに、センサに平均機能IIRフィルタ機能がついていたので、それを使ってセンサの値にフィルタをかけてみた。

www.switch-science.com

背景

先日、M5Stackを購入した。

PICよりも周波数が高く、ライブラリが多くて扱いやすそうというイメージで購入してみたが、初期化にしろ何にしろとにかくコーディングが楽。(一方で内部で何してるのかわからなくても動いてしまうということでもあるが・・・)

さて、M5Stackの派生でM5Stack Grayがある。

これにはBasicのM5Stackに加え、9軸IMU(加速度、ジャイロ、磁気)が搭載されており、動きモノの制御に使ったりするのに最適である。

www.switch-science.com

また、Basicと同じくLCD・3つのボタン・SDカードスロットがあるのでちょっとしたUIやログの記録もできる。プロトタイプ開発にはもってこいである。

今回、M5Stack Grayに内蔵されているセンサで取得できる値(加速度・角速度・磁場)以外に、高度の取得が必要になったので、M5Stack GrayにGroveで気圧センサを接続し、値を取得した。

オムロン絶対圧センサ2SMPB-02E

今回、使用した気圧センサは、以下のページで紹介されていたオムロンの気圧センサ2SMPB-02Eである。

qiita.com

基本的なソースコードは上記ページを参照のこと。

スイッチサイエンスからモジュールが出ているので、それを使った。

M5Stack Grayに接続するとこんな感じ。

f:id:kaname_m:20200322110206j:plain
M5Stack Grayにオムロン気圧センサモジュールを接続

センサのデータシートはこちら

また、ありがたいことにArduino環境でこのセンサを使うためのライブラリがGithubで公開されている

github.com

M5Stackでも、このライブラリを使用した開発ができる。

ライブラリインストール

Arduino環境にこの気圧センサのライブラリをインストールする。

上記Githubからライブラリをzip形式でダウンロード、Arduinoから スケッチ→ライブラリをインクルード→.zip形式のライブラリをインストール でOK。

M5Stackで読み込む

ライブラリが読み込めたら、最低限は以下の関数を使って値を読み込める。

// ライブラリインクルード
#include "Omron2SMPB02E.h"
#include <M5Stack.h>

// センサのインスタンス
Omron2SMPB02E prs;

// 初期化・リード
prs.begin();
M5.begin();
prs.set_mode(MODE_NORMAL);

float tmp = prs.read_temp();   // 温度[deg]も読める
float pressure = prs.read_pressure();   // 気圧[Pa]も読める

なお、MODE_NORMALとは測定→スタンバイ→測定を繰り返すモード。通常の使用ではこれを使えば良い。

その他に、SleepモードやForcedモードといった省電力のためのモードが存在する。(データシート参照)

センサ値にフィルタをかける

このセンサ、デフォルトではセンサ出力値になんのフィルタもかかっておらず、結構ノイズが乗る。

例えば以下のグラフは部屋の地面と天井の間をセンサを動かしたときの気圧の生データである。

f:id:kaname_m:20200322111907p:plain
2SMPB-02Eの生データ(単位はPa

まぁ動いていることはわかるが、ノイズ多いよね、という感じ。

そこで、センサ値にフィルタをかけるわけだが、このセンサには機能として「複数回の測定平均を出力」「IIRフィルタ」が内蔵されている。今回は内蔵されたこれらの機能を使うこととする。

こうした機能がない場合はCPU側でIIRフィルタなどのプログラムを組んでやる必要があるが、このセンサはその機能自体が内蔵されているのでCPU側としては楽である。

複数回のセンサ値の測定平均を出力

データシートに測定モード別の測定回数の記述がある。測定回数が増えるほど精度は上がるが、変換時間が伸びたり平均電流が大きくなる。

f:id:kaname_m:20200322112629p:plain
測定モード別特性(2SMPB-02Eのデータシートより)

例えば、Standard Modeでの測定をする場合は、温度/ 圧力(気圧)それぞれの平均回数を1,8に設定する。

prs.set_average(AVG_1, AVG_8); // 第1引数が温度の平均回数、第2引数が気圧の平均回数

// 引数は以下の定義
// arg of set_average()
#define AVG_SKIP 0x0
#define AVG_1    0x1
#define AVG_2    0x2
#define AVG_4    0x3
#define AVG_8    0x4
#define AVG_16   0x5
#define AVG_32   0x6
#define AVG_64   0x7

IIRフィルタ

さらに、IIRフィルタをかけてノイズを低減する。

IIRフィルタとは、無限インパルス応答を利用した信号処理によるフィルタである。詳細はWikipedia等参照。

このセンサはIIRフィルタによるローパスフィルタが内蔵されているので、それを設定する。

IIRフィルタを設定することで、さらにノイズを低減できる。データシートには以下のような表が記載されており、例えばStandard Modeではフィルタ係数を4に設定することでRMSノイズ[Pa]が2.6→0.8に低減できる。

f:id:kaname_m:20200322113433p:plain
IIRフィルタ適用時のRMSノイズ使用(2SMPB-02Eデータシートより)

なお、フィルタ係数を大きくするとノイズ自体は小さくなるが、原理上出力の遅れが大きくなるので、使うシステムに応じて適切な係数を設定することが必要である。

IIRフィルタ係数を設定する関数は以下の通り。

prs.set_filter(FILTER_4);      // IIRフィルタ係数を設定(4)

// 引数は以下の定義
// arg of set_filter()
#define FILTER_OFF 0x0
#define FILTER_2   0x1
#define FILTER_4   0x2
#define FILTER_8   0x3
#define FILTER_16  0x4
#define FILTER_32  0x5

これらのset_average()やset_filter()はset_mode()の前に呼ぶと良い。

測定結果

上記の手順で温度の平均回数8(Standard mode)、IIRフィルタ係数を4と設定し、センサを部屋の地面から天井まで動かしたときの出力値は以下の通り。ノイズが低減されていることがわかる。

部屋の天井までは2.5m程度なので、見た感じ±10cmくらいの精度で相対高度が測定できそうである。

f:id:kaname_m:20200322111910p:plain
測定値平均・IIRフィルタを使用したセンサ出力値(単位はPa

平均回数を上げたり、フィルタ係数を大きくするとさらに精度は上がるだろう。

まとめ

オムロンの気圧センサ2SMPB-02EのモジュールをGroveでM5Stackに接続して値を取得した。

さらに、センサ内蔵の平均値出力・IIRフィルタ機能を利用してノイズを低減し、±10cm程度の精度で相対高度が取れることを確認した。

ラズベリーパイを使ったIoTシステムに脆弱性があると指摘があったためセキュリティ対策を施した話

概要

先日のブログで記事を書いた、ラズベリーパイを使って作ったIoTシステムに「脆弱性がある」と指摘を受けたので、素人ながら調べつつ最低限のセキュリティ対策を施した話。

westgate-lab.hatenablog.com

(ちなみに、上の記事ははてなブログの週間ランキング2位になってしまった)

今週のはてなブログランキング〔2020年2月第1週〕 - 週刊はてなブログ

背景

先日上記のブログ記事を公開したところ、「システムに脆弱性がある」という指摘を多々頂いた。システムというのは、ラズベリーパイでインターホンを監視して、呼出音を検知したら条件に応じて解錠ボタンを押す」というものである。

もともとは、予定された配達か否かで2通りの解錠方法を考えていた。

f:id:kaname_m:20200126140827p:plain
予定された配達の場合(廃止済み)

f:id:kaname_m:20200126140822p:plain
予定していなかった配達の場合(現行版は常にこれ)

受けた指摘は主に2つ。

  • もともと予定された配達時間帯に万が一悪意を持った人物が我が家のインターホンを鳴らすと中央玄関が開いて、中へ入れてしまう

  • 解錠コマンド用URLにアクセスした相手を識別しておらず、悪意を持った人物がコマンドを送ることが可能

というものである。

1つ目に関しては、確かにそうなので、「予定された配達の場合」のモードは廃止した。一方で、2つ目に関しては何らか認証が必要そうだ。しかしやり方がわからない。

そんなときに、IPA情報処理推進機構から連絡があった。どうやら脆弱性関連情報の届出受付システムを通じてこのシステムの脆弱性を届出てくれた人がいたようだ。

脆弱性関連情報の届出受付:IPA 独立行政法人 情報処理推進機構

届出られた内容を読むと脆弱性の詳細が事細かに書いてあり、素人の自分にとっては非常にありがたい内容だった。届出内容には対処方法も書いてあり、どうやらアクセス制御機能を実装する必要がありそうだ。

「アクセス制御・・・?」状態だった私はまずそれを調べるところから始め、ようやくID/パスワードによる認証ができるようになったため、その過程を述べる。今回はDigest認証を使用した。

f:id:kaname_m:20200209114348p:plain
アクセス制御機能を追加したシステム。Digest認証を使用

Basic認証、Digest認証

アクセス制御とは、要はあるシステムに対し誰か何をしていいのかを設定したり識別したりして、それに基づいて操作を拒否したり許可したりすることである。

思いつくのはID/パスワードによる認証だが、ID/パスワードによる最も基本的な認証方法にBasic認証Digest認証がある。

私はアクセス制御については素人なので、詳細な説明は他の記事を読んでもらうとして、ここではごくごく簡単に説明する。

Basic認証:実装が容易で、簡易的な認証方法。ログインIDとパスワードを入力すると、それらがBase64エンコードされてサーバへ送信される。サーバはその情報をデコードし、ID/パスワードが一致していたらOKとする。

Basic認証はとても実装が楽だが、認証情報がただBase64エンコードされているだけで、誰でもデコードできるので、実質平文で送っていることになる。公衆Wifiで認証情報を入力するな、というのはこういうことのようだ。なので、一般的に通信を暗号化するSSLHTTPS)と一緒に使うようである。

Digest認証Basic認証が実質平文でID/パスワードを送っているのに対し、Digest認証ではパスワードをハッシュ化して送信する。ハッシュ化、というのは一方向関数を使ってある固定長のメッセージに置き換えることで、ハッシュ値からもとの値(ID/パスワード)を解析することを困難にするもの。サーバ側ではID/パスワード情報があるので、同様にハッシュ値を計算して、それらが一致すれば許可する、といった具体のようである。

→今回はDigest認証を使ってアクセス制御を実装することとした。

なお、Digest認証ではアクセス先のURLやユーザ名はハッシュ化されず平文で送られるため、それらも隠したい場合はHTTPSによる通信暗号化をする必要がある。

最低限の対策

ポート変更

このシステムではラズベリーパイ側のサーバはFlaskで実装している。Flaskはデフォルトで5000番ポートを使用するが、まずはこれを変更する。49513~65535が自由に使えるポート番号のようだ。

もちろん、これだけでは対策にはなり得ないわけだが、デフォルトポートを狙ってくるようなアクセスには効果があるだろう。

Flaskでポート番号を変更する場合は、起動時の引数にport=(好きなポート番号)を加えてやれば良い。 (以下の例ではFlaskの処理をスレッド化して起動している、これについては過去記事参照)

    rest_service_thread = threading.Thread(name='rest_service', target=app.run, args=('0.0.0.0',), kwargs=dict(debug=False,port=PORT_NUM))
    rest_service_thread.start()

URL変更

以前の記事ではモザイクを掛けていたとはいえほとんどURL丸見えで公開してしまったため、そりゃ見る人が見ればアクセスしてくるよね、という状態だった。(実際にアクセスログを見るといくつか外部からのアクセスが見られた。もちろんインターホンが鳴った時以外は何も反応しないわけだが)

なので、URLはより複雑な名前に変更した。(個人使用のシステムなのでURLは非公開)

(ただ、先程述べたように今回実装したDigest認証はURLは平文で送られるため、もしパケットキャプチャなどされた場合にはURLはわかってしまう)

Digest認証の実装

FlaskにDigest認証を実装する。今回は以下のページを参照した。

qiita.com

Digest認証はFlask拡張を入れれば簡単に実装できる。

pip3 install flask-httpauth

で、スクリプト上でインポートする。

from flask_httpauth import HTTPDigestAuth

Flaskインスタンスを生成する際は以下の通り。

# Flaskインスタンス生成
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your secret key'
auth = HTTPDigestAuth()

で、実際にアクセスがあったときの処理部分に以下を記述。

@app.route("/YOUR_URL/")
@auth.login_required
def your_function():

次に、ユーザネームを引数とし、あらかじめサーバに保存してある認証用の情報からパスワードを返す関数を実装する。ID/パスワードはDictionaryで持っておくのが楽で、スクリプト上に直接書いてもよいが、今回は外部ファイルから読み込んでいる。

@auth.get_password
def get_pw(username):
    # ユーザデータ読み込み
    users = pickle_load('./users.pickle');
    if username in users:
        return users.get(username)
    return None

ID/パスワードなどの認証用情報は以下の記事を参照してあらかじめ外部ファイルに出力しておいた。

neuryo.hatenablog.com

ここまで実装すると、http://[IPaddress]:[PORT_NUM]/[YOUR_URL]/にアクセスした際にID/パスワードの入力を要求される。

f:id:kaname_m:20200209135658p:plain
認証画面

これで最低限のアクセス制御が実装できた。なお、アクセスする度ID/パスワードを打っていては配達員が帰ってしまうため、それらの情報はブラウザに保存した。

その他

セキュリティ対策にはならないのだが、私はこのシステムにアクセスする端末を決めているので、その機種以外でのアクセスをhttpヘッダのUser-Agentを使って弾いてみた。

User-Agentについては以下の記事などが詳しいので、詳細はそちらを参照のこと。

qiita.com

from Flask import requestして、アクセスがあった際にrequest.headers.get('User-Agent')すると、例えば以下のような文字列が得られる。

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/xxx.xx (KHTML, like Gecko) Chrome/xx.x.xxx.xxx Safari/xxx.xx

(xxxはバージョン番号)

文字列を見るとわかるように、アクセス元のOSなどがここからわかる(上の例の場合はWin10 64bit)。

今回はこのシステムにアクセスする端末を限定し、それ以外の種類のものからのアクセスは弾くことにした。 (もちろん、これは個人を識別できるIDでは全く無いため、気休めである)

その他の例として、iPadから接続すると以下のようなUser-Agentが得られる。

Mozilla/5.0 (iPad; CPU OS 13_3 like Mac OS X) AppleWebKit/xxx.xxx (KHTML, like Gecko) CriOS/xxx.xxx Mobile/xxxx Safari/xxx.xxx

iPad」の文字列が入っており、iPadからのアクセスであることがわかる。

まとめ

ラズベリーパイを使った再配達撲滅システムに最低限のセキュリティ対策を行い、アクセス制御(Digest認証)を実装した。

個人で使うシステムとしてはこれで十分と思うが、もし足りない部分があれば追加で実装していこうと思う。

余談

暗号の歴史について、以前読んだこの本が非常に面白かったのでここで紹介しておく。

暗号解読―ロゼッタストーンから量子暗号まで

暗号解読―ロゼッタストーンから量子暗号まで

  • 作者:サイモン シン
  • 出版社/メーカー: 新潮社
  • 発売日: 2001/07/31
  • メディア: 単行本

スイッチの長押しで電源をON/OFFする(ように見える)回路を作る

今どきのモバイル機器は、電源をON/OFFするとき特定のスイッチを長押しすることが多い(気がする)。 身近にあるところだと、例えばモバイルルータやガラケーは長押しで電源ON/OFFである。

スマホiPadも、長押しで直接OFFではないが、長押しがOFFの動作に紐付いている。

このように、ある種今どきのモバイル機器は長押しが電源ON/OFFの動作につながっている。

趣味の電子工作でモバイルデバイスを作る際も、電池・バッテリによる電源供給をON/OFFするために何らかのスイッチを設けるのが普通である。私は昔はよくスライドスイッチなどで物理的にON/OFFしていたが、こだわりはじめると段々と実際の製品のように長押しでON/OFFしたくなってくる。

今回は、そんなスイッチ長押しで電源をON/OFFする(ように見える)(可能な限り最小構成の)回路について述べる。 ※電源回路の先にはマイコンがある前提。今回はPICを想定する。

「ように見える」と書いたのは、長押しで実際に電源供給がON/OFFするわけではなく、マイコンを利用してインジケータを長押しでON/OFFして、「あたかも長押しでON/OFFしたように見える」回路だからである。以降省略して「ON/OFFする」と書く。

この回路を使うと以下のような長押しON/OFFができる。

これは「ペンに付けてただタップ数を数えてくれるデバイス」だが、中身はさておきタクトスイッチの長押しで電源がON/OFFする(ように見える)。

この回路はできるだけ最小構成で組んだつもりだが、「もっとここ省略できる」などあれば是非教えて欲しい。

スイッチ長押し 電源ON/OFF回路

結論から言うと、以下のような回路でスイッチ1つの長押しで回路全体の電源供給をON/OFFできる。

f:id:kaname_m:20200130232439p:plain
スイッチ長押し 電源ON/OFF回路

図中の、FETの型番や抵抗値はあくまで例である。この回路はモバイル機器に使うことを想定しているため、この電源回路自体の消費電力をできるだけ下げるため、大きめの抵抗値を使っている。

また、FETやトランジスタ、いずれも秋月電子で入手できるものを選んでいる。マイコンの接続ピンはGPIOならどこでも良い。

動作説明

ユーザが長押しする物理的なスイッチは回路図中のSWである。そして、回路全体をON/OFFする電気的なスイッチがQ1のPchFETである。

例えば、LEDやディスプレイなどのインジケータが接続された回路なら、一定時間長押ししたらインジケータが付き、一定時間長押ししたらインジケータが消灯するのが理想だろう。この回路はそれを実現する。

以下に順序立てて動作を説明する。

(以下、H:電源電圧、L:GND電圧を示す。また、図中の赤線は電圧Hレベル青線は電圧Lレベル黒矢印は電流を示す)

電源OFF→ON

(SW押す前)Q1ゲートはHのため、Q1はOFF。電源は供給されていない。

f:id:kaname_m:20200201100400p:plain
電源OFF状態

①ユーザがSWを押す

②SWを介してQ2のゲートがHになり、Q2が導通する。

③Q2が導通することでQ1のゲートがLになり、実は一旦ここで回路としてはONになる。

f:id:kaname_m:20200201100408p:plain
電源供給開始。ただしインジケータはOFF

「え?押した瞬間ONになっちゃってるじゃん?」という意見はもっともだが、この時点ではインジケータは(マイコンが)OFFにしているため、ユーザは「電源OFF」と認識している。

マイコンは、電源ONしたらすぐに12ピン(RC1)をL出力に初期化する。よってT1はOFF状態。マイコンはその状態で一定時間何もせず待つ。

⑤一定時間後もマイコンの電源が入ったまま(ユーザがSWを押したまま)であれば、12ピンをHにする。よってはT1はON状態。

f:id:kaname_m:20200201100412p:plain
T1をON

⑥T1がONしたことによりT1も介してQ1のゲートはLになる。ここでLEDやディスプレイといったインジケータを動作開始する。ユーザは電源ONを認識する。

f:id:kaname_m:20200201100416p:plain
インジケータがON。ユーザは電源ONと認識する。

⑦ユーザがSWを離しても、T1がONのためQ1ゲートはLとなり、Q1はON状態。回路に電源が供給される。

f:id:kaname_m:20200201100420p:plain
ユーザがSWを離した。電源は供給され続ける。

ポイントは、④⑤でマイコンが一定時間何もせず待つことである。

SWを押してから一定時間経つまではマイコンは何も動作を開始しないため、ユーザから見ると、あたかも一定時間長押ししたら電源がONしたように見える、というわけである。

もしユーザが長押しでなく、ちょっと押しただけでSWを離すと、Q2がOFFになり、またT1もマイコンによりOFF状態のため、Q1ゲートはHになり、結果としてQ1はOFF、回路全体の電源供給はOFFになる。

電源ON→OFF

さて、晴れて電源がONできたら次はOFFである。全体の電気的スイッチであるQ1はT1によりON状態になっているため、正しく長押しを検知できれば、T1をOFFしてやることで回路全体をOFFすることができる。

そこで使うのがR3である。

電源ON→OFF時の動作は以下の通り。

f:id:kaname_m:20200201100424p:plain
電源ON状態

①SWを押していない時、R3がつながっている11ピン(入力ピン)はR1を介してLレベルである。

f:id:kaname_m:20200201100427p:plain
SWを押していない時、11ピンはLレベル

②ユーザがSWを押すと、11ピンはHになる。同時にQ2がONになるが、もともとT1を介してQ1がONしているので関係ない。

f:id:kaname_m:20200201100432p:plain
SWを押すと11ピンはHレベル

③11ピンのHレベルを検知したら、マイコンは一定時間12ピン(T1)をHのまま待つ。

④一定時間後も11ピンがHのままだったら、LEDやディスプレイなどのインジケータをOFFする。ユーザは電源OFFを「認識」する。同時に12ピンをLにする。T1がOFFになる。(実際にはまだQ2がONのため、電源供給は続いている)

f:id:kaname_m:20200201100437p:plain
一定時間SW ON(11ピンH)を検知したらT1とインジケータをOFF。ユーザは「電源OFF」を認識する。

⑥ユーザはインジケータのOFFを見て、SWを離す。Q2ゲートはLになり、Q2はOFFになるため、Q1ゲートはHになり、Q1がOFFする。結果として回路全体の電源供給はOFFする。

f:id:kaname_m:20200201100400p:plain
SWが離され、本当に電源供給が止まる

ここでも同様にポイントは③④でマイコンが一定時間何もせずに待つことであり、一定時間経ってインジケータをOFFすることであたかも長押しで電源OFFしたかのように見える。

もし、ユーザが長押しでなく、短時間でSWを離した場合は、12ピンはHのまま、すなわちT1はONのままなので電源供給は続く。

これで、長押しによる電源OFFが実現した。

この回路を実際に使ってみる

この回路は、長押しの長さをマイコンで設定できるので、ユーザの好きな長押し時間が作れる。ちょっと便利。

また、手に入れやすい部品で思いつく限り最小構成で組んでいるため、チップ部品を使えばかなり電源回路を小さく作れる。

最初に示した動画を再掲するが、上の回路を入れており長押しで電源ON/OFFする。(このペンについてはいずれ記事にしたい)

スイッチ長押しでON/OFFできると、なんか「それっぽい」感じがする。

世の中のモバイル機器も同じような回路を組んでいるんだろうか??

ラズベリーパイ使って宅配便の再配達を撲滅した話(総集編)

概要

外出時でも荷物を受け取れるように、ラズベリーパイを使って受取までのプロセスをほぼ自動化した話。

我が家がオートロックマンションのため、共同玄関をどうセキュアに開けるかがキモ。

背景

私は宅配便の受取が苦手である。

時間指定できるならまだマシだが、指定したその2~3時間どのタイミングで来るかもわからず、待っていなければならないのは苦痛である。

以前はコンビニ受け取りもしていたが、やはり段ボールを抱えて家まで帰るのは面倒である。(我が家は3件隣がコンビニだがそれでも面倒は面倒)

時間指定ができない宅配便などは、もう諦めて一度不在票入れてもらってその上で時間指定で受け取っていた。配達員の方々には申し訳ないがそうせざるを得ない。

日本の宅配便の約6件に1件が再配達

一人暮らし、共働きが多い現在、再配達率は相当多いのでは?と思い調べてみたところ、国土交通省の発表では平成31年度4月の段階で16%、都市部に限れば18%に上るそうである。

www.mlit.go.jp

5~6回に1回が再配達ということになる。正直もっと多いと思っていた。

配達業界もこれは問題と思っているのだろう、最近はAmazonなどで置き配も始まっている。

我が家はオートロックマンション

置き配はセキュリティ上不安な面も残るが、再配達問題の解決の一歩にはなるだろう。私も是非利用してみたい。だが、我が家はオートロックマンションのため、インターホンで「解錠」ボタンを押さないと共同玄関が開かない。共同玄関が開かないことには配達員は玄関にたどり着くことができない。これでは置き配してもらうこともできない。

f:id:kaname_m:20191222141738j:plain
我が家のインターホン。解錠ボタンを押さないと共同玄関は開かない。

そこで、ラズベリーパイを使ってインターホンを監視、共同玄関を解錠したりユーザに配達を通知するシステムを作ってみた。

インターホン監視システム概要

今回構築したシステムの概要は以下の通りである。あらかじめ配達がわかっている場合と、予定外の配達の場合で動き方が分かれる。

まずは予定された配達の場合。

時間指定便など、あらかじめ配達時間がわかっている場合は、事前にGoogle Calendarに配達予定を登録しておく。ラズベリーパイは配達予定を取得し、その時間帯にインターホンが鳴ったら共同玄関を開け、配達員を中に入れる。ラズベリーパイは同時にユーザへ配達の旨をプッシュ通知する。

f:id:kaname_m:20200126140827p:plain
予定された配達の場合
(2020年1月27日 19時50分追記)

「指定時間帯は誰でも入れてしまうセキュリティホールになる」とのコメントが多くありましたので,このモードは廃止しました.

次が予定外の配達の場合。

時間指定ができなかったものなど、事前にいつ届くかわからなかいものの場合、ラズベリーパイはインターホンを検知するとその通知とインターホンのモニタ画面をスマホへプッシュする。ユーザはモニタ画面を見て配達員と判断したら、ラズベリーパイに解錠コマンドを送る。今回、Flaskでラズパイ上にサーバを立て、そこにコマンドを送ることにした。

f:id:kaname_m:20200126140822p:plain
予定外の配達の場合

オートロックマンションの共同玄関はセキュリティのためにあるため、来る者拒まずすべて通していたらオートロックの意味はなくなってしまう。そのため、「事前にわかっている配達」か、「ユーザが判断して許可した配達」のみ、通すようにした。

なお、部屋の玄関までたどり着いたら、簡易宅配ボックスであるOKIPPAを使って受け取る。

www.okippa.life

ラズパイでインターホン音検知

このシステムのトリガは全てインターホン音である。なにはともあれ、ラズパイでインターホン音を検知する。

家に転がっていたウェブカメラのLogicool C270をラズパイに接続し、インターホンが押されたときの「ピロピロピロ・・・」という音を検知している。具体的には、音声データを常に取得、FFTで最大音量の周波数を取得し、インターホン音と一致するかを判定する、というもの。詳細は以下の記事を参照。なお、プログラミング言語にはPythonを使っている。

westgate-lab.hatenablog.com

インターホンの音を検知したら、同時にユーザのスマホでプッシュ通知を送っている。プッシュ通知にはPushbulletを使った。PushbulletはPython APIも用意されており、アカウントを作れば簡単にラズパイ→スマホへのプッシュ通知を実装できる。

westgate-lab.hatenablog.com

ラズパイで解錠ボタンを押す

インターホン音が検知できたら、次にサーボモータで解錠ボタンを押せるようにしてみる。サーボモータにはマイクロサーボSG92Rを使った。秋月電子にも売っている。pigpioを使い、pythonで動かしている。サーボモータはインターホンに強力両面テープで貼り付けた。解錠ボタンをちょうどよく押せる角度を調整するのがキモである。

ここの詳細は以下の記事を参照。

westgate-lab.hatenablog.com

ここまでできると、こういうことができるようになる。

ラズパイでGoogle Calendarから配達予定を取得する

次に、Google Calendarから配達予定を取得してみる。なお、予定自体はあらかじめユーザがスマホで設定する。 PythonからGoogle Calendar APIを使う手順はQuickstartが整備されているので、それに従って設定していけば良い。

developers.google.com

今回のシステムでは、CRONで1時間に1回Google Calendarを読みに行って新しい配達予定がないかを確認している。

読み取った予定をもとに「予定された配達」か「予定外の配達」かを識別している。

ここの詳細は以下の記事を参照。

westgate-lab.hatenablog.com

westgate-lab.hatenablog.com

ここまでくれば、一応「予定された配達」は自動で受け取れることができるようになる。

ラズパイ+ウェブカメラでインターホンのモニタを抽出、通知する

もし、予定外の配達だった場合、ユーザへインターホンのモニタ画像を通知する必要がある。今回は、OpenCVを使った。ただモニタを撮影してプッシュするとモニタ以外の無駄な領域が多いため、輝度値からモニタ部分のみを検出し、射影変換して向きを補正した上でPushbulletを使ってユーザへ通知している。

f:id:kaname_m:20200202221525p:plain
撮影した画像をそのままPushbulletで通知した場合

f:id:kaname_m:20200202221528p:plain
モニタ部分だけを抽出、向きを補正してPushbulletで通知した場合。こっちのほうが見やすい

ここの詳細は以下の記事を参照。

westgate-lab.hatenablog.com

westgate-lab.hatenablog.com

外出先からラズパイに接続されたサーボモータを操作する

さて、ここまで来たら、ユーザは通知されたモニタ画像を見て配達か否かを判断できる。もしこれが予定外の配達であると判断した場合は、ラズパイに解錠コマンドを送って共同玄関を開ける必要がある。しかし、ただコマンドを送るだけではない、「即座に判断し、即座に解錠コマンドを送る」必要がある。なぜならば、配達員はインターホンを鳴らして十数秒もすれば不在票を入れて帰ってしまうからである。

リモートでラズパイを操作するので有名なのはWebIOPiだが、これは基本GUIによる操作を前提としており、操作に時間がかかってしまう。今回のような用途には不向きである。

そこで、今回のシステムではFlaskを使ってラズパイにサーバを立ち上げ、そこにコマンドを送ることにした。

また、家のルータにポートマッピング設定を行い、Flaskで使うポートへのコマンドをラズパイに転送することで、外出先からのラズパイ操作を実現した。

ここまで来ると、スマホへの通知画面は以下のようになる。モニタ画像とともにプッシュされたURLを1タップすれば、サーボモータが動作、解錠ボタンが押され共同玄関が開く。

(2020年2月2日 追記) まとめのところにも同じ内容を追記しているが、解錠コマンドはインターホンが鳴った時のみ反応すれば良いため、インターホンを検知してから30秒間のみ、応答するようにしている。四六時中コマンドで開けられるわけではない。そもそもインターホンが鳴ってないときに解錠ボタンを押しても共同玄関は開かない。

f:id:kaname_m:20200202221521p:plain
配達時のスマホ通知画面。URLを1タップすれば共同玄関が開く

ここの詳細は以下の記事を参照。

westgate-lab.hatenablog.com

まとめ

ようやく、ここまででラズパイを使った再配達撲滅システムが完成した。

完成以来、再配達を依頼することはなくなった。ハッピーハッピー。

f:id:kaname_m:20200108231839p:plain
外出中でも荷物が受け取れた

(2020年1月27日 19時50分追記)

普段2,3人/日しか来ないブログのPV数がえげつないことになっていてビックリ・・・

上にも追記しましたが,「指定した時間帯は誰でも入れるセキュリティホールとなる」といったコメントが多くありましたので,このモードは廃止しました.

こうするとインターホンが鳴るとスマホに通知が飛び,その場でインターホンモニタを見てスマホから中央玄関を開けることもできるシステムになります.

なお,「解錠コマンド」はインターホンが鳴ってから30秒程度しかコマンドに応答しないようになっており,普段はコマンドを送ったところで何もしません.念の為.

(2020年2月9日 14時05分追記)

このシステムに対してアクセス制御を実装し、セキュリティ対策とした。 以下の記事参照。

westgate-lab.hatenablog.com

ESP32でBluetooth通信してるとASSERT_WARNが出る件

最近ESP-WROOM-32を買って弄っている。

ESP-WROOM-32にはBluetoothが搭載されているので、スマホなどBluetooth搭載機器と簡単に通信を行うことができる。

単純にBluetoothを試してみるだけなら、Arduino IDEファイル→スケッチ例→BluetoothSerial→SerialToSerialBTで手軽に通信することができる。

このスケッチ例を使って、ESP32とスマホとつないでBluetooth通信を試していたのだが、シリアルモニタにASSERT_WARN(1 9), in lc_task.c at line 5054というメッセージが定期的に生じる(ひどいと数秒に1回)。

別にメッセージが出るだけで、Bluetooth通信自体はできるのでいいっちゃいいのだが、かなり目障り。

f:id:kaname_m:20200120231420p:plain
Bluetooth通信時のシリアルモニタ。 一番下の行にASSERTメッセージが大量に。

対処方法

どうやら原因は使っていたノートPCのBluetoothドライバだったようだ。

自分のPCのデフォルトのドライバはRealtekのものだった。

f:id:kaname_m:20200120231423p:plain
デフォルトのBluetoothドライバ(赤枠)

こいつをWindows標準のドライバに変更すると症状が収まった。

ドライバを変更するには、デバイスマネージャーから件のドライバを右クリック→ドライバの更新→コンピューターを参照してドライバーソフトウェアを検索→コンピューター上の利用可能なドライバーの一覧から選択します をクリックする。

すると、以下の画面になるので、Generic Bluetooth Adapterを選択し、次へ、からインストールする。

f:id:kaname_m:20200120231427p:plain

バイスマネージャー上でドライバが切り替わっていればOKだ。これでASSERT_WARNメッセージは出なくなった。

f:id:kaname_m:20200120232421p:plain

ESP-WROOM-32(DevKitC)で0からLチカまで

先日タイトルのESP-WROOM-32を購入した。

akizukidenshi.com

私は今までずっとPICかRaspberryPiを使っていた。これまではその2つのどちらかで十分だった。 ただ、今回「PICよりも高速」かつ「RaspberryPiより低消費電力」なマイコンが欲しくなりESP-WROOM-32を使うことにした。

f:id:kaname_m:20200119181708p:plain
ESP-WROOM-32(DevKitC)

ESP-WROOM-32の主なスペックを抜粋すると、

  • WiFi:802.11 b/g/n
  • Bluetooth v4.2
  • デュアルコア Tensilica LX6
  • クロック:240MHz
  • SRAM:520KB
  • 電源電圧:2.2V~3.6V
  • 消費電流:平均80mA
  • 使用温度範囲:-40~85度

他にも色々あるが、とにかくWifiもBTも載っててクロックが240MHzも出せるすごいマイコン、という感じ。 さらに、こいつはArduinoの開発環境が使える。

今回初めてArduinoの開発環境に触ったのだが、あまりにあっという間に何でもできてしまうので拍子抜けしてしまった。

ESP32でLチカなんてこれまで数多の人間がネットに方法を載せているが、ボードのリビジョンや開発環境は日々更新されており、それによって手順も多少異なる(実際に微妙に人によって書いてある手順が異なる)ので、今日段階での手順を載せておく。

というわけで今回、今まで一度もArduinoを触ったことがなかった私がほんの数瞬でLチカできるようになるまでの備忘録。

環境は以下の通り。

ボード:ESP-WROOM-32(DevKitC)

PC:Windows10 64bit

ESP-WROOM-32で0からLチカ

まずは、Arduinoの開発環境(Arduino IDE)をインストールする。 以下の公式サイトから「Windows Installer, for Windows XP and up」をダウンロードしてインストールする。

www.arduino.cc

次にESP32のサポートパッケージをダウンロードする。ESP-WROOM-32はArduinoの公式ボードではないため、追加でボードの情報をIDEに入れる必要があるためだ。

espressifのGithubのStable release linkのURLをコピーする。

f:id:kaname_m:20200119182424p:plain
アンダーライン部分をコピーする

Arduino IDEを立ち上げ、ファイル→環境設定→追加のボードマネージャ のURLに貼り付け、OK

f:id:kaname_m:20200119182847p:plain
ArduinoIDEの環境設定画面

次にIDEのツール→ボード:hogehoge→ボードマネージャ を開いて、esp32で検索する。すると、esp32のパッケージが出てくるので最新バージョンを選んでインストールする。(2020年1月19日時点で最新バージョンは1.0.4)

f:id:kaname_m:20200119182426p:plain
ESP32のボードパッケージ

次に、ツール→ボードからESP32 dev moduleを選択。

ここまで来たらブレッドボードでLチカ用回路を用意する。実はこのESP-WROOM-32のDev-kitC、ブレッドボードに絶妙にうまくはまらない。普通に挿すと片側しかピンが取り出せないし、電源ラインをまたごうとすると0.5ピン分間隔がずれ、これも上手く挿せない。

そこで、ブレッドボードの一部を引っ剥がし、0.5ピン分ずらしてもう一度はっつけた。(以下の画像の赤矢印部分、少し隙間があるのがわかるだろうか) こうすることで両側のピンを使うことができる。

(なお、このE-CALLのブレッドボードはユニットごとにテープではっつけてあるだけなので力をかけて剥がせば外れる。ただし導通用金属片が抜ける場合があるので、慎重に)

f:id:kaname_m:20200119184433p:plain
少し改造したブレッドボードにESP-WROOM-32を挿す

あとはソースコードを書いてコンパイル、書き込みをする。 LチカをするならIDEのファイル→スケッチ例→01.Basics→Blink のコードを少し改造して、

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(4, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(4, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(500);                       // wait for a second
  digitalWrite(4, LOW);    // turn the LED off by making the voltage LOW
  delay(500);                       // wait for a second
}

で良い。なお、PIN4とはDev-kitCに「4」と書いてあるピンのことだ。あとはマイコンボードに書き込めばOK。ボード上のスイッチ操作は不要である。

環境構築からLチカまで、本当に数瞬でできてしまう。PICでレジスタいじいじしていた身からすると、信じられない・・・。

本当に、やりたいことを1行書けばできてしまう世界が目の前にあるようだ。

Flaskを使って外出先からURL経由でRaspberryPiを操作する

はじめに

この記事はオートロックマンション用不在時荷物受け取りシステムの開発記事です。

westgate-lab.hatenablog.com

前回の記事で、WebIOPiを使って外出先からリモートでRaspberryPiにつないだサーボモータを操作した。

westgate-lab.hatenablog.com

WebIOPiではURLによるGPIOの操作をサポートしているが、HTTP POSTでアクセスする必要があった。 通常のURLによるアクセスはHTTP GETのため、前回はRaspberryPiに接続したサーボモータを操作するために一旦WebIOPiのGPIO操作画面を開き、そこからサーボモータを操作していた。

今開発しているオートロックマンション用不在時荷物受け取りシステムでは、配達員がインターホンを鳴らしてから不在を判断して持ち帰ってしまうまでの間に通知を受けたユーザが反応してサーボモータをリモート操作、共同玄関を解錠する必要がある。 WebIOPiではURLを表示してからどうしても2タップ必要になるため、そこの時間がもったいない。 また、WebIOPiを使ってGPIOを操作する場合はGUI画面でピン番号をタップすることになるが、短時間で焦って押すと間違って別のピンをタップしてしまう恐れもある。

f:id:kaname_m:20200109215354p:plain
WebIOPiのGPIO操作画面。ピンの番号をタップしてOn/Offを操作する

そこで、より短時間・確実にユーザがインターホン通知に反応するため、今回Flaskを導入してURL1タップで共同玄関をサーボモータで解錠するところまでやってみた。

なお、RaspberryPi 3B+、Python3.7.3を使っている。

Flaskとは

Flask(フラスク)は、Python用の、軽量なウェブアプリケーションフレームワークである。小規模で簡単なウェブアプリケーションを作るのに適しているらしい。

今回の場合は「URLタップ」→「サーボモータを操作」だけなので、このような用途に向いているようだ。

Flaskのインストール

python3を使っているので、

sudo pip3 install flask

でFlaskをインストール。

FlaskでHello World

ウィキペディアに載っているFlaskのサンプルコードをもとにHello Worldしてみる。

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run("0.0.0.0")

内容としては、app = Flask(__name__)でFlaskのインスタンスを作成し、app.run("0.0.0.0")でローカルサーバでアプリケーションを実行する。run()に何もIPを指定しないとローカルホストで動作するため外部からアクセスすることはできないが、"0.0.0.0"を指定すると自分以外の外部からアクセスすることが可能だ。(この段階ではポートマッピングしていないのでアクセスできるのは同じLAN内の端末のみ)

Flaskはポート番号5000を使うため、例えばルート/にアクセスしたい場合は、簡易的にはブラウザでhttp://[ローカルIP]:5000にアクセスすれば良い。例えば、以下はFlaskを動かしているRaspberryPiのローカルIPが192.168.0.6の場合。もちろん、これは同じLANに接続されている端末からだけアクセス可能だ。

f:id:kaname_m:20200112224247p:plain
FlaskのHello World実行結果

こんな感じに”Hello World!”できる。簡単。

加えて、例えばサーボモータを操作するURLを実装するには以下のようなコードを追加実装し、http://[RaspberryPiのIP]:5000/push_servo/にアクセスすれば良い。

@app.route("/push_servo/")
def push_servo():
    pushServo()     # サーボを動かす関数に飛ばす
    return "Push Servo!"

アクセスしたユーザには"Push Servo!"が返る。

こんな感じにして、アクセスするURLを返ることで自由な操作を実装、実行することが可能である。

Flaskをスレッド化

Flaskは大変便利なのだが、app.run()するとそこでブロックされてしまうため、その他の処理を行うことができない。 今回の場合は常にウェブカメラから音声データを取得してインターホン検知を行う必要があるが、それがブロックされてしまう。 そこで、Flaskの処理をスレッド化し、メインルーチンの処理をブロックしないようにする。スレッド化にはthreadingを使う。

from flask import Flask
import time
import threading

app = Flask(__name__)

@app.route("/push_servo/")
def push_servo():
    return "Push Servo!"

@app.route("/")
def hello():
    return "Hello World!"

if __name__=="__main__":
    rest_service_thread = threading.Thread(name='rest_service', target=app.run, args=('0.0.0.0',), kwargs=dict(debug=False))
    rest_service_thread.start()
        
    while True:
        print("Main routine!")
        time.sleep(1)

    rest_service_thread.join()

これを実行した上で例えばLANの別のPCからhttp://[RaspberryPiのローカルIP]:5000/http://[RaspberryPiのローカルIP]:5000/push_servo/にアクセスすると、

$ python3 flasktest.py
Main routine!
 * Serving Flask app "flasktest" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Main routine!
Main routine!
Main routine!
Main routine!
Main routine!
Main routine!
192.168.0.4 - - [12/Jan/2020 22:54:19] "GET / HTTP/1.1" 200 -
Main routine!
Main routine!
Main routine!
Main routine!
192.168.0.4 - - [12/Jan/2020 22:54:23] "GET /push_servo/ HTTP/1.1" 200 -
Main routine!
Main routine!
Main routine!
Main routine!

上のように、メインルーチンをブロックせずにHTTPのリクエストを受けることができる。これで、ウェブカメラでインターホンを監視しながら同時にURLでユーザのリモート操作を受け付けられるようになった。

外出先からアクセスする

これまではLAN内での操作だったが、実際には外出先からRaspberryPiに接続されたサーボモータを操作したい。ここでは前回と同じく、ルータのポートマッピングの設定を行い外部からアクセス可能にする。

ポートマッピングの方法は前回の記事を参照。前回はWebIOPiのポート8000を設定していたが、今回のFlaskはポート5000なので、5000を設定すること。

westgate-lab.hatenablog.com

これで、例えば/unlock/というサーボモータを動かす機能を持ったURLを用意しておけば、http://[グローバルIP]:5000/unlock/というURLにユーザがアクセスすることで外出先からサーボモータを操作し、共同玄関を解錠する事が可能である。

さらに、このURLとインターホンの画面(以前の記事参照)をPushBulletで通知することで、ユーザはインターホン画面を確認したら1タップで共同玄関を解錠することが可能である。

PushBulletでの通知については、過去記事を参照。

westgate-lab.hatenablog.com