帰宅したら自動で解錠するスマートロックを自作する②~ボタンで解錠編~

前回はドアが閉まったら自動で鍵をかけるシステムを作りましたが、それだと開けることができないので結局鍵を出さないといけなくて面倒です。そこで今回は鍵の代わりにタクトスイッチを使った暗証番号的な方法で解錠する方法をご紹介します。

作りたいもの

前回のつづきです。

  1. ドアが閉まったら自動で施錠
  2. 暗証番号で解錠
  3. ICカードで解錠
  4. スマホを持って近づくだけで解錠

前回から追加で必要なもの

  • タクトスイッチ
押すと導通するふつうのタクトスイッチ

ドアの外側にスイッチをつける

外から押せる位置にスイッチをつけます。うちのドアの横のすき間から線を通せたので普通の導線を使っています。ドアのすき間が狭い場合はフラットケーブル等を使うと良いと思います。ESP8266側はGNDとデジタル入出力に繋ぎます。

方法を決める

ボタンを準備したら次はどんなパターンでボタンを押したら解錠するか決めます。

やり方はみなさんのオリジナリティを出せるところだと思います。例えばボタンを押す順番や、押す時間の長短などを組み合わせて秘密の暗号にします。ここでは長短の組み合わせをパスワードにしてみます。

長短の組み合わせ(モールス符号)をパスワードにしてみる

皆さんご存じモールス符号をパスワードにしてみます。例えば「OPEN」のモールス符号で「--- ・--・ ・ ―・」と押した時に解錠するようにしてみます。

新しく追加したのは関数secret_command()の部分です。ボタンを押したり離したりしたときに、digitalReadの値の立ち下がり・立ち上がりを検出して、ボタンを押していた時間で「・」か「―」を判定します。3秒以上入力がなかったら終了して、入力された符号とパスワードが一致していたら解錠します。

#include <Servo.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// ルータ設定
IPAddress ip(192,168,100,102);
IPAddress gateway(192,168,100,1);
IPAddress subnet(255,255,255,0);

// ESP8266のピン番号
const int servo_pin = 2;
const int door_pin = 12;
const int button_pin = 14;

Servo myservo;             // サーボオブジェクトを生成
int door_sensor = LOW;
int door_sensor_last = LOW;
ESP8266WebServer server(80);  //サーバーオブジェクト


void door_open(){
  myservo.attach(servo_pin);
  delay(50);
  myservo.write(170);
  delay(500);
  myservo.write(90);
  delay(500);
  myservo.detach();
}

void door_close(){
  myservo.attach(servo_pin);
  delay(50);
  myservo.write(10);
  delay(500);
  myservo.write(90);
  delay(500);
  myservo.detach();
}

void handle_open(){
  door_open();
  server.send(200, "text/html", "opened");
}

void handle_close(){
  door_close();
  server.send(200, "text/html", "closed");
}

bool secret_command(){
  // HIGHがスイッチが押されていない状態
  // LOWがスイッチが押されている状態
  
  unsigned long pressed_time = millis();
  unsigned long released_time = millis();
  int now_state = LOW;
  int last_state = LOW;
  
  float button_lpf = 0.0;  // センサ信号にローパスフィルタ(LPF)をかけた値
  
  String key = "---.--..-.";  //モールス符号でO・P・E・N
  String input = "";
  while(millis() - released_time < 3000){

    button_lpf = 0.9 * button_lpf + 0.1 * digitalRead(button_pin);
    if( button_lpf > 0.8 ){
      now_state = HIGH;
    }else if( button_lpf < 0.2 ){
      now_state = LOW;
    }

    if( last_state == HIGH && now_state == LOW ){
      // ボタンが押されたとき、HIGH→LOW
      pressed_time = millis();
    }else if( last_state == LOW && now_state == HIGH){
      // ボタンが離されたとき、LOW→HIGH
      released_time = millis();

      // ボタンを押していた時間で「.」か「-」を追加
      input += (released_time - pressed_time < 300) ? "." : "-";
    }

    last_state = now_state;
    delay(1);
  }
  
  if(key == input){
    return true;
  }else{
    return false;
  }
  
}

