ラズパイpicoでスマートロック?の自作

スマートロックは便利そうですが、スマホやカードキーなどを出先で無くした場合、締め出されてしまいます。

そこで今回は、物理的な鍵を使わないスマートロックっぽいものを製作してみました。

「スマートロック」という言葉は電気通信可能なものを指すようなので、今回の作品は厳密には「スマートロック」ではないですね・・

 

 

できたもの

アナ雪スマートロック

youtu.be

深夜だったので控えめにノックしました。聞こえにくいかもです

前回のプログラムを使用しました。

chocolate82436.hatenablog.com

周りに丸聞こえなので実際に導入するのはやめたほうがいいですね(笑)

 

加速度センサーでダイヤルロック化

youtu.be

 

ドアノブに加速度センサーを取り付けて、回転角度を計測しています。

動画では、「30°で1.5秒→0°で1.5秒→30°で1.5秒」の3動作で鍵が開くプログラムを使用しています。

パターンを複雑にすれば安全性は高まりますが、手早く解錠できるというスマートロックの大きなメリットを失ってしまうジレンマ・・

 

モーターについて

SG-90ではトルク不足だったため、SG-5010を使っています。

また、他のセンサーと組み合わせた際に、picoが電力不足になるのではないかと危惧していましたが、今のところ問題なく動いているのでよかったです。

 

機構について

既存のカギを使った外からの開け閉めにも対応できるようにしました。

制御角度が180°あるモーターであれば実現可能です。

100均の木材をカットして制作しました。

解錠(モーター:0° → 90° → 0°)

施錠(モーター:0° → -90° → 0°)

 

参考記事

qiita.com

加速度計測のプログラムを使わせていただきました。

I2Cは使ったことがなかったし、データシートも英語で難しそうだったのでとても助かりました。

ありがとうございます。

ラズパイpicoでマイクモジュール~特定の音に反応させたい~

ラズパイpicoで、特定のノックに反応する装置を作ってみました。
フーリエ変換などは行っておらず、非常にシンプルです。

できたもの

アナ雪

仕組みは、音と音の間隔から判定するという非常にシンプルなものです。

 

機械学習にも挑戦しているのですが挫折しかかっています(涙)

tech-blog.cerevo.com

docs.edgeimpulse.com

機械学習についてもやったこと忘れないうちにブログに書くつもりです

 

 

 

 

使用した部品

Raspberry Pi Pico
MAX9814

自動利得制御(AGC)とかいう頭良さそうな機能が付いたアナログマイクです。

www.amazon.co.jp

待てなかったのでprimeですぐ届くやつ買いました.. 馬鹿高い。

 

ノックの解析

今回認識させたいのは、アナ雪のあのノックです。↓

youtu.be

コンコンココンコン

 

計5回ノックします。

 

今回、アナ雪ノックかそれ以外かは、音の間隔で判断します。

そのために、アナ雪ノックの各ノック間隔は何秒くらいなのか、実際に自分でノックして調べました。

録音にはパソコンのマイク&windowsボイスレコーダーアプリを使い、波形を見れるフリーソフトで開きます。

 

Sazanamiというフリーソフトを使わせていただきました。

例えば、1回目のノックと2回目のノックの間隔はおよそ0.6秒とわかりました。

この情報を使って、1回目のノックから0.5~0.7秒の範囲で2回目のノックがされればOK、というようなプログラムを作成します。

 

配線

MAX9814 Pico
GND GND
Vdd 3V3(OUT)
Gain Vdd
Out ADC0
AR 未接続

MAX9814というマイクは、Gain端子をどこに接続するかでゲインを調整できるっぽいです。

今回はVdd(3V3)に接続して、一番小さい40dBとしました。

また、ARは反応速度?らしいですがよくわからないので未接続です。

 

プログラム

電圧値への変換はこちらのサイトを参考にさせていただきました。

ありがとうございます。

tech-and-investment.com

閾値を超えた後の処理について

閾値を超えた後30msスリープさせることで、1回のノックを複数と誤認しないよう対策しています。

もうちょっと賢い対策法もあると思います(涙)

閾値を超えた後の処理

載せるほどのものではないのですが、ブログの最後にソースコードを掲載しております。

 

ハマったポイント

波形は上下非対称

対策として出力電圧の絶対値を取りました。

 

time.time()関数の精度が低かった

time.time()は時間を取得できる関数で、経過時間を調べるために使用したかったのですが、デバイスによって精度に差があるようで、僕のピコはミリ秒に非対応っぽいです。

time.time()を使った場合 delta=500になってほしいのに..

自分の勘違いの可能性も大です

 

代わりに使用したのは公式ドキュメントで発見したtime.ticks_ms()です。

micropython-docs-ja.readthedocs.io

time.ticks_ms()を使った場合

今後の展望&ソースコード

これを利用して、インターホンや洗濯機、電子レンジの音などに反応して何かする何かが作れたらおもしろそうです。

