juliusの辞書を作って認識速度&精度を爆上げする(簡単辞書作成ツール公開中)

結論

juliusのユーザ辞書作成が地味に面倒くさいので、言葉を入れたら辞書を生成してくれるサービスを作りました。

→julius辞書簡単作成サービス

なぜ作ろうと思ったか

名古屋工業大学が公開している音声認識ソフトのjuliusは、インストール時の辞書を使えば何も設定しなくともほとんどの言葉を認識してくれます。しかし沢山の言葉の中から文章を推定するため声を発してから結果が出るまで時間がかかり、また認識率もあまりよくありません。

そこで、認識したい言葉が決まっている場合には、ユーザが自分で言葉のリストを作ることができるようになっています。

やり方は公式ドキュメント等を見ればわかりますが、読み方をローマ字で指定したりと割と面倒くさいです。

書きかけです<(_ _)>

初心者のためのIoT&電子工作入門 ①準備編

はじめに

最近スマート家電やホームコントロールという言葉もすっかり身近になってきました。声をかけるだけでテレビやエアコンがオンする環境はもう当たり前になってきています。

しかし、家電コントロールのシステムや規格はまだまだ統一されておらず、家電自体にネットワーク機能がついたものを購入するか、テレビを付けたりなどのリモコン(赤外線)操作を行うデバイスを購入するしかないのが現状です。しかも、そのスマートリモコンシステムが1万円くらいしたりするので、便利かもしれないけど購入は迷う値段です。

家の中のすべての電化製品をコントロールするためには、自分でモータやセンサを購入し、回路を作成し、基盤にはんだ付けし、プログラムを書き込むという作業が必要になってきます。本記事は「好きな家電をコントロールしてみたい」「電子工作でなにか役に立つものを作りたい」という方に向けて、やるべきことを解説していきます。

対象とする人

  • 家庭内IoTシステムを安く、簡単に作りたい方
  • 無線IoTデバイスの自作に挑戦してみたい方
  • RaspberryPiやArduinoを買ってみたは良いが、使い道に迷っている方
  • 実際に使えるプログラミングの勉強を(少しだけ)したい方

家庭内IoTシステムの完成形

現在私が家で稼働させているシステムの構成図です。

本記事で解説するシステムの完成形

本記事では①~④を中心に解説していきます。最終的には声やスマホ、時刻やセンサによってテレビやエアコンを点けたり、ライトを操作することができることを目指します。

①~④は一応優先順位です。それぞれの役割と、作るのに必要な知識・技術を簡単に解説しておきます。

①無線マイコン(ESP8266)

  • 役割:
    WiFiによる通信で動作命令を受け取ったり、一定時間経過など何らかのタイミングによって、出力ピンから電圧を出力しモータやLEDを動作させます。
  • 必要事項:
    Arduinoプログラミング(C++)、HTTP通信、電子回路(モータ、LED、抵抗、トランジスタ等)

IoTのキモとなる、プログラミングとハードウェアを結びつけるマイコン(小型コンピュータ)です。本記事ではESP8266というモジュールを使います。このモジュールは最近では有名なArduinoにWiFi通信モジュールがついたものです。かなり安価(1個700円程度)で購入でき、一つの機能に対して一つのモジュールを使うのが基本です。

②サーバ(RaspberryPi等)

  • 役割:
    マイコンではできないような、Webから天気予報の取得や画像・音声の解析を行い、必要に応じてマイコンに動作命令を送ります。また、家の外からスマホでデータを送る際、通信を受け取るための玄関的な役割(=サーバ)を担います。
  • 必要事項:
    Pythonプログラミング、Linuxの知識、ネットワーク(サーバ)の知識

マイコンでできないような画像・音声解析や、複雑なネットワーク機能を担当します。ちゃんとしたコンピュータであり、できることは無限ですが、LinuxというOSを使っているため初心者には多くの知識が必要となります。本記事ではサーバの構築など一部だけ紹介します。

③ルータ

  • 役割:
    WiFiや有線LANで繋がれた機器同士の通信経路を作ります。家の中だけで使っている場合はSSIDとパスワードを各機器に登録するだけで接続できますが、サーバで家の外からの通信を受ける場合には少し設定が必要になってきます。
  • 必要事項:
    ネットワークの知識

少し設定すれば終わりですが、家の外からの通信を受けるときは適切に設定する必要があります。

④スマートスピーカー(Google Home)

  • 役割:
    音声を検出し、予め登録した言葉を検出したら何か処理をさせます。
  • 必要事項:
    特になし

Google Homeやアレクサ等最近流行りのスマートスピーカーですが、対応機器が少ないため持て余している方もいるかと思います。本記事では自作したIoTデバイスをスマートスピーカーで操作できるようにします。

上の図では家庭内だけで通信が完結しているように書きましたが、Google Homeを使うにはIFTTTというサービスを利用するのが一番簡単です。そのためには家の外からの通信を受け入れる必要があり、②のサーバが必要になってきます(IFTTTが外部にあるため)。

できるようになること

この記事を順番に見ていくとできるようになることを段階的にまとめてみます。

1.①無線マイコンのみ

無線マイコンのみでもTVやエアコンを付けることはできます。問題はどうやって動作命令を伝えるかですが、簡単にやるならスマホでchromeなどを開いて特定のURLを打ち込むことでマイコンを動作させることができます。

また、無線マイコンにセンサを付け、無線マイコン同士で通信させることも可能です。

無線マイコンのみでできること

2.①無線マイコンと②RaspberryPi(サーバなし)

RaspberryPiを導入することで、Webから天気予報や現在の気温等を取得することができます。よって人が入力しなくても無線マイコンを動作させることが可能です。

また、RaspberryPiに音声解析ソフトをインストールすればスマートスピーカーがなくても音声で家電を操作することができます。ですが、認識精度はやはりGoogle Homeが圧倒的に高精度ですので、ここで終わるのはもったいない気がします。

無線マイコンとRaspberryPi(サーバなし)でできること

3.①無線マイコンと②サーバと③ルータを設定

RaspberryPiをサーバ化すると、今まではできなかった家の外からの通信を受けることができます。これにより外出先からスマホでエアコンを付けることが可能になります。

また、2つのサービスを結びつける「IFTTT」を使うことで、さらに動作命令を送る幅が広がります。

無線マイコン+サーバでできること

4.①無線マイコン+②サーバ+③ルータ+④スマートスピーカー

最後にスマートスピーカーを追加すれば、好きな家電を声で操作できるようになります。IFTTTを使えばスマートスピーカーの利用は簡単なので、次の最終形にするまでに時間はほとんどかからないと思います。

無線マイコン+サーバ+スマートスピーカー

今回は最終的な全体システムの説明と、それぞれの構成がどのような役割を持つのかを解説しました。次回からはESP8266を使って動作命令を受け取る→LED、モータを動かす部分を解説していこうと思います。

つたない文章でしたが、次回もよろしくお願いします。

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

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

作りたいもの

前回のつづきです。

  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のパスワード");

  // 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をオンに、立ち下がっていればオフにします。