void setup() 
{ 
  
  Serial.begin(115200);
  delay(200);

  //固定IPで運用するときの設定
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(WIFI_SSID, WIFI_PWD);

  // WiFiに接続するまで待つ
  Serial.println("");
  while(WiFi.status() != WL_CONNECTED){
    delay(1000);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("Connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // Webサーバを設定
  server.on("/open", handle_open);
  server.on("/close", handle_close);
  server.begin();

  // 開閉センサの入力を内部プルアップにする
  pinMode(door_pin, INPUT_PULLUP);

  // サーボ変数をピンに割り当て
  myservo.attach(servo_pin);

  door_close();
} 

void loop() 
{ 
  // サーバとして待ち受ける
  server.handleClient();

  // 現在のドアの開閉を検知
  // LOW=閉
  // HIGH=開
  door_sensor = digitalRead(door_pin);

  // 「開」→「閉」になったタイミングでサーボモータを回す
  if(door_sensor == LOW && door_sensor_last == HIGH){
    delay(1000);
    door_close();
  }  
  door_sensor_last = door_sensor;

  // ボタンで解錠
  if(digitalRead(button_pin) == LOW){
    bool success = secret_command();
    
    if(success){
      door_open();
    }
  }
  
}

完成!

簡単ではありますがこれだけでパスワード解錠的な機能をつけることができました。他にもテンキーなどのデバイスを付けてパスワードと一致したら解錠とかもできると思います。もっといいやり方があるよ!という方はコメントで教えてください。

ちゃんとセキュリティを考えるなら、10回入力に失敗したら一定時間操作を受け付けなくなるとかも考えたほうが良いかもしれません。今回の例ならパスワードが10ケタなので、2の10乗=1024回試されたら破られます。もう少しパスワードは長いほうが良いかも。

←前回    次回→

TinkerBoardと外付けHDDでRAIDのデータサーバを作る

はじめに

こんにちは。今回はTinkerBoardと外付けHDDで家庭内データサーバを組もうとした話をご紹介します。

RaspberryPiでもできる?

要るもの

いよいよ設定!

HDDをフォーマット

コマンドライン上でフォーマットする方法もあるけれど、今回はみんな大好きgpartedを使いました。GUIで直感的に操作できるし、確定を押すまで全ての処理は行われないので最終確認してから実行できるので僕はいつもgparted使ってます。入っていない人はsudo apt install gpartedすれば入ると思われます。

sudo gparted
gpartedの操作画面

まずは右上の/dev/xxxと表示されているプルダウンからフォーマットするデバイスを選択します。買ったばかりの状態のHDDでは注意マークが出ているかと思います。そういう場合は「デバイス」→「パーティションテーブルの作成」を行ってください。続いて「パーティション」→「New」を選択します。

パーティションの設定

パーティションの設定はほとんどデフォルトでいいと思います。参考にしたサイトでも紹介されている通り、「新しいサイズ」の項目は最大サイズよりも数百MB少なくしました。今回は全く同じHDDを購入し、最大サイズも全く同じでしたが念の為です。ラベル名は好きな名前をつけてください。その名前でフォルダ名が作られ自動でマウントされるので、WD01、WD02などわかり易い名前がいいでしょう。
操作が終わったらチェックマークを押して確定しましょう。処理が始まります。

HDD2台をRAID構成にする

複数台のHDDを使うことで、例えどれかが壊れてデータが失われても復旧できるようにデータに冗長性をもたせる技術をRAIDといいます。今回は2台のHDDに全く同じデータを書き込む「RAID1」を構成します。参考サイトのmdadmコマンドを一部変更して、次のようにしました。

sudo apt install mdadm
sudo mdadm --create /dev/md0 -v --raid-devices=2 --level=raid1 --chunk=128 /dev/sd[ab]

変更点は–raid-devicesの数を2にしたこと、–levelをraid1にしたこと、最後のデバイス名を自分の環境に合わせたことです。進捗は

watch -n 1 -d cat /proc/mdstat

とすることで確かめられます。RAID構築に一日以上かかるみたいです。ひえぇぇ。気長に待ちましょう。

できたデバイスをフォーマットしてラベル付け

2TBのディスクのフォーマットが完了するまでに2日以上かかってしまいました。完了したら最後の手順です。gpartedを使い、最初にHDDをフォーマットした時と同じ手順でデバイス/dev/md0をext4等の形式でフォーマットします。
そのままだとマウントしたときにやたら長いランダム文字列のフォルダができてしまうので、ラベル名を変えて置きます。gpartedでデバイスをアンマウントして、「パーティション」→「Label File System」です。

これで2つのHDDをRAID構成で使うことができます。お疲れ様でした。

参考にしたサイト

Raspberry PiにUSBメモリ4本でRAID10を組む

ラズパイにOpenCV入れた(ビルドエラー解決)

こんにちは、パパドラゴンです。大晦日に何やってんだって感じですが、今ラズパイ3にOpenCVを入れました。ビルドエラーに嵌ってしまい、3日も無駄にしてしまいましたが、なんとか年越し前に完了しました。

環境

raspbian: 8.0
OpenCV: 3.1.0
Python: 2.7.9

方法

こちらのサイトに書いてあるとおりにコマンドを実行しました。

Raspberry Pi講座 Python3 + OpenCV3(環境構築編)
https://sites.google.com/site/memorandumjavaandalgorithm/raspberry-pi-jiang-zuo-opencv3

失敗① ビルドでerror終了する

通常のapt-get upgradeではなく、sudo apt-get dist-upgradeすると成功しました。

失敗② ビルドで失敗する その2

makeするところでmake -j4 ではなくsudo makeだけで実行すると成功しました。

失敗③ make後、installすると、pkgconfig is not a directoryというerrorが出る

音声解析ライブラリJuliusをインストールしたときに作られたファイルが既にあったのが原因でした。makeファイルの中でpkgconfigで検索かけて保存先を別の名前に変更するか、元々あったファイルをリネームするととりあえず解決しました。

これにてインストール完了。やったね!

帰宅したら自動で解錠するスマートロックを自作する①~オートロック編~

はじめに

ホテルとかオフィスのドアみたいに閉まると自動で施錠してくれるやつ、あれすごく便利ですよね。家を出るときにわざわざポケットから鍵を出して鍵穴に指して回す動作をしなくていいと思ったよりだいぶ楽です。今回はオートロックを自作して自宅の玄関に取り付けた話です。

作りたいもの

まず、作りたいオートロックに必要な機能を決めます。

  1. ドアが閉まったら自動で施錠
  2. 暗証番号で解錠
  3. ICカードで解錠
  4. スマホを持って近づくだけで解錠

1→4になるほど難易度が高いと思います。この記事では最初の「ドアが閉まったら自動で施錠」をご紹介します。 まだ作ってる途中ですので、随時更新していきます。

必要なもの

  • ArduinoまたはESP8266 400円~2000円くらい
  • サーボモータSG92R  830円くらい
  • 100均の下駄箱ライト  108円

ArduinoまたはESP8266

Arduinoの種類は何でも大丈夫ですが、後々解錠にFelica(ICカード)を使う予定ならシリアル通信で152000bpsが使えるボードが必要です。一番良く使われているArduinoUnoは対応していません。下記の安いArduinoNano互換品とかでいいと思います。また今後スマホを使って解錠するなど、WiFiを使って何かすることを考えるならESP8266をおすすめします。ちなみに僕はESP8266で作ってます。

サーボモータ

サーボモータとは、指定した角度まで回転してくれるモーターです。室内側のサムターンを回すので、それなりにパワーのあるものが必要です。僕の家のサムターンはちょっと固めだったのか、持っていたSG90では回せませんでした。
不安な方はよりトルクの大きいSG92Rをおすすめします。こちらはSG90よりも動きが遅いらしいですが、楽々回してくれてかっこ良いです。

100均の開閉センサ付きライト

100均の下駄箱ライトは写真を撮るの忘れましたが、扉に磁石とセンサーを取り付けて双方が離れるとスイッチON、近づくとスイッチOFFするライトです。今回はこれの磁石とセンサー部分を切り取って使っています。100円で開閉センサが手に入るので便利です。
ここで紹介されているようなやつです。)

サムターン回し装置

まずモーターでサムターンを回す装置を作っていきます。3Dプリンターで部品を作っている人も見かけますが、CAD書いて出力するのも面倒なので割り箸とかで作っていきます。

どんな機構にするか

まずサムターンを回すための機構ですが、こんな感じになるように作ります。

サムターン回し機構。木のブロックがついた部分を左右に90度回すことによって鍵が開く。

木のブロックは余ってる木材の端材とかで全然OKです。それをサーボモーターに付属しているプラスチックのパーツに取り付けます。

木の端材をサーボモータについてるパーツにネジ止めしたところ

こうすることによって、ニュートラルにしておけば鍵を使って開けたり手で開けたりできます。モータで開閉するときは左右に90度回転させて、またニュートラルに戻すことで、手動とモータの動作を両立させることができます。

ドアノブに取り付け

割り箸とマスキングテープを使ってモータをドアノブに固定します。ここはドアノブの形状によって色々やり方があると思うので、ご自宅のドアにあったやり方で固定してください。

割り箸でモータを固定
ドアノブにつける。ここのやり方はお好みで。

開閉センサ

ドアが開いているか閉まっているかを検知するセンサを作ります。100均の下駄箱ライトを使えば簡単に作れます。100均のライトは片方が磁石、もう片方が金属板になっていて、磁石が近づくことで金属板が接触して電気が流れる仕組みになっています。今回はこの部分だけを切ってセンサ代わりに使います。

導線を切ってコネクタをはんだ付けする

2つの箱が近づくと導通、離れると絶縁です。

配線

サーボモータと開閉センサが用意できたら配線してみます。サーボモータは接続を間違えないようにESP8266のGND、Vin、D4(PWMピン)につなぎます。サーボモータから出ている線は茶色がGND、赤が5V、オレンジがPWM信号です。

開閉センサはGND、D0につなぎます。2本ありますがどちらの線をどちらにつないでもOKです。

配線イメージ

プログラム

ESP8266にこちらのプログラムを書き込みます。動作としてはすごく簡単で、開閉センサが「開」から「閉」になったタイミングでサムターンを回すだけです。せっかくWiFiが使えるESP8266を使ったので、WiFi経由で開け締めする機能も付けました!

#include <Servo.h>
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// ルータ設定
IPAddress ip(192,168,1,102);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);