スマートロックと組み合わせれば、一緒に雪だるま作ってくれる世界線も実現できるかもしれません。

また、今回はマイクとアンプがセットになったモジュールを使いましたが、オペアンプを使ってゲインを細かく調整できる回路も作ってみたいです。

ローパスフィルターとかにも興味あります。

 

機械学習も諦めていない..といいつつマジで沼ったら人生何回分あっても時間足りなくなるので、2023年は有意義な時間の使い方ができるようになりたいです..

 

ソースコード

# 誤認対策について
# 閾値を超えた後30msスリープさせることで、1回のノックを複数と誤認しないよう対策しています。

import machine
import time

# マイクの設定
mic = machine.ADC(0)
# LEDの設定
led = machine.Pin(25, machine.Pin.OUT)
var = -1 # 交互に点滅させる用
led.value(0)
# 出典:[【ADC】RaspberryPi Pico MicroPythonでアナログ入力を使う方法 【CdS】]
# https://tech-and-investment.com/raspberrypi-pico7-adc-cds/
# 16bitの数値一単位での電圧値を設定します
unit = 0.00005035477
# 閾値 マイクの出力電圧がこの値を超えるとノックと判断します
# Vddを3.3V (36番ピン)に接続&ゲインが40dBの場合1.5としました
# 環境に合わせて調整してください
threshold = 1.5
# ノックごとにwhile文を移動していきます
# 今回はノックが計5回なのでnumberは1~5
number = 1

while True:
    # 1回目のノック
    while number == 1:
        # ADCの値を読み込みます(16bitの生の数値)
        voltRaw = mic.read_u16()
        # 16bitの値から、電圧値に変換します
        volt = voltRaw * unit
        if abs(volt) > threshold: # マイクからの出力は正とは限らないため絶対値
            # ノック間の時差を求める用
            # picoではtime.time()がミリ秒に非対応っぽかったので、代わりの関数を使いました。
            # https://micropython-docs-ja.readthedocs.io/ja/latest/rp2/quickref.html
            time_knock = time.ticks_ms()
            time.sleep_ms(30) # 誤認対策
            print('~~1~~')
            number = 2 # 次のwhileループに進む
    
    # 2回目のノック
    while number == 2:
        voltRaw = mic.read_u16()
        volt = voltRaw * unit
        time_delta = time.ticks_diff(time.ticks_ms(), time_knock) # 時差を計算
        if time_delta > 700: # 1回目と2回目のノック間隔の上限
            print('2: over 700 msec --> reset')
            number = 1 # 始めのwhileループに戻る
        if abs(volt) > threshold:
            if time_delta < 400: # 1回目と2回目のノック間隔の下限
                time.sleep_ms(30) # 誤認対策
                print('2: within 400 msec --> reset')
                number = 1
            else:
                time_knock = time.ticks_ms()
                time.sleep_ms(30) # 誤認対策
                print('~~2~~')
                number = 3
                
    # 3回目のノック
    while number == 3:
        voltRaw = mic.read_u16()
        volt = voltRaw * unit
        time_delta = time.ticks_diff(time.ticks_ms(), time_knock)
        if time_delta > 500: # 2回目と3回目のノック間隔の上限
            print('3: over 500 msec --> reset')
            number = 1
        if abs(volt) > threshold:
            if time_delta < 300: # 2回目と3回目のノック間隔の下限
                time.sleep_ms(30) # 誤認対策
                print('3: within 300 msec --> reset')
                number = 1
            else:
                time_knock = time.ticks_ms()
                time.sleep_ms(30) # 誤認対策
                print('~~3~~')
                number =  4
                
     # 4回目のノック
    while number == 4:
        voltRaw = mic.read_u16()
        volt = voltRaw * unit
        time_delta = time.ticks_diff(time.ticks_ms(), time_knock)
        if time_delta > 300:
            print('4: over 300 msec --> reset')
            number = 1
        if abs(volt) > threshold:
            if time_delta < 100:
                time.sleep_ms(30) # 誤認対策
                print('4: within 100 msec --> reset')
                number = 1
            else:
                time_knock = time.ticks_ms()
                time.sleep_ms(30) # 誤認対策
                print('~~4~~')
                number = 5
    
    # 5回目のノック
    while number == 5:
        voltRaw = mic.read_u16()
        volt = voltRaw * unit
        time_delta = time.ticks_diff(time.ticks_ms(), time_knock)
        if time_delta > 700:
            print('5: over 700 msec --> reset')
            number = 1
        if abs(volt) > threshold:
            if time_delta < 400:
                time.sleep_ms(30) # 誤認対策
                print('5: within 400 msec --> reset')
                number = 1
            else:
                time_knock = time.ticks_ms()
                time.sleep_ms(30) # 誤認対策
                print('**FROZEN**')
                
                # LEDを交互に光らせるための設定
                var = var * -1
                if var == 1:
                    led.value(1)
                if var == -1:
                    led.value(0)
                # LEDを交互に光らせるための設定 ここまで
                
                number = 1