Python
PHP
RaspberryPi
IoT
加速度センサー

ラズパイで加速度センサーとステッピングモーターを使って、ハウル時計を作ってみた

ezgif.com-rotate (2).gif

はじめに

完成したものがこれです
ハウルに出てくる、円盤が自動で回転するやつのイメージで作りました(今回は円盤でなく針が回る形ですが)
加速度センサーの入ったサイコロを転がすと、その表示に合わせて、ルーレットの上を針が動くようになっています
円盤の表示をみて、サイコロの持ち主が今何をしているのかが一目でわかるというものにしました

用意するもの

  1. ステッピングモーター(バイポーラ ステッピングモーター ST-42BYH1004)
  2. モータードライバ(DRV8835使用ステッピング&DCモータドライバモジュール)
  3. はんだごて
  4. はんだ
  5. ブレッドボード
  6. ジャンパーワイヤ(オス) × 6
  7. ジャンパーワイヤ(メス) × 2
  8. Raspberry Pi(3 ModelB) 
  9. micro SDカード
  10. 加速度センサーの入ったサイコロ(加速度センサー:https://mono-wireless.com/jp/products/TWE-Lite-2525A/)
  11. micro SDカードリーダー
  12. micro USBケーブル
  13. USB ACアダプタ
  14. モノスティック(https://mono-wireless.com/jp/products/MoNoStick/index.html)

加速度センサー

加速度センサーを中に入れて、各面に今やっていることを表すイラストを表示しているサイコロを作って使います
Image uploaded from iOS (2).png
この面だと自席で作業中ということを示しています

Raspberry Pi準備

  • こちらのqitta記事を参考にセットアップを行いました https://qiita.com/Halhira/items/1da2ae543217be26988a
  • raspberry piにモノスティックを差します
  • SSH接続を行っているターミナルソフトで、「stty -F /dev/serial/by-id/usb-MONOWIRELESS_MONOSTICK_MW1IQ6P8-if00-port0 speed 115200」コマンドを入力

モーターを回転させる

  1. 配線

    下記サイトの配線を参考にさせて頂きました

    https://sites.google.com/site/memorandumjavaandalgorithm/raspberry-pi-jiang-zuo10-suteppingumotadoraiba-drv8835 
    Image uploaded from iOS (1).jpg
    こんな感じの配線になりました

  2. はんだづけ

    ドライバとブレッドボードの接触部分をはんだします

    はんだ付け.png
    こんな感じで12箇所はんだ付けしました

  3. pythonプログラム
    pythonプログラム「rotate.py」ファイルを作成(コードは下に記載)
    teratermなどのリモート接続しているものからraspberry pi上にファイルをアップロード
    raspberry pi上で「vim present_location」と入力して、「present_location」ファイルを作成、編集する
    初期値として、0をいれておく。「:wq」で編集終了して、ファイルを保存
    コマンドで「python rotate.py 2」などと入力
    引数を0~5の間で変えてみて、モーターが回転することを確認

  4. phpプログラムの作成
    「rotate.php」を作成(コードは下に記載)
    加速度センサーから値を受け取って、その値によってコマンドでpythonプログラムを叩くようにする
    raspberry pi上にファイルをアップロードする

ルーレット、針の作成

  1. PowerPointで6つの値を均一にした円グラフを作成
  2. 6つの扇型にそれぞれ、ステータスを表示するサイコロの面と同じイラストを配置
  3. 印刷
  4. 画用紙にノリで貼って、画用紙ごと円の形に切り取る
  5. 針の形に黒画用紙を切り取る 針の中心は直径1cmくらい
  6. 円盤の中心を六角形に切り取る(モーターの軸に引っかからない程度の大きさ)
  7. 細くした画用紙を丸くして、モーターの軸周りを囲むようにして円盤を乗せる土台を作る(軸の先端より少し低いくらいの高さ)
  8. モーターに土台の画用紙を張り付け、その上に円盤を張り付ける
  9. 軸の先端に両面テープで貼りをつける
  10. Image uploaded from iOS (1).jpg
  11. 完成したルーレットと針はこんな感じになりました

完成

サイコロを転がしてみて針が指すイラストと、サイコロの表面のイラストが同じになるかを確認する

あとがき

上のでは全部ローカルでやってますが、ラズパイを二つ用意して、片方で加速度センサーの値を取得して、片方でルーレットを回すのもやってみました。離れたところで、サイコロの持ち主が何をしているかやどこにいるがルーレットを見て直感的に理解できて便利そうです。
これをたくさん用意することで、ルーレットを壁に並べてっていう光景もなんか面白そうだなと思いました。
あとはんだ付が技術の授業以来で吸取線とはんだごて台買うの忘れてたんですけど、ちょっと辛かったので買ってやった方がいいなとという感じです。
詳細な説明を省いているところがあるので、もし実際に作ってみたいという場合は、ダイレクトメールを頂ければと思います。
読んでいただき、ありがとうございました。

ソースコード

「rotate.py」
1. コマンドラインから引数を入力して値を受けとる
2. 引数は0~5
3. 「present_location」ファイルに現在のモーターの角度を保存しておく
4. 「present_location」ファイルから読みとった値とコマンドラインで入力した引数から計算した角度の差分だけ回るようにする
5. 参考:https://sites.google.com/site/memorandumjavaandalgorithm/raspberry-pi-jiang-zuo10-suteppingumotadoraiba-drv8835 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import RPi.GPIO as GPIO
from time import sleep

class DRV8853:

    def __init__(self, PinA1, PinA2, PinB1, PinB2):
        self.mPinA1 = PinA1     #GPIO Number
        self.mPinA2 = PinA2     #GPIO Number
        self.mPinB1 = PinB1     #GPIO Number
        self.mPinB2 = PinB2     #GPIO Number

        self.SetWaitTime(0.01)

        GPIO.setmode(GPIO.BCM)

        GPIO.setup(self.mPinA1, GPIO.OUT)
        GPIO.setup(self.mPinA2, GPIO.OUT)
        GPIO.setup(self.mPinB1, GPIO.OUT)
        GPIO.setup(self.mPinB2, GPIO.OUT)

        GPIO.output(self.mPinA2,GPIO.HIGH)
        GPIO.output(self.mPinB2,GPIO.HIGH)

   """ウエイト時間を設定する"""
    def SetWaitTime(self, wait):
        if wait < 0.01:
            self.mStep_wait = 0.005
        elif wait > 0.01:
            self.mStep_wait = 0.01
        else:
            self.mStep_wait = wait

    """CWに1Step移動する"""
    def Step_CW(self):
        GPIO.output(self.mPinA1,GPIO.HIGH)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinB1,GPIO.HIGH)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinA1,GPIO.LOW)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinB1,GPIO.LOW)
        sleep(self.mStep_wait)

    """CCWに1Step移動する"""
    def Step_CCW(self):
        GPIO.output(self.mPinB1,GPIO.HIGH)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinA1,GPIO.HIGH)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinB1,GPIO.LOW)
        sleep(self.mStep_wait)
        GPIO.output(self.mPinA1,GPIO.LOW)
        sleep(self.mStep_wait)

    """目標ポジションに移動する"""
    def SetPosition(self, step, duration,mStep):
        diff_step = (100 - mStep + step) % 100
        print(step)
        if diff_step != 0:
            wait = abs(float(duration)/float(diff_step)/4)
            #wait = float2/25
            print("duration:"+str(duration))
            print("diff_step:"+str(diff_step))
            print("wait:"+str(wait))
            self.SetWaitTime(wait)
        for i in range(abs(diff_step)):
            if diff_step > 0:
                self.Step_CW()
            if diff_step < 0:
                self.Step_CCW()

   """終了処理"""
    def Cleanup(self):
        GPIO.cleanup()

