#作成動機
うちの団地には来客用の共用駐車場が1つしかない。 だからいつも激戦だ。
でも空いているかどうかは現場まで見に行かないといけない。
かなり面倒なのでIoTの力を借りることにした。
#作成イメージ
駐車場が空いているかどうか自分のスマホで見れたら便利だ。
でも共用の場所の情報を独り占めしていたらそれは良くない。
ということで、空いているかを他の住民も見れるようtwitterで表示させようと思う。
ということで、twitterに投稿できるようraspiとSORACOM Airでの通信を選択。
しかし共用駐車場はもちろん外なので、raspiを電池で駆動させないといけない。
raspiの常時ONは消費電力的にNGなので、必要なときに電源ONさせるパワーコントロール用にArduinoを使う。
#用意したもの
品名 | 買った値段 | 備考 |
---|---|---|
raspi Zero | ¥660 | Twitter投稿用。なお、リンクのraspiZeroはGPIOのピンのはんだ付け必要&無線LANついていないので注意。(つまづいた設定の参照1、つまづいた設定の参照2) |
ArduinoNano | ¥500ぐらい | raspiのパワーコントロール用&クルマが駐車場に入っているかを監視 |
SORACOMのSim | ¥1280 | SORACOM Air planDで契約 |
3G USBドングル AK-020 | メルカリで安く入手 | |
ミニUSBハブ | ¥596 | raspiZeroはミニUSB必要 |
電池ボックス | ¥60 | 単3電池3個用(raspi用) |
電池ボックス | ¥100 | 単3電池6個用(Arduino用) |
DCDCコンバータ(AE-XC102) | ¥300×3 | raspi電源用。5V昇圧を3個 |
超音波センサ | ¥450 | クルマが駐車場に入っているかを検出 |
ブレッドボード | 小さいもの | |
ジャンパワイヤ | たくさん |
#組み立て
筐体内に入れたところ
#設置
共用駐車場の様子
設置(このときは仮置き、後で固定しました)
#処理シーケンス概要
#作り方詳細
##1.ArduinoとraspiZeroの電源と接続
###(1)Arduino電源
Arduinoの入力電圧は7~12Vなので単3乾電池6本にてVINから投入。
なお少しでも電源セーブのため、delaysleep関数を使用させて頂き、電池使用量を減らした。
###(2)raspiZero電源
単3乾電池3個にてDCDCコンバータで5V昇圧して供給。
なお、各種情報よりraspiZeroの消費電流は500mAになるので、200mA流せるDCDCコンバータを3つ並列に接続して600mAまで流せるようにした。
なお、電池はマンガンはダメ。アルカリでないと安定して動かない。
###(3)ArduinoとraspiZero間の信号接続
ArduinoとraspiZero間でお互いに信号のやり取りをしているが、ArduinoからraspiにHIGH信号を渡すときは注意が必要。
ArduinoのHIGH信号は5Vに対し、raspiの受電圧は3.3V。なのでそのまま繋ぐとraspiが壊れるらしい。
ということで減圧すべく同じ値の抵抗3つを直列にして、2番めと3番めの間にraspi側のGPIOピンを刺すと3.3Vに変換できます。(抵抗分圧というらしい)
##2.Arduino設定
60×5secおきに超音波センサを使って駐車場枠内へ向かいセンシングさせる。
駐車場内にクルマがいるときは距離値が小さくなる。
一定値以下の時はクルマがいるとして駐車状態を1(HIGH)とし、一定値より大きいときは0(LOW)とした。
駐車場でのクルマの出入り変化時にraspiを起動させるべく、60×5sec前のクルマ駐車状態値との差分を取り、次のようにした。
- 駐車場が埋まった
0(60×5sec前にクルマがいなかった)- 1(クルマが入った)= -1:クルマが駐車された
駐車場が埋まったとする信号(※ピンをHIGH)と共にraspiONの準備
- 駐車場が空いた
1(60×5sec前にクルマがいた)- 0(クルマがいなくなった)= 1 :クルマが出ていった
駐車場が空いたとする信号(※ピンをLOW)と共にraspiONの準備
- 変化がない
差分値が0時は駐車状態に変化が無いのでraspiOFFを継続、再度60×5sec待つ。
// measure distance by using ultrasonic sensor
float duration, distance;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration / 2) * 0.0344; // unit is [cm]
if( distance < 60 ) {
parking = 1; // PARKING
} else {
parking = 0; // NOT PAPKING
}
int delta = oldparking - parking;
// FULL state
if ( delta < 0 ){
state[0] = 'R'; state[1] = 'O'; state[2] = 'N';
digitalWrite(Power_ENB, HIGH);
digitalWrite(Park_ENB, HIGH);
MAXCOUNT = 10;
}
// VACANT state
else if ( delta > 0 ){
state[0] = 'R'; state[1] = 'O'; state[2] = 'N';
digitalWrite(Power_ENB, HIGH);
digitalWrite(Park_ENB, LOW);
MAXCOUNT = 10;
// NO change
} else {
state[0] = 'I'; state[1] = 'N'; state[2] = 'I';
MAXCOUNT = 60 * 5;
}
raspiONはDCDCコンバータへスイッチ信号ON入れることで電池とraspi間で通電が行われる。
あとはraspi側の終了処理状態を待って(10secおきにチェック)、DCDCコンバータのスイッチをOFFにする。
// wait raspi OFF signal
else if(state[0] == 'R' && state[1] == 'O' && state[2] == 'N') {
if(timecount == 0) {
if( digitalRead(Pi_ACTIVE) == LOW ) {
state[0] = 'P'; state[1] = 'O'; state[2] = 'F';
}
}
}
// raspi OFF demmand
else if(state[0] == 'P' && state[1] == 'O' && state[2] == 'F') {
if(timecount == 0) {
state[0] = 'I'; state[1] = 'N'; state[2] = 'I';
digitalWrite(Power_ENB, LOW);
}
}
このようにしてパワーコントロールを行った。
コード全体は以下。
#include <avr/sleep.h>
#define Sensor_ENB 17
#define Power_ENB 3
#define Park_ENB 7
#define Pi_ACTIVE 6
//UltrasonicSonar difinitions
#define echoPin 4
#define trigPin 5
int timecount = 0;
char state[3];
int MAXCOUNT = 60;
int parking = 0;
int oldparking = 0;
/*****************************************************/
/* 変数 */
/*****************************************************/
void setup(){
//Serial.begin(9600);
pinMode(Power_ENB,OUTPUT);
digitalWrite(Power_ENB, LOW);
pinMode(Park_ENB,OUTPUT);
digitalWrite(Park_ENB, LOW);
pinMode(Pi_ACTIVE,INPUT);
state[0] = 'I'; state[1] = 'N'; state[2] = 'I';
//UltrasonicSonar setting
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
}
void loop()
{
TransState();
delaySleep(1000);
}
void TransState(void)
{
if(state[0] == 'I' && state[1] == 'N' && state[2] == 'I' ) {
if(timecount == 0) {
//Serial.print( "state=" ); Serial.print(state);
// measure distance by using ultrasonic sensor
float duration, distance;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration / 2) * 0.0344; // unit is [cm]
//Serial.print( " distance=" ); Serial.print(distance);
if( distance < 60 ) {
parking = 1; // PARKING
} else {
parking = 0; // NOT PAPKING
}
int delta = oldparking - parking;
//Serial.print( " delta=" ); Serial.print(delta);
// FULL state
if ( delta < 0 ){
state[0] = 'R'; state[1] = 'O'; state[2] = 'N';
digitalWrite(Power_ENB, HIGH);
digitalWrite(Park_ENB, HIGH);
MAXCOUNT = 10;
}
// VACANT state
else if ( delta > 0 ){
state[0] = 'R'; state[1] = 'O'; state[2] = 'N';
digitalWrite(Power_ENB, HIGH);
digitalWrite(Park_ENB, LOW);
MAXCOUNT = 10;
}
// NO change
else {
state[0] = 'I'; state[1] = 'N'; state[2] = 'I';
MAXCOUNT = 60 * 5;
}
}
}
// wait raspi OFF signal
else if(state[0] == 'R' && state[1] == 'O' && state[2] == 'N') {
//Serial.print( "state=" ); Serial.print(state);
if(timecount == 0) {
if( digitalRead(Pi_ACTIVE) == LOW ) {
state[0] = 'P'; state[1] = 'O'; state[2] = 'F';
}
}
}
// raspi OFF demmand
else if(state[0] == 'P' && state[1] == 'O' && state[2] == 'F') {
//Serial.print( "state=" ); Serial.print(state);
if(timecount == 0) {
state[0] = 'I'; state[1] = 'N'; state[2] = 'I';
digitalWrite(Power_ENB, LOW);
}
}
if(timecount >= MAXCOUNT-1){
timecount = 0;
//Serial.print( " timecount=" ); Serial.print(timecount);
}
else{
timecount++;
//Serial.print( " timecount=" ); Serial.print(timecount);
}
oldparking = parking;
//Serial.print( " oldparking=" ); Serial.println(oldparking);
}
// Thanks for [http://radiopench.blog96.fc2.com/blog-entry-485.html]
void delaySleep(unsigned long t) {
//
// 注意:millis関数を使っているので50日以上の連続動作は出来ない。
//
unsigned long t0;
if( t <= 16 ) { // 16ms以下なら普通のdelayで処理
delay(t);
}
else{ // 17ms以上ならスリープ入れたdelayで実行
t0 = millis(); // 開始時のmillisの値を記録しておき
set_sleep_mode (SLEEP_MODE_IDLE); // アイドルのモード指定
while( millis() - t0 < t ) { // 設定値になるまでループ
sleep_mode(); // スリープに入れる(自動復帰するので何度も指定)
}
}
}
##3.raspiZero設定
Arduinoから電源ONの信号をもらったときにtweet処理が自動動作するための設定。
###(1)SORACOM Airの設定
ソラコムのサイトにあるsetup_air.shにてネットワーク設定。手順通りにやれば簡単にできる。
ちゃんとネットワークに繋がったかの確認方法も記載あるので、心配はない。
(LXterminal上で ifconfig ppp0
で確認できる)
ちなみにSORACOMの認識までは無線LANの認識より数十秒時間長くがかかるのかな、自動起動時にネットワークに繋ぐ処理前にsleepを40秒入れた。(後述のシェルスクリプト内に記載)
###(2)twitter投稿用Python実装とシェルスクリプト化
twitter投稿のためにはまずtwitterAPIを取得する必要がある。ここでは詳しく書きませんが、利用申請から許可までに1weekぐらいかかりました。
APIが利用できるようになったら4つのキーを手に入れてコード化。
駐車状態を表すGPIO信号よりONOFFを読み取り、HIGH(クルマが入ってきたとき)であれば「埋まったよ」のtweetを、LOW(クルマがいなくなったとき)であれば「空いてるよ」のtweetを発信する。
# -*- coding: utf-8 -*-
import tweepy
import time
import RPi.GPIO as GPIO
from time import sleep
from datetime import datetime
# 登録時の各種キーを代入
CK="*********************"
CS="*********************"
AT="*********************"
AS="*********************"
# Twitterオブジェクトの生成
auth = tweepy.OAuthHandler(CK, CS)
auth.set_access_token(AT, AS)
api = tweepy.API(auth)
# for parking ON/OFF switch setting
GPIO.setmode(GPIO.BCM) #GPIOへアクセスする番号をBCMの番号で指定することを宣言します。
GPIO.setup(4,GPIO.IN) #BCM 4番ピンを入力に設定します。
def main():
if GPIO.input(4) == GPIO.LOW:
api.update_status(status='5分前にクルマがいなくなったよ' + datetime.now().strftime('%Y/%m/%d'))
time.sleep(0.1)
GPIO.cleanup()
else:
api.update_status(status='5分前にクルマが駐車したよ' + datetime.now().strftime('%Y/%m/%d'))
time.sleep(0.1)
GPIO.cleanup()
if __name__ == "__main__":
main()
このPyコードの実行処理をシェルスクリプトに格納しておく。
上述の通り、SORACOMがつながるのを少し待つためにこのあとにsleepを入れた。
あと、このシェルスクリプトに実行属性を付与することもお忘れなく。
#!/bin/sh
cd
cd Documents
sleep 40
python3 monitoringTweet.py
cd
###(3)起動時の自動実行
起動時の自動実行は /etc/rc.local を編集することで可能となる。
sudo nano /etc/rc.local
にて編集開始。
もともと書かれているコード内の「fi」と「exit 0」の間に自動実行させたいコードを追加する。
sudo -u pi echo 3 > /sys/class/gpio/export
sudo -u pi echo out > /sys/class/gpio/gpio3/direction
sudo -u pi echo 1 > /sys/class/gpio/gpio3/value
sudo -u pi /home/pi/Documents/my-script1.sh
sleep 120
sudo -u pi echo 0 > /sys/class/gpio/gpio3/value
sudo shutdown -h now
起動してすぐにraspiが電源ON状態であることを示すためにGPIO3からON出力するコードを入れる。
その後作成したシェルスクリプトを呼び出しtweet処理実行。
最後にシャットダウン処理を行うが、その前にraspiOFF許可状態をArduino側に教えるべく、GPIO3からOFF出力するコードを入れる。
なお、シャットダウン前にsleepを120sec入れた。これがないとraspi起動後すぐに電源OFFになってしまい、
/etc/rc.local を編集できなくなる。注意が必要。
#動作とその結果
電源の投入順番だが、
➀Arduino
➁raspi
としないと狙いの動作にならない。(逆転させるとraspiの電源がうまくコントロールできない)
ここは少しハマってしまった箇所。
で、共用駐車場にクルマの出し入れをしてみると・・・
Twitter上にtweet表示された!
(5分前・・・としたのは実際の駐車状態と検出タイミングにラグが有るため)
#電源の持ちについて
現在検証中ですが、執筆時点で40時間程度は稼働しています。
おそらくですが、消費電力を見積もると概算で少なくとも20日程度は動作すると思います。
#終わりに
電子工作の力で地域貢献がたくさんできると思ってます。
身近な人のためになることを今後もやっていこうと思ってます。
###参考
本内容は数多のwebサイト情報を頼りに作ったものの、最たる参考文献はトランジスタ技術2017年2月の岩田氏の文献(P76-89 )によるところが多大にあります。先人の知恵に感謝申し上げます。