// ESP8266のピン番号
const int servo_pin = 2;
const int door_pin = 12;

Servo myservo;             // サーボオブジェクトを生成
int door_sensor = LOW;
int door_sensor_last = LOW;
ESP8266WebServer server(80);  //サーバーオブジェクト


void door_open(){
  myservo.write(170);
  delay(1000);
  myservo.write(90);
}

void door_close(){
  myservo.write(10);
  delay(1000);
  myservo.write(90);
}

void handle_open(){
  door_open();
  server.send(200, "text/html", "opened");
}

void handle_close(){
  door_close();
  server.send(200, "text/html", "closed");
}

void setup() 
{ 
  
  Serial.begin(115200);
  delay(200);

  //固定IPで運用するときの設定
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(WIFI_SSID, WIFI_PWD);

  // WiFiに接続するまで待つ
  Serial.println("");
  while(WiFi.status() != WL_CONNECTED){
    delay(1000);
    Serial.print(".");
  }
  
  Serial.println("");
  Serial.println("Connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // Webサーバを設定
  server.on("/open", handle_open);
  server.on("/close", handle_close);
  server.begin();

  // 開閉センサの入力を内部プルアップにする
  pinMode(door_pin, INPUT_PULLUP);

  // サーボ変数をピンに割り当て
  myservo.attach(servo_pin);

  door_close();
} 

void loop() 
{ 
  // サーバとして待ち受ける
  server.handleClient();

  // 現在のドアの開閉を検知
  // LOW=閉
  // HIGH=開
  door_sensor = digitalRead(door_pin);

  // 「開」→「閉」になったタイミングでサーボモータを回す
  if(door_sensor == LOW && door_sensor_last == HIGH){
    delay(1000);
    door_close();
  }
  
  door_sensor_last = door_sensor;
  
}

ルータ設定やWiFiパスワードの部分はご自身の環境に合わせて変えてください。

Arduinoを使う人はヘッダーファイルとか適宜変更して、それ以外はほとんどそのままで行けると思います。

完成!

プログラムができたら開閉センサを近づけたり離したりしてみてください。近づけたときにサーボモータが回るはずです。

ESP8266を使っている人はスマホやパソコンのURLのところに192.168.xxx.xxx/closeと入力しても動きます。

確認ができたらドアに取り付けてみます。

※注意 オートロックなので閉め出されないように鍵を持っておくこと!動作テスト中に閉め出されたら恥ずかしいです。

ドアに取り付けてマスキングテープでいい感じにしました

最大の問題はUSBケーブルが玄関まで届くかですね。僕は延長コードを3個経由しています。あと家から出るときに室内側からサムターンを回すのが少し大変です。慣れれば簡単に開けられますがちょっとモータが邪魔なので、今度は家の中からスイッチとかで開けられるようにしておきます。

今回は以上です。次回は帰ってきたときに暗証番号的なもので解錠する方法をご紹介します。

次回→

Volumio2に再生コマンドを送る!

はじめに

こんにちは。何も考えずにラズパイzeroを購入してしまったせいで使い道が無くて持て余していたので、volumioをインストールしてミュージックサーバーにしてみたパパドラゴンです。

volumioのインストールはこちらのページで紹介されているDACをそのまま買って組んでSDカード焼くだけでめっちゃ簡単でした!
でもvolumioはブラウザから操作することを考えて作られているらしく、別のラズパイから再生コマンドを送る方法がいまいち分かりにくかったので紹介します。

やりたいこと

  • Volumio2を「他のラズパイ」から「コマンドラインで」操作したい
  • Google Homeを使って操作したい
  • WiFi経由でArduinoから操作したい

方法

結論から言いますと、Volumioの公式ドキュメントにAPI一覧が書いてあります。
ネットワーク関係は全く素人なので理解できたとこだけ読んだところ(よくない)、GETリクエストを送る方法が一番カンタンで早い気がします。

仕組みはともかく使い方だけなら、ブラウザでもラズパイのターミナル上でもpython上でもとにかく「http://volumio.local/api/v1/commands?cmd=〇〇」のアドレスにGETリクエストを送れば、結果(成功すれば現在の状態、失敗すればエラー)が辞書型で帰ってきます。
「volumio.local」の部分は「192.168.0.5」のようなアドレスに変える必要があるかも知れません。

例えばchromeだとアドレスバーに上記を打ち込めばこんな感じに帰ってきて再生が始まります。

「192.168.0.5」の部分は自分のvolumioのアドレスに変えてください

ラズパイのターミナルだとcurlコマンドを使ってこんな感じ。

ppdr@raspberrypi:~ $ curl http://volumio.local/api/v1/commands?cmd=stop
{"time":1538585873376,"response":"stop Success"}

pythonならこんな感じです。

import urllib2

res = urllib2.urlopen("http://volumio.local/api/v1/commands?cmd=play",timeout=5)
print( res.read() )

 

どんなコマンドが送れるの?っていうとアドレスの「?」以降(クエリ)の部分に色々指定して送れば良さそうです。

 

クエリ動作
cmd=play再生
cmd=stop停止
cmd=pause一時停止
cmd=prev前の曲へ
cmd=next次の曲へ
cmd=volume&volume=80音量を80に
cmd=playplaylist&name=Rockプレイリスト「Rock」を再生
cmd=repeatリピートをON&OFF
cmd=randomランダム再生をON&OFF

他のコマンドについては公式ドキュメントを参照してくださいm(_ _)m。

これが出来ればとりあえず汎用性は高そうです。プレイリストやキューの一覧を取得するコマンドもあるので帰ってきた辞書型を判断して処理を分けるとかもできそうです。
時間があればGoogle Homeから起動とかも試してみたいです。お部屋BGMライフが始まりそう。

ラズパイで(ホスト名).localでSSH接続...できない(できた)

 

ホスト名の変更

ラズパイは「ssh (ユーザー名)@(ホスト名).local」でIPアドレスを覚えなくても接続できてこれがかなり便利です。デフォルトだと「ssh pi@raspberrypi.local」です。

ところが2つ以上のラズパイを接続したい場合だと、デフォルトのままではホスト名が重複してしまうので、どちらか片方のホスト名を変更する必要があります。

sudo raspi-config

で設定を開き、「2 Network Options」→「N1 Hostname」を選択して変更するだけ!

ところが再起動後に「ssh pi@raspi_zero.local」とやっても接続できませんでした。

理由はホスト名に_(アンダースコア)を使っていたこと。代わりに-(ハイフン)を使うようにしたら問題なく接続できました!
意外な落とし穴でしたが、みなさん気をつけましょう。

I2C 16×2 LCDで日本語(カタカナ)を表示する(pythonプログラムあり)

LCDでカタカナ表示

こんにちは。ラズパイやArduinoでセンサ等を利用していると、気温などの数値をどうやって表示するか意外に悩みます。そんなメッセージや数字を表示したい時に便利なのがLCDディスプレイです。LCDは電光掲示板のようにドットで表現された文字を表示するデバイスで、ラズパイの5V電源でも動かすことが出来ます。

Osoyoo製LCD。電子工作入門セットに入っていました。

上の電子工作入門セットに入っていたOsoyoo製LCDは、I2Cで簡単にラズパイと接続できて、さらに制御用pythonプログラムも公式で公開されているのでとっても簡単にメッセージが表示できる…、のですがこの制御プログラム、英数字の出力のみでカタカナの表示方法が全く説明されていません。ドキュメントは日本語なのに不親切極まりないです。(初心者向けセットなのにマニュアル等一切なし。せめて各文字に対応するアドレスだけでも公開して欲しい…)

というわけで今回はこれにカタカナや記号を表示させることを目標にしたいと思います。

(追記)
このLCDの裏にはんだ付けされているI2C接続用マイコン「HD44780」は有名らしく、こちらで検索するとわりとヒットしました。今回はOsoyooのプログラムをもとにpythonで操作する方法をご紹介します。

このICは広く利用されているようで、説明されているページも多くありました。

表示は出来るっぽい

Osoyooの公式ページに従って進めてみます。

とりあえずドキュメントに従ってLCD制御用pythonプログラムをダウンロードして、文字を表示させてみる。LCDとラズパイの5V、OUT、SDA(#2ピン)、SDL(#3ピン)を接続して、制御プログラムのmain関数の中に次のように書けば英数字は表示できます。

def main():
  lcd_init()
  lcd_string("Hello world!",LCD_LINE_1)

このままだと表示されたメッセージが一瞬で消えてしまうので、一番下の二行をコメントアウトしておきます。

#finally:
  #lcd_byte(0x01,LCD_CMD)

これで英数字は問題ないのですが、カタカナを表示しようとすると文字化けしてしまいます。

対策

かなり強引ですが、文字化けすることを見越して元のテキストを変えてしまう変換表を作ってみました。表示したいテキストを一回この関数で変換した後、上記の方法でとりあえず正しい文字が表示できます。

def katakana(text):
	list1a = u"「。、」ヲ・ィァェゥォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜"
	list1b = u"アァイィウゥエェオォカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミ"
	list2a = u"ガギグゲゴザジズゼゾダヂヅデドバビブベボ"
	list2b = u"ザシジスズセゼソゾタダチヂッツナニヌネノ"
	list3a = u"パピプペポ"
	list3b = u"ナニヌネノ"
	
	text_changed = ""
	for l in text:
		#カタカナを変換
		if list1a.find(l) >= 0:
			text_changed += list1b[list1a.find(l)]
		#濁音を清音+濁点に
		elif list2a.find(l) >= 0:
			text_changed += list2b[list2a.find(l)]
			text_changed += u"マ"
		#半濁音を清音+半濁点に
		elif list3a.find(l) >= 0:
			text_changed += list3b[list3a.find(l)]
			text_changed += u"ミ"
		#その他の文字はそのままにする
		else:
			text_changed += l
	
	return text_changed
def main():
  lcd_init()
  text = u"ラズベリーパイ"
 text2 = katakana(text)
  lcd_string(text2,LCD_LINE_1)

対策②

上の方法は対処療法であまりよろしくないので、いっそ表示出来る文字を全て調べてみました。制御プログラムを見たところによると、文字の表示は「0x80 0x##」を送ることで出来るみたいです。「##」の部分に対応する文字を全て表示させてみた結果はこのようになりました。

ほぼShift-JISっぽいが少し違う所も…

どうしてもPCで出せなかった文字は説明にしてあります。英数字やカタカナはShift-JISに沿っていますが、「千」「万」「円」「√」などの文字もあります。これだけあれば色んな情報が表示出来そうですね。個人的には「℃」が使いたいのですが、「゜」「C」で表示出来そうです。

これらの文字を出すにはmain関数内に次のように書きます。

def main():
  lcd_init()
  
  #何行目に表示するか指定
  lcd_byte(LCD_LINE_1,LCD_CMD)
  
  #0xFC=「円」を表示
 lcd_byte(0xFC,LCD_CHR)

「円」を表示することができたでしょうか?
これだと一文字しか表示できず、また毎回表を見るのも面倒なので文字コードの順に並べたリストを作ってみました。文字コードnの文字がn番目に出てくるようにしてあります。これでカタカナを含む文字列を上の表の文字コードに変換して表示出来ます。

2019/11/3追記: 表示される文字のリストcodesの0x27のシングルクォーテーションが抜けていましたので修正しました。

def lcd_string_kana(message,line):
	#文字コードnの文字 = n番目の文字
	codes = u'線線線線線線線線線線線線線線線線                !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}→←                                 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜αäβεμσρq√陰ι×¢£nöpqθ∞ΩüΣπxν千万円÷ 塗'
	#変換する文字の辞書(ex.「バ」=「ハ」「゛」)
	dic ={u'ガ':u'カ゛',u'ギ':u'キ゛',u'グ':u'ク゛',u'ゲ':u'ケ゛',u'ゴ':u'コ゛',u'ザ':u'サ゛',u'ジ':u'シ゛',u'ズ':u'ス゛',u'ゼ':u'セ゛',u'ゾ':u'ソ゛',u'ダ':u'タ゛',u'ヂ':u'チ゛',u'ヅ':u'ツ゛',u'デ':u'テ゛',u'ド':u'ト゛',u'バ':u'ハ゛',u'ビ':u'ヒ゛',u'ブ':u'フ゛',u'ベ':u'ヘ゛',u'ボ':u'ホ゛',u'パ':u'ハ゜',u'ピ':u'ヒ゜',u'プ':u'フ゜',u'ペ':u'ヘ゜',u'ポ':u'ホ゜',u'℃':u'゜C'}
	message = message.ljust(LCD_WIDTH," ")
	lcd_byte(line, LCD_CMD)
	
	#濁音など、文字の変換
	message2 = ""
	for i in range(LCD_WIDTH):
		if ( message[i] in dic.keys() ):
			message2 += dic[message[i]]
		else:
			message2 += message[i]
	
	#文字コードのリストにある文字なら表示、それ以外は表示しない
	for i in range(LCD_WIDTH):
		if (codes.find(message2[i]) >= 0):
			lcd_byte(codes.find(message2[i])+1,LCD_CHR)
		elif (message2[i] != u' '):
			print "No such character!    :" + message2[i]

ついでに「ガ」などLCDでは二文字で表示する文字を「カ」「゛」に分解する処理も混ぜておきました。

全体のプログラムも載せておきますご参考までにどうぞ。

#coding:utf-8
import smbus
import time
import sys

# Define some device parameters
I2C_ADDR  = 0x3f # I2C device address, if any error, change this address to 0x3f
LCD_WIDTH = 16   # Maximum characters per line

# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

LCD_BACKLIGHT  = 0x08  # On
#LCD_BACKLIGHT = 0x00  # Off

ENABLE = 0b00000100 # Enable bit

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

#Open I2C interface
#bus = smbus.SMBus(0)  # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off 
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display
  time.sleep(E_DELAY)

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = the data
  # mode = 1 for data
  #        0 for command

  bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
  bits_low = mode | ((bits<<4) & 0xF0) | LCD_BACKLIGHT
  
  # High bits
  bus.write_byte(I2C_ADDR, bits_high)
  lcd_toggle_enable(bits_high)

  # Low bits
  bus.write_byte(I2C_ADDR, bits_low)
  lcd_toggle_enable(bits_low)

def lcd_toggle_enable(bits):
  # Toggle enable
  time.sleep(E_DELAY)
  bus.write_byte(I2C_ADDR, (bits | ENABLE))
  time.sleep(E_PULSE)
  bus.write_byte(I2C_ADDR,(bits & ~ENABLE))
  time.sleep(E_DELAY)

def lcd_string(message,line):
  # Send string to display

  message = message.ljust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):
    lcd_byte(ord(message[i]),LCD_CHR)

def lcd_string_kana(message,line):
	#文字コードnの文字 = n番目の文字
	codes = u'線線線線線線線線線線線線線線線線                !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}→←                                 。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜αäβεμσρq√陰ι×¢£nöpqθ∞ΩüΣπxν千万円÷ 塗'
	#変換する文字の辞書(ex.「バ」=「ハ」「゛」)
	dic ={u'ガ':u'カ゛',u'ギ':u'キ゛',u'グ':u'ク゛',u'ゲ':u'ケ゛',u'ゴ':u'コ゛',u'ザ':u'サ゛',u'ジ':u'シ゛',u'ズ':u'ス゛',u'ゼ':u'セ゛',u'ゾ':u'ソ゛',u'ダ':u'タ゛',u'ヂ':u'チ゛',u'ヅ':u'ツ゛',u'デ':u'テ゛',u'ド':u'ト゛',u'バ':u'ハ゛',u'ビ':u'ヒ゛',u'ブ':u'フ゛',u'ベ':u'ヘ゛',u'ボ':u'ホ゛',u'パ':u'ハ゜',u'ピ':u'ヒ゜',u'プ':u'フ゜',u'ペ':u'ヘ゜',u'ポ':u'ホ゜',u'℃':u'゜C'}
	message = message.ljust(LCD_WIDTH," ")
	lcd_byte(line, LCD_CMD)
	
	#濁音など、文字の変換
	message2 = ""
	for i in range(LCD_WIDTH):
		if ( message[i] in dic.keys() ):
			message2 += dic[message[i]]
		else:
			message2 += message[i]
	
	#文字コードのリストにある文字なら表示、それ以外は表示しない
	for i in range(LCD_WIDTH):
		if (codes.find(message2[i]) >= 0):
			lcd_byte(codes.find(message2[i])+1,LCD_CHR)
		elif (message2[i] != u' '):
			print "No such character!    :" + message2[i]

def main():
  # Main program block

  # Initialise display
  lcd_init()
  argv = sys.argv
  argc = len(argv)
  if (argc == 2):
  	lcd_string(katakana(argv[1].decode('utf-8')),LCD_LINE_1)
  elif (argc == 3):
  	lcd_string(katakana(argv[1].decode('utf-8')),LCD_LINE_1)
  	lcd_string(katakana(argv[2].decode('utf-8')),LCD_LINE_2)
  else:
  	#lcd_string(katakana(u"No data."),LCD_LINE_1)
  	lcd_string_kana(u"キオン:∞℃です",LCD_LINE_1)
  	lcd_string_kana(u"チョキン:1千万円",LCD_LINE_2)

if __name__ == '__main__':

  try:
    main()
  except KeyboardInterrupt:
    pass
  #finally:
    #lcd_byte(0x01, LCD_CMD)

カタカナや記号を含めたメッセージを表示することに成功しました!
色々表示させて遊んでみます。

データの表示、ユーザーインターフェースなど色々使えそう

人が通ったらシャッターを切る防犯カメラ

こんにちは。前回カメラストリーミングをやったのですが、ぶっちゃけずっと動画データをインターネットに流し続けるのもなんか無駄な感じがします。そこで今回は以前に作ったカメラストリーミングを応用して人が通ったときだけシャッターを切るカメラを作ってみたいと思います。

目的

人が通ったときだけシャッターを切るカメラを作ります。
防犯や入退室チェック、ペットや野生動物の観察にも使えて割と応用がききます。
切るタイミングも変えれば一定間隔で撮影してタイムラプス動画みたいなのも作れます。

準備するもの

fswebcamのインストール

カメラキャプチャにはfswebcamというソフトを使いましょう。いつもどおり次のコマンドでインストールします。

sudo apt-get install fswebcam

webカメラをラズパイに接続したら、撮影できるかどうかテストしてみましょう。ターミナルに次のコマンドを打つだけです。

fswebcam image.jpg
ppdr@raspberrypi:~ $ fswebcam image.jpg
--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Adjusting resolution from 384x288 to 352x288.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Writing JPEG image to 'image.jpg'.

勝手にカメラが認識されてとっても簡単です。image.jpgというファイルに画像が出力されていると思います。

ちなみに、よく使いそうなパラメータはこんな感じです。

-qメッセージを表示せずに実行
-l [seconds][seconds]秒ごとに撮影。タイムラプス撮影できそう。
-d [device]デバイス名を指定
-D [number][number]秒待ったあと撮影。起動直後だとうまく撮影できないときに。
-r [size]動画サイズを指定
–greyscale白黒で撮影
–no-banner画像に表示される時間などの文字を無くす
–overlaypngファイルを画像に重ねる。一部だけ隠したい時とかに便利??
–jpeg ,–pngファイル名を指定してjpg,pngで保存する。省略することもできます。

人感センサ

最も有名で作例も多いこちらの人感センサを使います。パーツ屋さんで500円くらいで買いました。

モジュールになっていて便利

このセンサ、両端の端子を5VとGNDにつなぐだけで真ん中の端子から出力を出してくれるスグレモノです。人などの熱源の動きを感知すると出力は3Vになり、動きがなくなると0Vになります。
ブレッドボードに次のように配線しました。

赤をラズパイの5V、黒をGND、オレンジを17番ピン、青を24番ピンに接続しています。ちなみに赤色LEDは確認用です。
さて、センサをテストしてみましょう。プログラミングは僕の愛用のpythonを使います。(コンパイルしなくても動く便利)

 

SENSOR_pin = 17
LED_pin = 24

import wiringpi as wp
import time

wp.wiringPiSetupGpio()
wp.pinMode(SENSOR_pin,0)
wp.pinMode(LED_pin,1)

state = 0
prev_state = 0
wp.digitalWrite(LED_pin,0)

try:
  while 1:
    state = wp.digitalRead(SENSOR_pin)

    if (state != prev_state):
      if state > prev_state:
        wp.digitalWrite(LED_pin,1)
      else:
        wp.digitalWrite(LED_pin,0)

    prev_state = state

    time.sleep(0.1)

except KeyboardInterrupt:
  wp.digitalWrite(LED_pin,0)
  print "KeyboardInterrupt"

今の出力の値(state)を読み込み、前回の値から立ち上がっていればLEDをオンに、立ち下がっていればオフにします。

ラズパイで防犯カメラを作る!(PHPで外部から起動)

前回の記事で自宅カメラの動画をストリーミング再生できるようになりました。しかし、前回のままだとmjpg-streamerをずっと起動しておく必要があるので、セキュリティ的にも不安です。外出先から見たいときだけ起動して終わったら消せるようになると便利なので、今回はそれに挑戦します。

目的

  • WEBページ上に自宅カメラの動画を埋め込む
  • 読み込んだときに起動・ボタンで終了できるようにする(phpを使用)

準備

前回の記事のセットアップでmjpg-streamerが起動できるようになっていることが前提です。
WEBページでPHPを動かすので、サーバの設定とポート開放をしておきましょう。

方法

こんな感じでcamera.phpをサーバのCGIが動くディレクトリに作成します。

<html>
<head>
<title>Camera Streaming</title>
</head>
<body>
camera streaming.<br>
<?php 
 $output = shell_exec('/home/ppdr/mjpg-streamer/mjpg_streamer -b -i "/home/ppdr/mjpg-streamer/input_uvc.so -f 10 -r 320x240 -d /dev/video0 -y -n" -o "/home/ppdr/mjpg-streamer/output_http.so -w /usr/local/www -p 5555 -c ppdr:password"');
 ?>
 
 <iframe width="320" height="240" src="http://192.168.0.24:5555/stream_simple.html" scrolling="no" frameborder="0"></iframe>

<?php 
 if(isset($_POST['stop'])){
 $output = shell_exec('kill -9 `pidof mjpg_streamer`');
 }
 ?>
 <form action="cam.php" method="post">
 <input type="submit" name="stop" value="stop" style="width:50%; height:10%" />
 </form>
</body>
</html>

一つ目の<?phpで始まる部分でシェルスクリプトを動かし、前回と同じコマンドでカメラを起動しています。

パラメータに-bでバックグラウンドで実行、-pでポート番号を指定、-cでパスワードを設定しています。
サーバから起動するなら実行ユーザはwww-data等になります。そのままでは権限が無いのでカメラが起動できませんので、/dev/vido0の「その他のユーザ」の権限を追加しておきましょう。

sudo chmod o+rw /dev/video0

<iframe>は他のページを埋め込むタグです。アドレスとポート番号を指定し、stream_simple.htmlを指定します。
これはmjpg-streamerの動画だけのページです。

2つめの<?phpでボタンを押したときのコマンドを指定しています。mjpg-streamerの公式ドキュメントにあるコマンドそのままです。

こんな感じで見れました。

ラズパイとWEBカメラで動画ストリーム配信ができる!

こんにちは。最近パソコン周りを整理していたらWEBカメラを見つけました。前からラズパイを使って防犯カメラ的なものを作れたらいいなーと思ってましたので、今回はWEBカメラでストリーミングしてみようと思います。

目的

・WEBカメラをラズパイに接続する
・動画をストリーミング配信する

今回使うカメラはこちらです。たまたま家にあっただけで、USB接続のカメラならもっと安いのでもいいと思います。

ELECOM PCカメラ 200万画素 筒型 UCAM-DLA200Hシリーズ

MJPG-streamerのインストール

Linuxでストリーミング再生できるソフトである「MJPG-streamer」を使います。このソフトはapt-getでインストールできないらしいので、「Subversion」でインストールします。まず必要なソフトをインストールしておきます。

sudo apt-get update
sudo apt-get install subversion libjpeg-dev imagemagick

続いて「MJPG-streamer」を取得してインストールします。

svn co https://svn.code.sf.net/p/mjpg-streamer/code/mjpg-streamer mjpg-streamer
cd mjpg-streamer
make
sudo make install

ストリーミング配信してみる

まずはWEBカメラがラズパイにきちんと認識されているか確認します。

ppdr@raspberrypi:~ $ lsusb
Bus 001 Device 006: ID 14cd:125c Super Top SD card reader
Bus 001 Device 007: ID 056e:700a Elecom Co., Ltd 
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Elecom…みたいな感じのカメラの名前が出ていれば認識されています。デバイスとしての名前も確認しておきましょう。

ppdr@raspberrypi:~ $ ls /dev

...
video0
...

私の名前はvideo0という名前で認識されました。どれがカメラかわからない方は抜いたり指したりして追加された名前を確認してみましょう。

次のコマンドでMJPG-streamerを起動しましょう。

cd ~/mjpg-streamer
sudo ./mjpg_streamer -i "./input_uvc.so -f 10 -r 320x240 -d /dev/video0 -y -n" -o "./output_http.so -w /usr/local/www -p 8080 -c ppdr:password"

-fはフレームレートで、1秒に何回動画を更新するかです。少ないほど通信量が小さくなります。
-rは動画のサイズです。あまり大きくしないほうが止まらずに見れます。
-pはストリーミングするポート番号です。webページやリモートデスクトップで使用している80や443など以外の番号ならなんでもいいでしょう。ポートが使用中だと次のようなエラーになります。

bind: Address already in use

WEB上に公開して誰でも見れてしまうとセキュリティ的に(プライバシー的に?)良くないので-cでパスワードをかけます。[ユーザ名]:[パスワード]で指定します。

エラーがでなければストリーミング配信が始まります。
ネットワークにつながっている他のPCのブラウザのアドレス欄に、[ラズパイのIPアドレス]:[ポート番号]を入力してエンターを押します。(例)192.168.0.7:8080

こんな感じのページに移動して、カメラの映像を見ることが出来ました!

使い道① 外出先から自宅カメラの様子を確認

防犯カメラ的な使い方をするなら、外出先から見れると便利です。その場合はVPN接続がおすすめです。ラズパイに外出先からVPNする方法は長くなるので次回解説します。

使い道② 動画をWEBに公開

動画を他の人に見てもらいたい場合は、

  • DDNSサービスを利用してラズパイに接続できるようにする
  • ルーターのポートを開放する

両方とも解説しているサイトが多数あるので、ここでの説明は割愛します。VPNで自分だけ見る場合はいいですが、外部に公開する場合はパスワードをきっちり設定しましょう。