"""メイン関数"""
if __name__ == '__main__':

    """コマンドライン引数をargvに格納"""
    argv = sys.argv
    argc = len(argv)
    print(argv)
    """引数が正しい数入っているかを確認"""
    if(argc != 2):
        print('argc')
        quit()
    """現在のモーターの角度が保存してある「present_location」ファイルを開いて、入っている値を変数mStepに入れる"""
    file = open('present_location', 'r')
    for present_location in file:
        mStep = int(present_location)
        print(mStep)
        break
    file.close()

    """回転した後の角度を、「present_location」ファイルに新しく書き込んで保存する"""
    file = open('present_location', 'w')
    step = int(argv[1]) * 16
    file.write(str(step))
    file.close()
    print(step)

    StepMoter = DRV8853(PinA1=4, PinA2=17, PinB1=27, PinB2=22)
    #転がしたサイコロの面にルーレットの針が連動するようにモーターを回転
    try:
         StepMoter.SetPosition(step,2,mStep)
    except KeyboardInterrupt  :         #Ctl+Cが押されたらループを終了
    except Exception as e:
        print(str(e))
    finally:
        StepMoter.Cleanup()
        print("\nexit program")

「rotate.php」

<?php

# hcidump
define('PATTERN_PREFIX', '/([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})\.([0-9]{6}) ([<>]) (.*)/');
# tweliteコマンド
define('CMD_CAT_TWELITE', 'cat /dev/serial/by-id/usb-MONOWIRELESS_MONOSTICK_MW1IQ6P8-if00-port0');
# twelite
define('PATTERN_PREFIX_TWELITE', '/(rc.*)/');

deleteTrashProcess("TweLiteCollector");
exitWhenDuplicateProcess(basename(__FILE__));
$pp = executeCatTweLite();

# キャッシュ
$beaconCache = array();
$detectedAt = mktime();
$currentTime = strftime("%Y%m%d_%H%M%S");
$logFp = openLogFile($currentTime, 'twe_lite_');

function executeCatTweLite()
{
    $pp = popen(CMD_CAT_TWELITE, "r");
    if ($pp == false) {
        echo "catを起動できませんでした。 / " . CMD_CAT_TWELITE . "\n";
        exit;
    }

    return $pp;
}


# ビーコンの検出
$beacon = null;
while (($str = trim(fgets($pp, 8192))) !== false) {
    $newBeacon = preg_match(PATTERN_PREFIX_TWELITE, $str, $matches);
    if ($newBeacon) {
        echo $str, PHP_EOL;
        list($_, $_, $_, $ed, $_, $x, $y, $z) = sscanf($str,
            "%13s" .
            ":lq=%d%8s" .
            ":ed=%8s" .
            ":%28s" .
            ":x=%d" .
            ":y=%d" .
            ":z=%d");
        if($ed == '810F4632'){
            $state = 0;
            if( $x >  50 ){ $state = 0; }
            if( $x < -50 ){ $state = 1; }
            if( $y >  50 ){ $state = 2; }
            if( $y < -50 ){ $state = 3; }
            if( $z >  50 ){ $state = 4; }
            if( $z < -50 ){ $state = 5; }
            exec("python rotate.py $state", $outputs);
        }
        echo $outputs[0], PHP_EOL;
        $outputs = [];
    }
}