RPi-Cam-Web-Interfaceで監視カメラ製作(画面にアノテーション編)

前回Raspberry Pi Zero WにセットアップしたRPi-Cam-Web-Interfaceではデフォルトで画面上側に日付と時刻が表示されていますが、これに加えて文字を表示させることができます。残念ながら現在のところ2バイト文字は表示されませんが、用途によっては役に立ちそうです。

今回は外気を測定して得られた温度・湿度・気圧を表示させます。秋月電子通商から購入したbme280をRaspberry Pi zero W につないで計測する予定だったのですが、Zero W本体の発熱の影響を受けてしまい正確に気温を測ることができませんでした。ESP8266以上に熱を持つようです。センサーは雨などで水に濡れることもNGなので設置場所に相当の工夫が必要です。少々甘く考えていました。残念ですが今回はあきらめます。

代わりに以前作製したESP-WROOM-02使用、温度・湿度・気圧計のMQTTデータを使います。本当はこれをZero Wにリプレースしたかったのでした。

自宅Debian jessie サーバで稼働しているMQTTブローカ Mosquitteに気温,湿度,気圧が約5分間隔でパブリッシュされています。サブクライブするそれぞれのトピックは 気温:/home/garage/Temperature/state 湿度:/home/garage/Humidity/state 気圧:/home/garage/Pressure/stateです。

RPi-Cam-Web-Interfaceでは /dev/shm/mjpeg/user_annotate.txt の内容が画面に表示される仕組みになっています。mosquitte_clients を使ったshell scriptをcronで15分ごとに起動させてMQTTデータを取得しファイル書き込みを行います。

■ Zero Wに最新版のmosquitte_clientsをインストールします

$ wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
$ sudo apt-key add mosquitto-repo.gpg.key

$ cd /etc/apt/sources.list.d/

$ sudo wget http://repo.mosquitto.org/debian/mosquitto-jessie.list

$ sudo apt-get update
$ sudo apt-get install mosquitto-clients

■ shell script 作製

$ cd ~
$ mkdir scripts
$ cd !$
$ vim.tiny mqtt_get.sh
------------------------------ 以下の内容を作成 ------------------------------
#! /bin/bash

RESULT=`mosquitto_sub -h SERVER_ADDRESS -C 3 -t /home/garage/Temperature/state -t /home/garage/Humidity/state -t /home/garage/Pressure/state`

printf "T:%.1fC H:%.f%% %.fhPa" ${RESULT} > /dev/shm/mjpeg/user_annotate.txt

SERVER_ADDRESSはmosquittoが動いているサーバのアドレスです

■ 実行権を与えてcronに登録します

$ chmod u+x mqtt_get.sh
$ crontab -e
------------------------------ ファイルの最後に追記 ------------------------------
# Edit this file to introduce tasks to be run by cron.
・
・
・
# m h  dom mon dow   command

*/15 * * * * ~/script/mqtt_get.sh

■ RPi-Cam-Web-Interface側の設定

Camera Settings > Annotation (max 127 characters) > %aを記入して OK

文字のsize等も調整できます。

webから情報を取ってきて表示させるなど、応用ができそうです。

raspberry pi zero W にRPi-Cam-Web-Interfaceで監視カメラ製作


前々回セットアップしたraspberry pi zero Wにwebカメラを装着します。動かすソフトはraspberry piに特化したRPi Cam Web Interface (現在 version 6.3.3)を使います。

URL: http://elinux.org/RPi-Cam-Web-Interface

特徴です(webサイトより)

  • 高いフレームレートでの表示,センサーの全エリア使用可能
  • カメラの明るさややコントラスト等を調節可能
  • mp4形式でフルHDビデオ画像をSDカードに保存
  • 静止画およびコマ撮り静止画をSDカードに保存
  • SDカードに保存した動画や静止画をプレビュー可能、サムネイルを指定してダウンロード,zip形式で一括ダウンロード可能
  • 動体検知して録画をする機能、スクリプトを実行する機能
  • 録画動作のスケージュール機能
  • 古くなった録画ファイルの自動削除等々

とても高機能です。カメラからの動画をブラウザ等でモニタできて、それと同時にSDカードに記録できます。動体検知して静止画やコマ撮り静止画、動画いずれかを書込み可能です。同時にスクリプトも実行可能なので、以前失敗したメール送信なども再びチャレンジできそうですね。

また、時間に応じてカメラの設定、録画方式等を変えるなどのスケージュール機能も備えています。経度、緯度を指定して日の出、日没を算出するユニークな機能も備えています。

このソフトはRasiperry Piカメラのみに対応していてUSBカメラには対応していないみたいです。

■ インストール作業
Raspian Jessie Liteをインストールした後に、上記サイトのBasic Installationに書かれているとおりに作業して問題なくインストールできました。ただ、サイトの最後の方にコマ撮り録画をスムーズにするためにgstreamerをインストールした方が良いと書いてあります。sudo apt-get install gstreamer1.0 コマンドでインストールできますが、しばし時間がかかります。

新規インストールするならばFullパッケージのインストールをお勧めすると書かれていたので、Lite ではなくRASPBIAN JESSIE WITH PIXELをインストールする方法を改めて試してみました。前回の記事に書いたように、wpa_supplicant.confを作成し、sshを自動起動させるようにすると、すべてSSH経由で作業できます。

etcher

etcher


[インストールしたOS]
2017-03-02-raspbian-jessie.img
RASPBIAN JESSIE WITH PIXEL 
Image with PIXEL desktop based on Debian Jessie
Version:March 2017
Release date:2017-03-02
Kernel version:4.4
Release notes:Link

■ OS をEtcherなどでSDカードに書き込み後、セットして電源を入れます 

・パスワードを変更します
$ passwd

・不要なパッケージを削除します
$ sudo apt-get remove --purge wolfram-engine scratch minecraft-pi sonic-pi dillo gpicview penguinspuzzle oracle-java8-jdk openjdk-7-jre oracle-java7-jdk openjdk-8-jre
$ sudo apt-get clean
$ sudo apt-get autoremove

・パッケージを最新にします
$ sudo apt-get update
$ sudo apt-get dist-upgrade

raspi-configを起動します
$ sudo raspi-config

・タイムゾーン設定
4 Localisation Options> I2 Change Timezone > Asia > Tokyo

・テキストログインへ変更&自動ログインを禁止
3 Boot Options > B1 Desktop / CLI > B1 Console

・カメラサポートを有効にします
5 Interfacein Options > P1 Camera > Yes

・github リポジトリをクローンしてRPi_Cam_Web_Interfaceをインストールします

$ git clone https://github.com/silvanmelchior/RPi_Cam_Web_Interface.git
$ cd RPi_Cam_Web_Interface
$ chmod u+x *.sh
$ ./install.sh

途中、オプション選択画面になります。Webサーバは3種類の中からお好みで選択できますが、なるべく軽い方が良いかと思い,初めて使うlighttpdにしました。インストール後別のwebサーバにする時はクリーンインストールからやり直すことを勧めています。
また、Webの管理画面にBasic認証をかけるときはUser Passwordを入力します。
webcam インストール

AutoStartを選ぶと電源投入後自動起動します。またRPi_Cam_Web_Interfaceフォルダの中のstart.sh stop.shでプログラムのスタート・ストップ可能です。update.shでプログラムのアップデート,remove.shでプログラムの削除となります。

■ カメラを接続します
わたしが購入したのはHD 1080P Camera Module Board 5.0MP Wide Angle Fish Eye For Raspberry Pi A/Bこちらです。(後で気付きましたが赤外線カメラでした)

5MP(2592×1944pixel)画素で 1080p/30fps 720p/60fps 640×480/90 のVideoを配信できます。さらに魚眼レンズ付きといううたい文句でした。どんな映像がとれるのか楽しみです。付属のカメラケーブルではzeroに取り付けられないので、あらかじめPiromoni社からZero用のカメラケーブルを仕入れておきました。ところが…

一瞬目がくらみました


やってしまいました!!カメラケーブル取り付けの時にプラスチック製の留め具を破損してしました。賢明な皆様はこのようなドジはしないと思いますが、お気をつけください。留め具は1mm~2mmぐらいしか動く範囲がありません。取り扱いはソフトにお願いいたします。

ケースは余裕をもたせTAKACHIのプラスチックケース SW-100にしました。電源ですがmicro-usbとGPIOの+5V,Gndどちらに接続しても同じらしいです。今回はGPIOの方にに給電しますが、くれぐれも結線間違いしないようにせねばなりません。+-を逆にしたり他のGPIOピンに電圧をかけたりすると壊れてしまいます。また、未確認ですが電源は3.3Vの電圧でも動作可能らしいです。
ケースに取り付け
さて、カメラとZero Wを結線して電源を入れます。ブラウザでアクセスするアドレスはhttp://ipAddress:port/subfolderとサイトに書かれています。macとwindows10ではipアドレスだけではなくホスト名.localでもアクセスできます。デフォルトの80番でインストールしたならば:portも不要です。

ただ、上記の記述に反し、意外にも今回私のインストール作業では http://raspberrypi.local/ (サブディレクトリなし)が管理画面になっていました。webサーバにlighttpdを選んだ時だけみたいです。インストーラーのバグでしょう。webサーバにapach,nginxを選んでデフォルトでインストールしたときは、マニュアル通りhttp://raspberrypi.local/html/が管理画面になります。

また、http://raspberrypi.local/min.php へアクセスするとコントロールボタン等は表示されず、カメラ画像だけが表示されまます。

ちなみにchrome v59.0 ,IE11,opera v44.0,firfox v51.0ではホスト名でアクセスできるのですが、なぜかMS Edge 38.14393 ではIPアドレスでしかアクセスできませんでした。

アクセスした画面にエラー表示が出るときは,カメラケーブルの接続が悪いか又はraspi-configでカメラを有効にしていない可能性があります。再起動で回避できることもあります。

RPi-Cam-Web-Interface管理画面です

■ 使い方
設定できるパラメータがたくさんあって、私は全部の内容を理解できていません。わかるところを簡単にご説明します。設定した値は System > reset Setting で元に戻すことができるので色々トライされることをお勧めします。(注 : schedule settingsはリセットされません)

[record video start/stop] 現在のカメラ画像の動画録画をスタート・ストップ
[record image] 現在のカメラ画像の静止画をキャプチャー
[tamelapse start/stop]  現在のカメラ画像のコマ撮りをスタート・ストップ
[motion detection start/stop] 動体検知録画をスタート・ストップ(Camera Settingで Motion detect modeをInternalにする必要があります)

※ 4/3 追記 設定項目は多数ありますが、とりあえず Camera Setting > Motion detect mode : Internal 次にmotion detection start だけで動体検知-動画(Video)録画することが可能です。ただ、このままだとSDフラッシュカードがいずれ満杯になってしまいますので、古くなったファイルの削除(後述)だけは、設定しておいた方が良いでしょう。

[Camera Setting]
Camera Setting

Resolutions: Video及び静止画,解像度等を設定します
Timelapse-Interval (0.1…3200): コマ撮りの間隔(秒)
Video Split : 動画を分割します(秒)
Annotation (max 127 characters): アノテーション デフォルトで日時が表示されています
Buffer (1000… ms), default 0: 動体検知して録画されるまでにタイムラグがあるので画像のバッファリングを予めおこないます(ms)
Sharpness: シャープネス
Contrast : コントラスト
Brightness : 照度
Saturation : 彩度
ISO : ISO感度
Exposure Mode : 露光モード
White Balance : ホワイトバランス
Image Effect : 画像にイフェクトをかけることができます 
Rotation : 90度単位で画像を回転させます
Sensor Region : 画像センサーの使う範囲を指定
MP4 Boxing mode : h264動画をmp4に変換するモード
Motion detect mode : 動体検出モード

MP4 Boxing mode は動画ファイルのmp4変換作業の選択で、backgroundにするとブラウザで動画を再生することができますが、CPU処理を消費してしまい反応が遅くなります。offにするとブラウザでは見ることができずダウンロードしてからVLCメディアプレーヤーなどで再生することになります。その場合でもサムネイルは作成されます。

Motion detect mode : Internalにすると Motion Settingsのボタンが出現して動体検出の設定ができるようになります。

アノテーションはデフォルトで日時が表示されますが、スクリプトを組んで他の情報も表示させることが出来るみたいです。

[Motion Settings]
Motion Settings

動体検知のセッティングです。風で木の枝が動いたことなどで録画スイッチが入ってしまうとか、細かく考えると動体検知の調整は少々めんどうです。ただ、デフォルトのままでも動体検知しますので詳細にセットしたい時だけ値を変更するのが良いと思います。

Noise level : ノイズと動きを区別するために画像をフィルタします
Threshold : 画像変化の検知基準をpixl数でセットします
Clipping factor : Thresholdで変化のあったフレームを何個で有効とするかの値
MasK Image: 不検知部分のファイルを作成して、動体検知の範囲を絞ります。ファイル形式はPGMで画像と同じ幅,高さでなければいけません。
Changes Frames to start : スタートとなる動体検知のフレーム数
Still Frames to stop : ストップとなる動体不検知のフレーム数

自分の理解ではスレッショルドを超えたフレーム数が(Changes Frames to start)×(Clipping factor)個になった時にスタートとなるのではないかと思います。誤っていたらすみません。

[System]

シャットダウンやリブート、[Camera Setting]と[Motion Settings]パラメータのリセットができます。Default Stream とMJPEG Streamの切り替えもありますが、何が違うのか私にはわかりません。

[Edit schedule settings]
Edit schedule settings
指定した時間にカメラの感度等などの設定や記録する方式を変更するなど、コマンドを実行することができます。Command referenceのボタンを押すと実行できるコマンドの説明が出てきます。デフォルトは24時間休みなく動体検知したときに動画を記録する設定になっています。管理画面トップのmotion detection start ボタンをクリックすると動体検知録画がはじまります。起動と同時に動体検知する場合はこの画面のPeriod Start欄に md 1を書き込みます。

MOtion Start Stopの欄ですが、コマ撮りをしたいときはca 1をtl1 ,ca 0 を tl 0 に変更します。同様に静止画1枚を撮りたいときときはca 1を
imに変更します。

Days Su-Sa 欄のチェックは日曜日から土曜日に対応して、曜日による動作指定です。チェックがない曜日はその右にかかれている動作を行いません。

● 録画した画像ファイルがSDカード容量を超えてしまうのを防ぐためにファイル削除機能があります。2通りの設定方法があります。
1. PurgeVideo_Hours PurgeLapse_Hours PurgeImage_Hours それぞれ動画、コマ撮り静止画、静止画が指定時間以上古いファイルを消去します。48を指定すると2日以上たったファイルは消去されます。
2. ファイルの最大の記録スペースを指定して、それを超えた時に古いファイルから消去する方法。
PurgeSpace_ModeExを下記のいずれかで選んで値を PurgeSpace_Levelに設定します。
Min Space % (書込み可能ファイル容量の最小値 パーセント)
Max Usage % (使用済ファイル容量の最大値 パーセント)
Min Space GB (書込み可能ファイル容量の最小値 ギガバイト)
Max Usage GB (使用済ファイル容量の最大値 ギガバイト)

このファイル削除作業はManagement_Interval に設定された秒数(デフォルトでは1時間)ごとに行います。
同時に実行させたいスクリプトがもしあれば、Management_Command を指定します。このスクリプトはインストールしたサブディレクトリ下にあるmacroディレクトリに保存されていて、実行可能でなければなりません。

● DayModeはAll Day(全日)の他に Sun Based (日の出、日の入りの時間を基準) Fixd Time(特定の時間指定)があります。

Sun Based DayMode

Sun Based DayMode

Sun Based DayMode は一日をDawn(明け方),Day(日中),Dusk(夕暮れ),Night(夜中)の4つに区切ります。日の出を基準にDawnStart_Minutes(負の値) DayStart_Minutes を設定して明け方の時間を定めます、日の入りを基準にDayEnd_Minutes DuskEnd_Minutes を設定して夕方の時間を定めると、4つの時間帯が定まります。

日の出、日の入りの時刻は自動的に計算されますが、そのために Latitude(緯度) Longtitude(経度) GMT時間との差を指定します。東京の例をとるとLatitude=35,Longtitude=139,GMTOffset=9 になると思います。

Period Start欄にその時間が来たら実行させたいコマンドを設定します。

Fixed Times DayMode

Fixed Times DayMode

Fixed Times は時間固定で指定です。

●またログファイルの操作もここで行うことが出来ます。

[Download Videos and Images]Download Videos and Images

録画したファイルのサムネイルが表示されます。プレビューをしたり、必要なファイルのダウンロードを行います。先ほど述べたようにMP4 Boxing modeがBbackgroundになっていないと、動画のプレビューはできません。

■ 感想
とても使いやすく、動体検知録画もできるので非常に便利です。以前作ったEESP-WROOM-02+Arducamではパワーが足りませんでしたが、今回はとってもいい感じです。まだ、長時間使っていませんが、Zeroのスペックでデータの通信がwifiでも十分実用になりそうです。素晴らしいソフトを作ってくださった作者の方に感謝します。

■ またもや失敗談
夜、蛍光灯の下で撮影する時は問題なかったのですが、昼間日光の元で撮影すると全く色合いがおかしいことに気付きました。最初は理由がわからず、一生懸命色彩の調整を行い無駄な努力をしていましたが、このカメラはナイトビジョンだったのですね。他の方の記事を読んで、ようやく理解できました。自然光では色が変で、画像もぼけた感じになってしまいます。写る角度(範囲)は広いですが、選択間違いでした。とりあえず映像はわかりますので、しばらく使ってみたいと思います。

自然光では色が変です

部屋を暗くして赤外線LEDライトをあててみました

Raspberry Pi Zero W キーボード・ディスプレイ接続無しでOSセットアップ

Raspberry Pi Zero Wにキーボードとディスプレイを接続せずにWifi経由SSHでRaspbian OSセットアップします。

前回の記事にRaspberry Pi Zero Wのセットアップにキーボードとディスプレイ接続が必要だと書きましたが、接続しなくてもRaspbian Jessieをインストールできます。慣れた方ならこちらの方法が簡単です。こちらに書かれていましたのでご紹介します。

Wifi接続に必要なwpa_supplicant.confファイルを予め作成しておくことと、デフォルトで停止しているSSHサーバを起動させることが肝です。

■ wpa_supplicant.confの作成
EtcherなどでRaspbianディスクイメージを書き込んだSDカードですが、/bootディレクトリはFAT32なのでWindowsやMacからアクセスすることができます。
また、無線LAN接続に必要なwpa_supplicant.confをあらかじめここに作製しておけばRaspbianは起動時にこのファイルを/etc/wpa_supplicant/wpa_supplicant.confにコピーしてくれます。ファイル形式はUTF-8,改行コードLFなので、エディタはサクラエディタやTeraPadなどが良いと思います。

wpa_supplicant.conf

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
        ssid="SSID"
        psk="PASSWORD"
        key_mgmt=WPA-PSK
        scan_ssid=1
        }

私の所の無線LAN暗号方式がWPA2-PSKで、またANY接続不許可の設定なので
key_mgmt=WPA-PSKとscan_ssid=1の2行が挿入されています。

■ SSHサーバの自動起動

上記と同じく/bootディレクトリに拡張子無しのsshをいう名前のファイルを作成します。これだけでSSHサーバが自動起動します。

作成ファイル

作成ファイル

■ SDカードをZero Wに差して電源を入れます。立ち上がりにしばらく時間がかりますが、その後にWifi経由でSSHログインできるようになります。
ホスト名:raspberrypi.local userID: pi password: raspberryです。

今回のようにipアドレスではなくホスト名(raspberrypi.local)でアクセスするためにはマルチキャストDNS名前解決が必要なのですが,Windows10以外のWindowsマシンでは, Bonjour for Windowsがインストールされている必要があると思います。

Raspberry Pi Zero W をゲットしました

英国 PIMORONI社からRaspberry Pi Zero Wを購入しました。3月10日に注文して到着まで約2週間でした。https://shop.pimoroni.com/products/raspberry-pi-zero-w

Raspberry Pi Zero & Pi Zero W

Raspberry Pi Zero & Pi Zero W ちょっと見では区別がつきません

Raspberry Pi Zeroに下記2つの機能がプラスされています。
On-board Wireless LAN – 2.4 GHz 802.11 b/g/n (BCM43438)
On-board Bluetooth 4.1 + HS Low-energy (BLE) (BCM43438)

私が注文した時は本体が£8.00 GBP 送料£5.50 GBP 計 £13.50 GBP ,1ポンド約140円ですから日本円にして約1,900円で手に入れることができました。PayPalで決済できます。

ただ、いまPIMORONI社のサイトを見ると、なぜか本体の値段が£9.60 GBPに上がっています。物は同じだと思いますがなぜでしょうね。

Raspberry Pi Zero W  本体 ,本体+アダプタ,本体プラスアダプタ+ケースのセット

Raspberry Pi Zero W  本体 ,本体+アダプタのセット,本体+アダプタ+ケースのセット


スイッチサイエンス等のサイトでも販売されているのですが現在は品薄になっているようです。アマゾンも一応検索してみました。やはりZero Wは出品されていませんでした。Wなしの無印Zeroが出品されていたので、何の気なしに見てみると、驚くほど高値です。
海鮮料理店で時価という値札を見る時がありますが、それと似た感覚ですね。この世の中のすべての品物が、いずれそのようになってしまうのでしょうか?

Raspberry Pi を持っている方はご存知だと思いますが、動作させるためには本体の他に必要な物があります。

接続コネクタ

接続コネクタ


・micro-SD メモリ (私は8GByteを使っています。ちなみにRaspbian Jessie LITEをインストール直後は1GByte弱をシステムが使用します)
・mini-HDMI ディスプレイケーブル
・micro-B USBケーブル
・micro-B USBケーブル+5V電源

それからPC,ディスプレイ(HDMI入力),USBキーボード,無線LAN環境がセットアップのために必要です。一度セットアップしてしまえば、その後はSSHで接続することができますので ディスプレイとキーボードは外して運用が可能です。

アダプタの干渉

アダプタの干渉


注意すべき点はHDMIのコネクタが標準のものではなくmini-HDMIタイプであることと、USBも電源も双方micor-BタイプのUSBコネクタであることです。変換プラグを使うときは物理的な干渉も考慮する必要があります。
PIMORONI社で本体と同時にRaspberry Pi Zero Adaptor Kit を購入するもの良い方法だと思います。USB micro-B <=> USB TypeA変換アダプタ1個, mini-HDMI <=> 標準タイプHDMI変換アダプタ1個, ヘッダーピン(はんだ付けタイプ)が入っています。本体とセット(Zero W + Adaptors PIM256)で£14です。
上記の本体+アダプターにケースを付けたセット(Zero W + Adaptors + Pibow Zero W PIM257)が£19です。このケースは切り出された4枚のアクリル板を4層に重ねてネジで止めたものです。うまく説明できませんがケースの組立作業は簡単なパズルのようです。
※3/26追記 上記に反してしまいますが、ディスプレイとキーボードをつながずにOSセットアップも可能でしたこちらに記事を書きました

またカメラをつなぐ予定のある方はついでにCamera Cable – Raspberry Pi Zero editionを検討されるのが良いと思います。zeroのCSI camera connector はPi3のコネクターより幅が狭いです。

PIMORONIチェックアウト

PIMORONIチェックアウト


それともう一つ、カートに入れた商品のチェックアウトですが、途中必ずお勧め商品のページを通っていくようになっています。次のページに移るためのボタンが最下行なのでわかりずらかったです。(これも悩みました)

OSはRASPBIAN JESSIE LITEをインストールしました。インストールに使用したPCはWindows 10 ver1607 (OSビルド14393.53)です。備忘の為に作業内容を記入しておきます。

■ 今回インストールしたファイル
2017-03-02-raspbian-jessie-lite.img 
RASPBIAN JESSIE LITE
Minimal image based on Debian Jessie
Version:March 2017
Release date:2017-03-02
Kernel version:4.4

etcher

etcher


zipファイルを解凍しETCHER https://etcher.io/ にてmicro-SD Flashカードへ書き込みます。書込み・ファイルのベリファイ・終了後アンマウント,一連の動作が自動で行われます。とても簡単です。

Raspberry Pi Zero Wへ書き込んだmicro-SDを差し込み、キーボードとディスプレイをつなぎます。私のHDMIアダプタは精度が悪いのか、差し込み方の具合によって信号が送られないことがあります。しっかりと確認し電源を入れます。Wではない無印Zeroの時は無線ドングルの為にOSインストールの時はUSBハブが必要でしたが今回は不要なので、すっきりとしていますね。

初回起動時にパーテーションの拡大作業が自動で行われます。ログインプロンプトが出るので。userID: pi password: raspberry でログインします。

■ raspi-config を起動します

$ sudo raspi-config
raspi-config

raspi-config

・ホスト名設定
デフォルトはraspberrypiなのでホスト名を変更します。今回は仮にpizeo-wとします。
2 Hostname > 使える文字の説明 > ホスト名をpizero-w に変更

・タイムゾーン設定
4 Localisation Options> I2 Change Timezone > Asia > Tokyo

・日本語キーボードを選択します
4 Localisation Options >I3 Change Keyboadrd Layout > Generic 101-key PC > Other > Japanese > Japanese > The default for the keyboard layout > No compose key

・wi-fi country設定
4 Localisation Options >I4 Change Wi-fi Country > JP Japan > OK

・ローケル (今回はskip 日本語フォントがインストールされないと文字化けします。)

・SSHサーバ起動設定
5 Interfacinf Options > P2 SSH > YES

raspi-config をFinishで終了させます。次の画面でリブートが吉でしょう。
■ rootのパスワードの変更とユーザの新規作成

$ sudo passwd root

Enter new UNIX password:*******
Retype new UNIX password:*******
passwd: password updated successfully

・ユーザを追加 (ユーザー名は仮に tarouとします)

$ sudo adduser tarou
Adding user `tarou' ...
Adding new group `tarou' (1000) ...
Adding new user `tarou' (1000) with group `tarou' ...
Creating home directory `/home/tarou' ...
Copying files from `/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for tarou
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] y

スーパーユーザを設定します(最終行に tarou ALL=(ALL:ALL)ALL を追記)

# sudo visudo
.
.
. 
tarou ALL=(ALL:ALL)ALL  

一旦ログアウトした後tarouでログインします

$ exit

$ sudo -s
We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for tarou:
root@pizero-w:/home/tarou#

スパーユーザーになれることを確認後 pi ユーザー削除

# userdel pi

■無線LAN設定 

・vim.tiny か nanoを使い/etc/wpa_supplicant/wpa_supplicant.conf を編集

# vim.tiny /etc/wpa_supplicant/wpa_supplicant.conf
 country=JP
 ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
 update_config=1
 network={
        ssid="*****"    
        psk="******"    
        key_mgmt=WPA-PSK 
        scan_ssid=1    
       }

上記*****にはSSIDとパスフレーズをそれぞれ記入します。私の所の無線LAN暗号方式がWPA2-PSKで、またANY接続不許可の設定なので、さらに下2行を記入します。

・/etc/network/interfaces の編集 ==> DHCPでIPアドレス取得の場合は不要です 
※3/26追記
こちらの記事に書かれていますが、Raspbian Jessie でDHCPの場合は/etc/network/interfacesの編集は必要ないみたいです。

リブート後無線LANが繋がるか確認

# shutdown -r now
.
# ifconfig 
.
.
wlan0     Link encap:Ethernet  HWaddr xx:xx:xx:xx:xx:xx
 ここ---> inet addr:xxx.xxx.x.xx  Bcast:xxx.xxx.x.255  Mask:255.255.255.0
          inet6 addr: fe80::xxxx:xxxx:xxxx:xxx/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:9152 errors:0 dropped:5752 overruns:0 frame:0
          TX packets:1204 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:1277232 (1.2 MiB)  TX bytes:145163 (141.7 KiB)

ipアドレスが正常か確認します。つづいてパッケージを更新します。

# apt-get update
# apt-get upgrade

PC のTeratermなどターミナルソフトからSSHでログインできるか確かめます。ホストpizero-w.localでアクセスできればOKです。

ssh ターミナルからアクセス

ssh ターミナルからアクセス

■ IPV6を無効にします

# echo "net.ipv6.conf.all.disable_ipv6 = 1" >> /etc/sysctl.conf

まだ他に諸々ありますが、今日の所はここらへんで。

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (5) ドップラーセンサーモジュールの追加

秋月電子通商で購入したドップラーセンサーモジュールを前回製作したArduCAMの回路に追加しました。

■ドップラーセンサーモジュール(24GHz)DIP化セット NJR4265 J1(24GHz)

OpenHABにはメールを送るアクションがあります。訪問者があった時、ドップラーセンサーで感知して画像をメールする一連の動作を考えました。が、しかし。結論から言いますと、上手く動作しませんでした。今回は失敗ですが、備忘のために作業した記録を残します。

Arducam回路図 02

Arducam回路図 02

NJR4265J1は説明によると、24GHz帯マイクロ波ドップラーによる移動体検知を行う人感センサ(最大10m)で、単一電源3.3V~5.0V動作です。シリアルインターフェースはCMOSレベル、10KΩ抵抗でプルアップされてます。

ArduCAM + NJR4265J1写真

ArduCAM + NJR4265J1

OTAで書き込みができるようになりましたので、一度スケッチを書き込んだ後には書き込みのためにシリアルポートを使わなくてすみます。そちらにNJR4265J1を繋ぎました。

ボーレート:9600bps データビット:8bit パリティ:奇数(odd) ストップbit:1 フロー制御:なし で通信します。また、後からコマンドを送りセンサーの感度調節を行うことができます。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

#include <Wire.h>
#include <ArduCAM.h>
#include <SPI.h>
#include "memorysaver.h"

#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#include <PubSubClient.h>

// Enabe debug tracing to Serial port.
#define DEBUGGING

// Here we define a maximum framelength to 64 bytes. Default is 256.
#define MAX_FRAME_LENGTH 64

// Define how many callback functions you have. Default is 1.
#define CALLBACK_FUNCTIONS 1


const int CS = 16;


long lastMsg = 0;
char msg[50];
int value = 0;
String inputString = "";         // a string to hold incoming data  //serial
boolean stringComplete = false;  // whether the string is complete  //serial

int wifiType = 0; // 0:Station  1:AP
const char* ssid = "xxxxxxx"; // Put your SSID here
const char* password = "xxxxxxx"; // Put your PASSWORD here
const char* mqtt_server = "xxx.xxx.xxx.xxx"; // mqtt broker IP Address

IPAddress ip(192, 168, 11, 1);     //ESP-WROOM-02 IP Address 
IPAddress gateway(192, 168, 11, xxx); // Gateway IP Address
IPAddress subnet(255, 255, 255, 0);  // Subnet Mask

WiFiClient espClient;
PubSubClient mqtt_client(espClient);

ESP8266WebServer server(80);

ArduCAM myCAM(OV2640, CS);

void start_capture() {
  myCAM.clear_fifo_flag();
  myCAM.start_capture();
}

void camCapture(ArduCAM myCAM) {
  WiFiClient client = server.client();

  size_t length = myCAM.read_fifo_length();
  if (length >= 393216) {
    //    Serial.println("Over size.");
    return;
  } else if (length == 0 ) {
    //    Serial.println("Size is 0.");
    return;
  }

  myCAM.CS_LOW();
  myCAM.set_fifo_burst();
  SPI.transfer(0xFF);

  if (!client.connected()) return;
  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: image/jpeg\r\n";
  response += "Content-Length: " + String(length) + "\r\n\r\n";
  server.sendContent(response);

  static const size_t bufferSize = 4096 ; // 1024
  static uint8_t buffer[bufferSize] = {0xFF};

  while (length) {
    size_t will_copy = (length < bufferSize) ? length : bufferSize;
    SPI.transferBytes(&buffer[0], &buffer[0], will_copy);
    if (!client.connected()) break;
    client.write(&buffer[0], will_copy);
    length -= will_copy;
  }

  myCAM.CS_HIGH();
}

void serverCapture() {
  start_capture();
  //  Serial.println("CAM Capturing");

  int total_time = 0;

  total_time = millis();
  while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK))
  {
    delay(1);
  }


  total_time = millis() - total_time;
  //  Serial.print("capture total_time used (in miliseconds):");
  //  Serial.println(total_time, DEC);

  total_time = 0;

  //  Serial.println("CAM Capture Done!");
  total_time = millis();
  camCapture(myCAM);
  total_time = millis() - total_time;
  //  Serial.print("send total_time used (in miliseconds):");
  //  Serial.println(total_time, DEC);
  //  Serial.println("CAM send Done!");
}

void serverStream() {
  WiFiClient client = server.client();

  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
  server.sendContent(response);

  while (1) {
    start_capture();
    delay(1);
    while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK))
    {

    }
    size_t length = myCAM.read_fifo_length();
    if (length >= 393216) {
      //      Serial.println("Over size.");
      continue;
    } else if (length == 0 ) {
      //      Serial.println("Size is 0.");
      continue;
    }

    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
    SPI.transfer(0xFF);

    if (!client.connected()) break;
    response = "--frame\r\n";
    response += "Content-Type: image/jpeg\r\n\r\n";
    server.sendContent(response);

    static const size_t bufferSize = 4096  ; //  1024
    static uint8_t buffer[bufferSize] = {0xFF};

    while (length) {
      size_t will_copy = (length < bufferSize) ? length : bufferSize;
      SPI.transferBytes(&buffer[0], &buffer[0], will_copy);
      if (!client.connected()) break;
      client.write(&buffer[0], will_copy);
      length -= will_copy;
    }
    myCAM.CS_HIGH();

    if (!client.connected()) break;
  }
}

void handleNotFound() {
  String message = "Server is running!\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(200, "text/plain", message);

  if (server.hasArg("ql")) {
    int ql = server.arg("ql").toInt();
    myCAM.OV2640_set_JPEG_size(ql);
    //    Serial.println("QL change to: " + server.arg("ql"));
  }
}

void mqtt_callback(char* topic, byte* payload, unsigned int length) {
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.print("\r\n");
}

void mqtt_reconnect() {
  while (!mqtt_client.connected()) {
    if (mqtt_client.connect("ESP8266Client_02")) {
      mqtt_client.publish("outTopic", "hello world");
      mqtt_client.subscribe("/home/gate/motion/com");
    } else {
      delay(5000);
    }
  }
}
void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    if (inChar != '\n' && inChar != '\r') {
      inputString += inChar;
    }
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

void setup() {
  uint8_t vid, pid;
  uint8_t temp;
#if defined(__SAM3X8E__)
  Wire1.begin();
#else
  Wire.begin();
#endif
  //  Serial.begin(115200);
  //  Serial.println("ArduCAM Start!");

  // set the CS as an output:
  pinMode(CS, OUTPUT);

  // initialize SPI:
  SPI.begin();
  SPI.setFrequency(4000000); //4MHz

  //Check if the ArduCAM SPI bus is OK
  myCAM.write_reg(ARDUCHIP_TEST1, 0x55);
  temp = myCAM.read_reg(ARDUCHIP_TEST1);
  if (temp != 0x55) {
    //    Serial.println("SPI1 interface Error!");
    while (1);
  }

  //Check if the camera module type is OV2640
  myCAM.wrSensorReg8_8(0xff, 0x01);
  myCAM.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
  myCAM.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
  if ((vid != 0x26) || (pid != 0x42)) {
    //    Serial.println("Can't find OV2640 module!");
    while (1);
  } else {
    //    Serial.println("OV2640 detected.");
  }

  //Change to JPEG capture mode and initialize the OV2640 module
  myCAM.set_format(JPEG);
  myCAM.InitCAM();
  myCAM.OV2640_set_JPEG_size(OV2640_640x480);
  myCAM.clear_fifo_flag();
  myCAM.write_reg(ARDUCHIP_FRAMES, 0x00);

  if (wifiType == 0) {
    // Connect to WiFi network
    //    Serial.println();
    //    Serial.println();
    //    Serial.print("Connecting to ");
    //    Serial.println(ssid);

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    //set static ip part
    WiFi.config(ip, gateway, subnet);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      //      Serial.print(".");

    }
    //    Serial.println("WiFi connected");
    //    Serial.println("");
    //    Serial.println(WiFi.localIP());
  } else if (wifiType == 1) {
    //    Serial.println();
    //    Serial.println();
    //    Serial.print("Share AP: ");
    //    Serial.println(ssid);

    WiFi.mode(WIFI_AP);
    WiFi.softAP(ssid, password);
    //    Serial.println("");
    //    Serial.println(WiFi.softAPIP());
  }

  // Start the server
  server.on("/capture", HTTP_GET, serverCapture);
  server.on("/stream", HTTP_GET, serverStream);
  server.onNotFound(handleNotFound);
  server.begin();
  //  Serial.println("Server started");

  ArduinoOTA.begin();
  //    Serial.println("OTA Ready");
  //    Serial.print("IP address: ");
  //    Serial.println(WiFi.localIP());


  Serial.begin(9600, SERIAL_8O1);
  // reserve 200 bytes for the inputString:  //serial
  inputString.reserve(200);  //serial
  // NJR4264J default threshold setting
  delay(5000);               // START UP
  Serial.print("@SM550");   // SENSOR SENSITIVITY (Leaving 1-999 Integer)
  Serial.print("\r\n");
  Serial.print("@SP550");   // SENSOR SENSITIVITY (coming 1-999 Integer)
  Serial.print("\r\n");
  // setup_wifi();
  mqtt_client.setServer(mqtt_server, 1883);
  mqtt_client.setCallback(mqtt_callback);



}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();

  if (!mqtt_client.connected()) {
    mqtt_reconnect();
  }
  mqtt_client.loop();

  serialEvent(); //call the function
  if (stringComplete) {
    if (inputString == "@C") {
      inputString = "coming";
    }
    else if (inputString == "@L") {
      inputString = "leaving";
    }
    else if (inputString == "@N") {
      inputString = "no_movement";
    }

    inputString.toCharArray(msg, 50);
    mqtt_client.publish("/home/gate/motion/state", msg);
    inputString = "";
    stringComplete = false;
  }

  long now = millis();
  if (now - lastMsg > 30000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world from motion_sensor #%ld", value);
    mqtt_client.publish("outTopic", msg);   // keep_alive to  mosquitte
  }

}

前回のスケッチにNJR4265J1の制御を付け加え、MQTTブローカーを経由してセンサーの値をやりとりするようにしました。シリアルインターフェースを通して送られていたメッセージをすべてコメントアウトです。

Topic /home/gate/motion/state へ NJR4265J1は "coming" "leaving" "no_movement" をパブリッシュします
Topic /hoem/gate/motion/com   へメッセージを送るとNJR4265J1を制御できます。

NJR4265J1は移動体が接近すると ”@C“ また移動体が離反すると “@L” 動きが無いときは”@N“を送ってきます。 (正確にはそれぞれの文字の後ろにCR:キャリッジリターン と LF:ラインフィードが付加されます) 今回は上記欄のように”coming” “leaving” “no_movement”と文字列変換しました。以前使用したMQTTlensで動作の様子を見ることができます。

MQTTlens 01

MQTTlens 01

MQTTlens で MQTTブローカーへ接続後 トピック /home/gate/motion/state をsubclibeするとセンサーから送られてきた情報が表示されるようになります。
(こちらにMQTTlensの使い方 )

同様に トピック /home/gate/motion/com へ ”@SP?” とコマンドを送ります。これは現在設定されている近接感度(閾値)を応答する命令です。図MQTTlens 02に示されている例では700が応答されています。今回のスケッチでは感度の初期設定値を近接と離反、双方とも550にしましたが、1~999の範囲で設定することができます。NJR4265J1の取り扱い説明書は秋月電子のweb siteからダウンロードすることができます。
http://akizukidenshi.com/download/ds/jrc/NJR4265J1_Rev00.pdf

MQTTlens 02

MQTTlens 02

続いてOpenHABの設定です。まず、メールを扱えるようにするためにopenhab-addon-action-mail をインストールします。(Debian Wheezyでの手順になります)

# apt-get update
# apt-get upgrade
# apt-get install openhab-addon-action-mail

次に /etc/openhab/openhab.cfg を編集します。今回はGmailのメールサーバを使って送る設定にしました。

######################## Mail Action configuration ####################################
#
# The SMTP server hostname, e.g. "smtp.gmail.com"
#Gmail の送信メールサーバ
mail:hostname=smtp.gmail.com

# the SMTP port to use (optional, defaults to 25 (resp. 587 for TLS))
#Submissionポート指定
mail:port=587

# the username and password if the SMTP server requires authentication
#Gmailのアドレス(ユーザー名)
mail:username=xxxxxx@gmail.com  
#パスワード
mail:password=xxxxxx

# The email address to use for sending mails
#Gmailのアドレス(または自分が使っている別のメールアドレス)
mail:from=xxxxx@gmail.com

# set to "true", if TLS should be used for the connection
# (optional, defaults to false)
# TLSを使う
mail:tls=true

# set to "true", if POP before SMTP (another authentication mechanism)
# should be enabled. Username and Password are taken from the above
# configuration (optional, default to false)
#mail:popbeforesmtp=

itemsファイルに追記です /etc/openhab/configurations/my_home.items

String motion_gate "Motion Sensor [%s]"  {mqtt="<[broker:/home/gate/motion/state:state:default]"}

最後に新規rulesファイルを作成します。センサーが"coming"を通知してきたときにメールを送るようにしました。見よう見まねでconcurrentロックを加えましたが、間違っているかもしれません。

/etc/openhab/configurations/rules/sendmail.rules

import java.util.concurrent.locks.ReentrantLock

var java.util.concurrent.locks.ReentrantLock lock  = new java.util.concurrent.locks.ReentrantLock()

rule "Send Mail"

when
  Item motion_gate changed to coming
then
  lock.lock()
  try{
     sendMail("xxxx@hogehoge.com","Motion Sensor","There is motion","http://192.168.11.1/capture")
} finally{
     lock.unlock()
}
end

送付先等は sendMail("宛先メールアドレス","題名","本文","画像URL")のように指定します。
また、ESP-WROOM-02に設定したIPアドレスを便宜上192.168.11.1とします。

OpenHABをリスタートして動作確認をしますが、最初に述べた通り次の2点で動作不良でした。

(1) センサーが反応してから画像キャプチャまでのディレイが長すぎて目的の絵が撮れない。メール送信までの一連の動作にかなり時間がかかる。
(2) センサーの感度が悪い、誤作動が頻発する。

(1)についてはある程度予測はしていました。画像添付メールは送られて来ますが、残念ながら毎回肝心な人物が写ってないです。写ってもらうためには5~10秒以上カメラの前に居てもらわなくてはなりません。ArduCAM + esp8266の性能限界なのでしょうね。また、mosquittoとOpenHABが動いているDebianサーバのスペックも十分でない可能性があります。今回はメール送信はあきらめてリアルタイム映像表示に専念します。将来別のWebCAMを購入することがあったら、また試してみます。(早くRaspberry Pi Zeroが安く手に入るようにならないかな。 )

電波が干渉してしまった?

電波が干渉してしまった?

(2)の原因が当初わからなかったのですが、どうやらESP-Wroom-02とNJR4265J1間の電波干渉が原因みたいでした。考えてみれば双方ともGHzの高周波を使用しています。昔なら私みたいな素人が扱える周波数帯ではなかったハズです。(技術の進歩はすごいですね) 感度設定に依ると思いますが、最低でも30cm程度離して設置しないと誤作動してしまうようです。

その後、先日作成した気温・気圧・湿度測定のボードをMQTT仕様にして画像と共に表示させるようにしました。(こちらも一波乱ありましたが...)

PCからOpenHAB 画面の表示にはChrome のアドオン Mini OpenHAB Controller が便利です。

OpenHABからの表示

OpenHABからの表示

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (4) ArduCam(web camera)とOTA (無線でスケッチ書き込み)

OpenHABでは画像も表示されることがわかりましたので、何か良いものはないかと物色していたところ、EbayでArducamを見つけました。
ArduCAM01
Arducam Mini module Camera Shield w/ 2 MP OV2640 for Arduino UNO Mega2560 board

200万画素のCMOSイメージセンサー OV2640搭載でインターフェースはSPIとI2CでAruinoにつなげることができるみたいです。3.3V動作可能なのでESP-WROOM-02とも相性が良さそうですね。製造元のサイトにESP8266との配線図が載っていますので無線WEBカメラが簡単に出来そうです。(実際は私の不徳で長い道のりでした)

ArduCAMのサイト
http://www.arducam.com/arducam-supports-esp8266-arduino-board-wifi-websocket-camera-demo/

Arducam回路図01

Arducam回路図01

ArduCAMライブラリーのインストールを行います。
https://github.com/ArduCAM/Arduino
こちらのサイトから DownloadZIPでファイルをダウンロードして、解凍したできたArduCAMディレクトリをそのままArduinoのlibraryフォルダにコピーします。Arduino IDE から

ファイル > スケッチの例 > ArcuCAM > ESP8266 > ArduCAM_mini_Ov2640_Capture

をロードします。

const char* ssid = "SSID"; // Put your SSID here
const char* password = "Password"; // Put your PASSWORD here

自分のWifi環境に合わせ上記二つのを書き換えてスケッチを走らせてみます。ちなみに今回私は Arduino IDE ver 1.6.7 , ボードマネージャー esp8266 by ESP8266 Community Ver 2.1.0 で作業を行いました。

実行してみると、シリアルモニタの表示は

ArduCAM Start!
Can't find OV2640 module!

残念ながらArduCAM MINI(ArduCAM-M-2MP)には何種類かリビジョン違いがあるみたいで、私の物はこのスケッチでは動かないようです。スケッチの一部を変更します。

if ((vid != 0x26) || (pid != 0x41)){
Serial.println("Can't find OV2640 module!");

ここの行の 0x41を0x42に書き換えて、ようやくOV2640イメージセンサーを認識するようにしました。

動作の確認方法です。直接ブラウザでアドレス指定しても良いのですが、ダウンロードして展開したファイル

libraryes > ArduCAM > examples > ESP8266 > ArduCAM_Mini_OV2640_Capture > html > index.html

こちらにある index.html(またはvideo.html)をブラウザで立ち上げます。起動時シリアルモニタに表示されるDHCPで割り振られたESP-WROOM-02のIPアドレスをCamera IP Address欄にインプットした後、下のいずれかの解像度のボタンをクリックするとキャプチャした画面が表示されます。

画像が乱れてしまった

画像が乱れてしまった

…という訳だったのですが、残念!ちゃんと表示されません。

https://github.com/ArduCAM/Arduino/issues/21 こちらに2つの解決法が載っていました。

1. ESP8266 libraryのコードを手直しする方法 ボードマネージャでesp8266をインストールした場合は下記の場所のコードを一部書き換えます。(書き換え内容はURL先記述文)

ユーザー > (User NAME) > AppData(隠しフォルダ) > Local > Arduino15 > packages > esp8266 > hardware > esp8266 > (version NO.) > libraryes > ESP8266WiFi > SRC以下

2.スケッチ中のバッファサイズを書き換える方法 (4096を1024へ)

static const size_t bufferSize = 1024; //4096 2ケ所

今回は 2. 案を採用しました

画面表示速度ははっきり言って遅いです。640×480の解像度では大体2~3秒で1画面描画でしょうか。0.5~0.3fpsぐらいだと思います。(後付けの注釈:書いている私が言うのはおかしいですが、実用性だけを考えると別のwebcamを選択した方が良いと思います。)

ArduCAMとESP-WROOM-02の転送速度がネックになっているようです。ただ、320×240の解像度では割と早く表示されます。また、1.案でスケッチをコンパルすると速度が速くなるかもしれません。(後付けの注釈:気持ち早くなりました)

ようやく表示されました

ようやく表示されました

さて私個人的には、過去さんざん失敗してきたOTA(Over The Aire : WiFi経由のスケッチ書き込み)に挑戦です。Arduino IDE ver 1.6.7より正式対応になったようです。こちらtakehikoshimajimaさんのサイトに詳しく書かれています。

上記サイトの文中に記述されてますが、下記プログラムがインストールされていることが必要です。

■ Arduino IDE ver 1.6.7と Board : esp8266 by ESP8266 Community ver 2.0.0 (or later)
■ Python 2.7

さらに、マルチキャストDNSを使ってホストの名前解決という事なので、この他にWindowsマシン + Arduino IDEの構成ではBonjourも必要だと思います。iTunesがインストールされていれば同時に入っているかもしれません。
Bonjour
サンプルスケッチに少し手を加え、スタティック IP AdressとOTAを設定します。


#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

#include <Wire.h>
#include <ArduCAM.h>
#include <SPI.h>
#include "memorysaver.h"

#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

// Enabe debug tracing to Serial port.
#define DEBUGGING

// Here we define a maximum framelength to 64 bytes. Default is 256.
#define MAX_FRAME_LENGTH 64

// Define how many callback functions you have. Default is 1.
#define CALLBACK_FUNCTIONS 1

const int CS = 16;

int wifiType = 0; // 0:Station  1:AP
const char* ssid = "SSID"; // Put your SSID here
const char* password = "PASSWORD"; // Put your PASSWORD here

IPAddress ip(192, 168, 11, 1); //ESP-WROOM-02 IP Address
IPAddress gateway(192, 168, 11, xxx); //Gateway IP Address
IPAddress subnet(255, 255, 255, 0); //SubNet MASK

ESP8266WebServer server(80);

ArduCAM myCAM(OV2640, CS);

void start_capture() {
  myCAM.clear_fifo_flag();
  myCAM.start_capture();
}

void camCapture(ArduCAM myCAM) {
  WiFiClient client = server.client();

  size_t length = myCAM.read_fifo_length();
  if (length >= 393216) {
    Serial.println("Over size.");
    return;
  } else if (length == 0 ) {
    Serial.println("Size is 0.");
    return;
  }

  myCAM.CS_LOW();
  myCAM.set_fifo_burst();
  SPI.transfer(0xFF);

  if (!client.connected()) return;
  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: image/jpeg\r\n";
  response += "Content-Length: " + String(length) + "\r\n\r\n";
  server.sendContent(response);

  static const size_t bufferSize = 1024 ; // 4096
  static uint8_t buffer[bufferSize] = {0xFF};

  while (length) {
    size_t will_copy = (length < bufferSize) ? length : bufferSize;
    SPI.transferBytes(&buffer[0], &buffer[0], will_copy);
    if (!client.connected()) break;
    client.write(&buffer[0], will_copy);
    length -= will_copy;
  }

  myCAM.CS_HIGH();
}

void serverCapture() {
  start_capture();
  Serial.println("CAM Capturing");

  int total_time = 0;

  total_time = millis();
  while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK))
  {
    delay(1);
  }


  total_time = millis() - total_time;
  Serial.print("capture total_time used (in miliseconds):");
  Serial.println(total_time, DEC);

  total_time = 0;

  Serial.println("CAM Capture Done!");
  total_time = millis();
  camCapture(myCAM);
  total_time = millis() - total_time;
  Serial.print("send total_time used (in miliseconds):");
  Serial.println(total_time, DEC);
  Serial.println("CAM send Done!");
}

void serverStream() {
  WiFiClient client = server.client();

  String response = "HTTP/1.1 200 OK\r\n";
  response += "Content-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n";
  server.sendContent(response);

  while (1) {
    start_capture();
    delay(1);
    while (!myCAM.get_bit(ARDUCHIP_TRIG, CAP_DONE_MASK))
    {

    }
    size_t length = myCAM.read_fifo_length();
    if (length >= 393216) {
      Serial.println("Over size.");
      continue;
    } else if (length == 0 ) {
      Serial.println("Size is 0.");
      continue;
    }

    myCAM.CS_LOW();
    myCAM.set_fifo_burst();
    SPI.transfer(0xFF);

    if (!client.connected()) break;
    response = "--frame\r\n";
    response += "Content-Type: image/jpeg\r\n\r\n";
    server.sendContent(response);

    static const size_t bufferSize = 1024  ; //  4096
    static uint8_t buffer[bufferSize] = {0xFF};

    while (length) {
      size_t will_copy = (length < bufferSize) ? length : bufferSize;
      SPI.transferBytes(&buffer[0], &buffer[0], will_copy);
      if (!client.connected()) break;
      client.write(&buffer[0], will_copy);
      length -= will_copy;
    }
    myCAM.CS_HIGH();

    if (!client.connected()) break;
  }
}

void handleNotFound() {
  String message = "Server is running!\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(200, "text/plain", message);

  if (server.hasArg("ql")) {
    int ql = server.arg("ql").toInt();
    myCAM.OV2640_set_JPEG_size(ql);
    Serial.println("QL change to: " + server.arg("ql"));
  }
}

void setup() {
  uint8_t vid, pid;
  uint8_t temp;
#if defined(__SAM3X8E__)
  Wire1.begin();
#else
  Wire.begin();
#endif
  Serial.begin(115200);
  Serial.println("ArduCAM Start!");

  // set the CS as an output:
  pinMode(CS, OUTPUT);

  // initialize SPI:
  SPI.begin();
  SPI.setFrequency(4000000); //4MHz

  //Check if the ArduCAM SPI bus is OK
  myCAM.write_reg(ARDUCHIP_TEST1, 0x55);
  temp = myCAM.read_reg(ARDUCHIP_TEST1);
  if (temp != 0x55) {
    Serial.println("SPI1 interface Error!");
    while (1);
  }

  //Check if the camera module type is OV2640
  myCAM.wrSensorReg8_8(0xff, 0x01);
  myCAM.rdSensorReg8_8(OV2640_CHIPID_HIGH, &vid);
  myCAM.rdSensorReg8_8(OV2640_CHIPID_LOW, &pid);
  if ((vid != 0x26) || (pid != 0x42)) {
    Serial.println("Can't find OV2640 module!");
    while (1);
  } else {
    Serial.println("OV2640 detected.");
  }

  //Change to JPEG capture mode and initialize the OV2640 module
  myCAM.set_format(JPEG);
  myCAM.InitCAM();
  myCAM.OV2640_set_JPEG_size(OV2640_640x480);
  myCAM.clear_fifo_flag();
  myCAM.write_reg(ARDUCHIP_FRAMES, 0x00);

  if (wifiType == 0) {
    // Connect to WiFi network
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    //set static ip part
    WiFi.config(ip, gateway, subnet);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");

    }
    Serial.println("WiFi connected");
    Serial.println("");
    Serial.println(WiFi.localIP());
  } else if (wifiType == 1) {
    Serial.println();
    Serial.println();
    Serial.print("Share AP: ");
    Serial.println(ssid);

    WiFi.mode(WIFI_AP);
    WiFi.softAP(ssid, password);
    Serial.println("");
    Serial.println(WiFi.softAPIP());
  }

  // Start the server
  server.on("/capture", HTTP_GET, serverCapture);
  server.on("/stream", HTTP_GET, serverStream);
  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println("Server started");

  ArduinoOTA.begin();
  //  Serial.println("OTA Ready");
  //  Serial.print("IP address: ");
  //  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
}


最初は通常通りシリアルインターフェースからスケッチを書き込みます。再起動してESP-WROOM-02がネットワークに繋がった後に再度 Arduino IDEを立ち上げると、新たにネットワークインターフェースが表示されます。

新しい無線経由のポートが表示されます

新しいWiFi経由のポートが表示されます


インターフェースを切り替えてスケッチをアップロードしたのですが残念ながら、

[ERROR]: No response from device 

エラーメッセージが表示されてうまくいきません。なかなか一筋縄ではいかないようです。

かなり悩みましたが結局、失敗の原因はPCにインストールされているセキュリティソフトのファイヤーウォールでした。外部から始まった内向きの通信がブロックされているのが問題だったようです。(盲点でした)

すべてクリアして初めてOTAが成功したときは感動モノでした。スピードも速いですし、すごく便利ですね。デプロイ(設置)した後でもスケッチを簡単に書き換えられそうです。(注:WiFi経由のポートを選択した時はシリアルモニタは使えないようです。)

画像はブラウザから直接下記のアドレスでもアクセスできます。(便宜上 ESP-WROOM-02のIPアドレスを192.168.11.1とします)

http://192.168.11.1/capture :静止画
http://192.168.11.1/stream :動画

これをOpenHABから表示させるようにします。本来OpenHABではmjpeg動画も表示できるのですが、今回のESP-WROOM-02 + ArduCAM では表示可能なものの長時間の運用がどうもうまくいきませんでした。仕方なく静止画を数秒ごとに再表示させることにします。

# cat /etc/openhab/configurations/sitemaps/my_home.sitemap

sitemap my_home label="Main Menu"
{
Frame label="MQTT" {
Switch item=lamp1 label="Gatepost Lamp"

Frame label="" {
Image url="http://192.168.11.1/capture" refresh=1000
}
}

1000ミリ秒(= 1 S)ごとに画面をリフレッシュする指定なのですが、残念ながら私のOpenHab 1.8.1では呼び出した時のままで、再描写されませんでした。これにも相当悩みましたが、こちらに書かれているとおりコードを変更して解決しました。(ディレクトリは Debian wheezy の場合です)

(※ 3/20追記 この方法で画像はリフレッシュされるようになりましたが、他のアイテムがリフレッシュされないようです。一長一短です。)

(※ 3/27 追記 Debian wheezy にて apt-get でインストールされるOpenHABのバージョンが 1.8.2になったようです。下記の作業は必要ないと思います。)

https://community.openhab.org/t/please-help-test-fix-for-image-and-chart-refresh-in-classic-web-ui/8106

# /etc/init.d/openhab stop
# mkdir ~/temp
# cd /usr/share/openhab/server/plugins
# mv org.openhab.ui.webapp_1.8.1.jar ~/temp/
# cd /usr/share/openhab/addons/
# wget https://dl.dropboxusercontent.com/u/4286376/classicui-full-image-chart-refresh/org.openhab.ui.webapp-1.8.2-SNAPSHOT.jar
# /etc/init.d/openhab start
OpenHABから表示されました

OpenHABから表示されました

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (3)

前回までで門柱灯(gatepost lamp)のリモート操作ができるようになりましたので、いよいよ自動化の設定に入ります。

とりあえず次のような動作を考えました。

  1. 昼行灯(ひるあんどん)は良くないので明るいときは無条件にOFF
  2. 日没後、暗くなったらON
  3. PM9:00にOFF

EEスイッチを置き換えたので他の項目は実現できなくても1の項目だけは何としてもクリアしたい所です。

前回までに光センサーの値はESP-WROOM-02側からMQTT Brokerの /home/gatepost/sensor/stateへパブリッシュされるようになっていますので、今回はOpenHAB側のconfigを一部追加して表示させるようにします。本来光センサーの値をリアルタイムに表示してもあまり意味をなさないのですが、今回は自動化の設定値を求めるために一時的に行います。(シリアルモニタで見るより簡単・手間いらずです)

■itemsの編集

$ sudo vi /etc/openhab/configurations/items/my_home.items
Group All

Switch lamp1 "gatepost Lamp" (all){mqtt=">[broker:/home/gatepost/lamp/com:command:on:ON],>[broker:/home/gatepost/lamp/com:command:off:OFF],<[broker:/home/gatepost/lamp/state:state:default]"}

/* 以下を追記します */
Number light_sensor "光センサー (Light Sensor) [%d ]"  {mqtt="<[broker:/home/gatepost/sensor/state:state:default]"}

■sitemapの編集

$ sudo vi /etc/openhab/configurations/sitemaps/my_home.sitemap
sitemap my_home label="Main Menu"
{
   Frame label="MQTT" {
   Switch item=lamp1 label="Gatepost Lamp"
   /* 以下を追記します */
   Text item=light_sensor
  }  
}

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-1

光センサーの値がスイッチの下の行に表示されるようになりました。さて、さらにこれを進めて一日の推移をグラフにして表示してみます。

 

 

 

 

先日紹介した https://openhardwarecoza.wordpress.com/2015/03/30/openhab-mqtt-arduino-and-esp8266-part-5-1-graphing-sensor-data/ こちらのサイトを参考に設定を行います。

■rrd4j add-onをインストール

$ sudo apt-get install openhab-addon-persistence-rrd4j

上記はdemoサイトが設定されていれば追加されるみたいです。そのときは不要です。

■persistenceファイルの編集

$ sudo vi /etc/openhab/configurations/persistence/rrd4j.persist
Strategies {
        // for rrd charts, we need a cron strategy
        everyMinute : "0 * * * * ?"
}

Items {

light_sensor : strategy = everyMinute

}

■sitemapの編集

$ sudo vi /etc/openhab/configurations/sitemaps/my_home.sitemap
sitemap my_home label="Main Menu"
{
   Frame label="MQTT" {
   Switch item=lamp1 label="Gatepost Lamp"
   Text item=light_sensor
   
   /* 以下を追記します */
   Frame label="フォトトランジスタの出力をAD変換した値" {
        Chart label="light_sensor" item=light_sensor period=d refresh=30000
  }
 }
}

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-2

再起動後しばらくするとグラフが描かれ始めます。period=d としたので24時間のグラフになりました。このようにOpenHABではとても簡単にグラフを描くことができます。

こちら に書かれていますがデータをグループで指定して複数のグラフを書くこともできるようです。ただ残念ながら現在のところ複数のY軸の設定は出来ないようなので異なるカテゴリーの値を一つのグラフに表示させることは難しいようです。また、設定が悪いのかバグなのかは分かりませんが、私のiOSアプリではなぜかグラフが表示されません。(サファリなどのブラウザからは表示されます)

観察の結果、だいたいセンサーの値が25を下回ったら電灯をONにすれば良さそうです。電灯をOFFにする条件は、センサーの値の変動を考慮して50を超えた値で無条件にOFFとしましょう。先ほども申しましたが光センサーの値のグラフはあまり意味をなさないので、将来的には気温のグラフを表示させようと思っています。

余談ですが、当初は光センサーのフォトトランジスタの向きが良くなかったようで、暗いときの感度が低くNGでした。センサーを外付けにして調整したころ改善しました。

(失敗) 間接的に外の光を計測していたので感度が不足してしまった

(失敗) 間接的に外の光を計測していたので感度が不足してしまった

さて、ようやく準備ができました。いよいよルールを設定する番です。OpenHABではJAVA言語を発展させたXtendという言語でルールを記述するようです。JAVAすら初めてな私にとっては、ハードルが極めて高そうですね。でもそこは、いつもどおり「見よう見まね」で切り抜けていきましょう。

https://github.com/openhab/openhab/wiki/Rules やhttps://github.com/openhab/openhab/wiki/Samples-Rules を参考にして、だいたい次の事がわかりました。

● rules ファイルはテキストで記述され、

[インポート:Imports]   [変数宣言:Variable Declarations]   [ルール:Rules]

3セクション構成となっている

rules file 例 (https://github.com/openhab/openhab/wiki/Samples-Rules#how-to-turn-on-light-when-motion-detected-and-is-dark 記載)

    var Number counter = 0
    var Number lastCheck = 0

    rule "corLightOn"
    when   
            Item corMotion changed from OFF to ON or
            Item corFrontDoor changed from CLOSED to OPEN
    then   
            counter = counter + 1
            if(corBright.state < 40) {
                    sendCommand(corLightCeil, ON)
            }
    end

    rule "corLightOff"
    when   
            Time cron "0 * * * * ?"
    then   
            if(lastCheck == counter) {
                    counter = 0
                    lastCheck = -1;
                    sendCommand(corLightCeil, OFF)
                    sendCommand(corMotion, OFF)
            } else {
                    lastCheck = counter
            }
    end  

上記の通りルールは一つのファイルに複数記入しても良いみたいですね。正直言って [インポート:Imports] は全く理解不能ですが、この例のように簡単なルールでは必要ないみたいです。恐らくArduino IDEで言えばライブラリみたいなものでしょう。まぁ、動作しなかった時に考えましょう。何か読み込ませばきっと動くでしょう。たぶん。

● ルールが起動するトリガは次の3つ

  1. Itemベーストリガ —– コマンドが発行されたりステータスが変更になったときに起動
  2. Timeベーストリガ —– 時間指定による起動
  3. Systemベーストリガ—- システムの開始時などに起動

Itemベーストリガ 
    Item  received command []
    Item  received update []
    Item  changed [from ] [to ]

Timeベーストリガ
    Time is midnight
    Time is noon
    Time cron ""

TimeベーストリガではLinuxのcronライクな表現で指定できるのですね。ただ こちら を見るとlinux の cronにくらべ機能が拡張されているようです。左から順に

秒 分 時 日 月 曜日 年(オプション)

秒や年が指定できるようです。persistenceファイルの編集の時にも出てきました。ワイルドカードの*はともかくとして、?マークは見慣れないですね。どうやら日と曜日はどちらかを指定して指定しない方に?をつけるようです(たぶん)。という訳で16時台から20時台の15分毎に起動させたければ “0 0/15 16-20 * * ?” と指定すれば良いですね。

以上をふまえて、当初計画した自動化ルールを作成します。

  1. 光センサー(Item:light_sensor)の値を調べ、50より大きいときは門柱灯(Item:lamp1)へOFFのコマンドを送る。[1分おきに実行]
  2. 光センサーの値が25より小さい時はONのコマンドを送る。[16時から20時45分まで15分おきに実行]
  3. 門柱灯へOFFのコマンドを送る。[21時0分0秒に実行]

サンプルを見ると日没時間等を計算式で求める事も可能なようですね。まったくもってアメージングです。でも、あまり欲をかいてしまうと失敗してしまいますので、身の丈にあった設定ファイルにしておきます。

■gatepost.rulesの作成

$ sudo vi /etc/openhab/configurations/rules/gatepost.rules
rule "LightOFF_in_daylight"
    when
                Time cron "0 * * * * ?"
    then
                if (light_sensor.state > 50){
                   sendCommand(lamp1, OFF)
                }

rule "LightON_after_sunset"
    when
                Time cron "0 0/15 16-20 * * ?"
    then
                if (lamp1.state == OFF && light_sensor.state < 25){
                   sendCommand(lamp1, ON)
                }
                

rule "LightOff_at_21"
    when
                Time cron "0 0 21 * * ?"
    then
                    sendCommand(lamp1, OFF)
    end

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

11月22日 東京での日の入り時間は16時31分でした。 16時45分に門柱灯が自動的に点灯しましたので、どうやらうまく行ったようです。ログを見てみます。

$ sudo cat /var/log/openhab/events.log

....
2015-11-22 16:28:55 - light_sensor state updated to 54
2015-11-22 16:29:00 - lamp1 received command OFF
2015-11-22 16:29:00 - lamp1 state updated to OFF
2015-11-22 16:29:00 - light_sensor state updated to 53
....
2015-11-22 16:29:55 - light_sensor state updated to 50
2015-11-22 16:30:00 - light_sensor state updated to 51
2015-11-22 16:30:05 - light_sensor state updated to 46
....
2015-11-22 16:44:56 - light_sensor state updated to 24
2015-11-22 16:45:00 - lamp1 received command ON
2015-11-22 16:45:00 - lamp1 state updated to ON
2015-11-22 16:45:01 - light_sensor state updated to 24
2015-11-22 16:45:06 - light_sensor state updated to 24
2015-11-22 16:45:11 - light_sensor state updated to 25
....

光センサーの値が5秒間隔で記録されています。必要以上に間隔が短すぎるかもしれませんね、後で調整することにします。

光センサーを外付けにしました。

光センサーを外付けにしました

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (2)

とある日 工作をしている私に家人から質問がありました

家人:「一体その品物に、いくら掛かっているのですか?」私:「2,000~3,000円ぐらいかな」家人:「また、また、尾戯れを」私:「本当ですってば」

しかし嘘ではありません。一番高価な部品はesp-wroom-02モージュールで、これが約1000円。AC100VーDC5V電源とケースが各400円程。SSRキットが250円。+その他部品ですから大体3,000円以内で収まっていると思いますよ。

(しかしながら、原因不明で電源が壊れてしまい廃棄処分になった1号機の部品代や将来使うかもしれないと思い購入してしまったドップラーセンサーモジュール、スケッチ書き込みのための超小型USBシリアル変換モジュール、予備の部品、etcを合計すると、それなりの金額になっているのは間違いない事実ではあります。)

回路図

回路図

回路図は基本的に前回と同じですが次の変更を加えました。AC100V-DC5V電源にe-bayから仕入れた Ultra-compact power module HLK-PM01(出力5v 0.6A)を使い、電灯線制御にソリッドリレー。 さらに将来の自動化を目的にフォトトランジスタによる照度センサー。そしてローカルで電灯を制御するためにSW3を新設しました。

【デプロイした2号機】USBケーブルの先端にUSBシリアル変換モジュール。

【デプロイした2号機】USBケーブルの先端にUSBシリアル変換モジュール。スケッチを書き込むために仮にAC100Vへ配線して通電しています。 スケッチの書き込み時は100Vに通電せずUSB側から給電するようにします。(100vに通電して書き込みを行うと最悪の場合PCを破損する可能性があります。)

技術もないのに省スペースを追求しすぎて1号機は失敗してしまいました。教訓を活かして2号機は秋月電子の 【ユニバーサル基板 Bタイプ(95x72mm)】とケースにホームセンターで仕入れた【PVKボックス(プールボックス) 未来工業】に集積度を上げず余裕を持って作ることにしました。

既存の電灯配線に制御回路を繋げなくてはいけませんが、これが結構大変です。制御回路を配置する場所が問題となります。通常、電灯は図の様に配線されているので設置候補として図中A,B,Cの場所があります。Bは一番良い場所なのですが、大抵見えない屋根裏とかに隠蔽されているので設置や保守が大変です。

Cからは何とか回路を引き出すことが出来ますが2芯ケーブルの線間電圧が100VではないのでESP-WROOM-02のための電源が別途必要になります。

残るはAの電灯直下ですが、こちらは雨風にさられることになりますので、そちらの対策が必要になります。

配線01

たまたま私の家の門柱灯はEE-Switch【暗くなると自動的にONになる】が露出配線で繋がっていて、この場所にEE-Switchと入れ替える形で制御回路を設置することができました。

(ちなみにこの配線工事は自宅であっても法律上、電気工事士の有資格者が行う必要があるので注意が必要です。)

家庭などのAC配線では線間電圧(100V)という概念のほかに対地電圧という概念が重要になります。アースされているのが接地側で感電の危険性が高いのが非接地側となります。

電気器具もなるべく感電しないようになっていて、白熱電球の人が触れやすいネジの部分の電極は接地側になるよう配線されています。同じ理由で差し込むときガイドになるコンセントの左側が右側より大きく、接地側に接続されています。

また、配線の色は原則として接地側が白又は緑で、非接地側には他の色を使う決まりになっています。(場合により例外もあります)
EE_switch
テスターや未来工業の「検電くん」を使い調査した結果、自宅のEE-スイッチに来ている3芯ケーブルも白が接地側、黒が非接地側、赤が電灯に繋がるように配線されていました。

ESP-WROOM-02のArduinoスケッチは前回のものを少し手直しいたしました。変更点は以下の通りです。

●アナログインターフェースPIN 【TOUT】に繋がっている照度センサのフォトトランジスタの値を analogread(A0) 命令で0Vから1Vを0~1024の値に変換して読み込みます。

手持ちの部品、回路では全く光が入らない状態でも0にならず、大体20ぐらいの値が読み込まれました。この値はMQTT Broker /home/gatepost/sensor/stateへパブリッシュしてOpenhabに伝えます。

【失敗してしまった1号機】

失敗してしまった1号機

失敗した1号機はこの値が不安定だったのでいろいろ弄っている内に、なぜか電源が壊れてしまったのでした。

●DHCPからスタティックIPアドレスに変更しました。DHCPで受け取ったアドレスにはリース期間が設定されているので、その期間が過ぎると一旦接続が切れてしまいます。

●手元で電灯を操作するために 【IO4】にSWを新設しました。当初、割り込みを使ってスケッチを記述しようとしたのですが、読み込んでいるライブラリと相性が悪いのかうまく動作しませんでした。

今回はOPENHARDWARE.CO.ZAさんのコードを使わせていただきました。ただ、このスケッチではMQTTサーバに繋がっていないと動作しません。改善の余地があります。

●ESP-WROOM-02がバックグラウンドでTCP等の処理をする時間を確保するためにloop()の最後に delay(10) を挿入しました。

●SSRをスイッチするためにトランジスタを1段入れましたのでON/OFFの極性が逆になりました。またloop()中 mills()がオーバーフローしたとき(起動してから5ヶ月後)挙動がわからないのでabs()で絶対値の差で比較しました。

/*
 Basic ESP8266 MQTT example

 This sketch demonstrates the capabilities of the pubsub library in combination
 with the ESP8266 board/library.

  2015/10/12 改変
  - subscribes to the topic "/home/gatepost/lamp/com", printing out any messages
   - If the characters of the topic "/home/gatepost/lamp/com" are "ON", switch ON the ESP Led, "OFF" switch off
 
  2015/11/08 改変
  - phototransistor (照度センサー)  analogRead(A0) 
  - Static IP
  - a physical button on IO4 (turns the device on/off)
    --> https://openhardwarecoza.wordpress.com/2015/04/05/openhab-mqtt-arduino-and-esp8266-part-5-2-combined-switch-and-sensor-module-based-on-arduino/
*/ 


#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "your_SSID";
const char* password = "your_Password";
const char* mqtt_server = "192.168.xx.xx";  // mqttサーバのアドレスを設定します。
IPAddress ip(192,168,xx,xx);
IPAddress gateway(192,168,xx,xx);
IPAddress subnet(255,255,255,0);
 
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
int buttonPin = 4;
boolean buttonState = LOW; //stroage for current button state
boolean lastState = LOW;//storage for last button state
boolean OutputPinState = LOW;
int analog_val;
char illumination[5];

void setup() {
  pinMode(5, OUTPUT);     // Initialize the 5 pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  //set static ip part
  WiFi.config(ip,gateway,subnet);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }


  // check whether ON or OFF
  if( payload[0] == 'O' && payload[1] == 'N' ) {
    digitalWrite( 5, HIGH);
    OutputPinState = HIGH;
    client.publish("/home/gatepost/lamp/state", "ON"); 
  }
  else if( payload[0] == 'O' && payload[1] == 'F' && payload[2] == 'F' ) {
    digitalWrite( 5, LOW );
    OutputPinState = LOW;
    client.publish("/home/gatepost/lamp/state", "OFF"); 
  }
  else {
    Serial.print( "Unknown command : " );
    // Serial.println( (char*)payload );
  }
  Serial.println();
}

void reconnect() {
  // Loop until we're reconnected 
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    
    
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("/home/gatepost/lamp/com");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

 unsigned long now = millis();
  if (abs(now - lastMsg) > 5000) {
    lastMsg = now;
    analog_val = analogRead(A0);                 // read photoTr
    itoa(analog_val,illumination,10);
    client.publish("/home/gatepost/sensor/state",illumination);    
    ++value;
    snprintf (msg, sizeof(msg), "hello world from Gatepost. ILLUMINATION=%u #%ld  ",analog_val, value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);

  }
  buttonState = digitalRead(buttonPin);
  if (buttonState == LOW && lastState == HIGH){   //if button has just been pressed
    Serial.println("pressed");
    delay(300);                                   //prevent from chattering
    
    //toggle the state of the LED
    if (OutputPinState == HIGH){
      digitalWrite(5, LOW);
      OutputPinState = LOW;
      client.publish("/home/gatepost/lamp/state", "OFF");

    } else {
      digitalWrite(5, HIGH);
      OutputPinState = HIGH;
       client.publish("/home/gatepost/lamp/state", "ON");
    }
  }
  lastState = buttonState;
  delay(10);
}

デプロイ後、スマートフォンから電灯のON/OFFが出来るようになりました。めでたしめでたしです。でもこれで終了ではありません。まだまだ続きます。

リモート操作する門柱灯です

リモート操作する門柱灯です

ESP-WROOM-02とMQTT + openHABでホームオートメーションに挑戦 (1)

前回はMQTTプロトコルとESP-WROOM-02を使ってLEDをリモート操作しましたが、LEDだけでは物足りないので自宅の門柱灯を操作する目標を立てました。この電灯は少し不便なところにスイッチがあって普段は残念ながらほとんど使われていません。遠隔操作が出来れば便利に使えそうです。完成までの道のりは長そうですが一つずつクリアしていきたいと思います。

前回はCHROMEアプリのMQTTlensからLEDを操作しましたが、電灯を操作するのに毎回トピック等を入力するのでは、あまりにも操作性が悪いです。そこでオープンソフトウエアのopenHABというホームオートメーションソフトを導入しました。

openHABホーム http://www.openhab.org/

openHabはこちらに書かれていたので知ったのですが、インターネットで検索しても日本語の解説があまり無いようです。多機能ですが設定も難しそうです。いつものことですが今回も手探りの状態です。

openHABの特徴です。

  • webやiOS,Androidから操作するユーザーインターフェースがある。
  • 色々なデバイスやシステム(スイッチのON,OFFのみならずセンサーの値を表示する機能,その他諸々)を統合して管理することができる。
  • 制御に関するルール(時間、近接、明暗、温度、センサーで読んだ値に関連付け)をプログラムすることができる。
  • Java 1.7.の走るWindows, MacOS X or Linuxマシンで動かすことができる。などなど

MQTTのプロトコルにも対応していますので、前回インストールしたMQTTブローカーと連携することができます。リモート操作だけではなく、センサーやタイマーと連動するインテリジェントな動作も構築可能ですね。

上記のサイトにインストール方法等の記述がありましたので、それに沿ってインストールを行いました。(以下の記述はほぼ上記サイトの転記になってしまいました。)

openHab (version 2012/10/10現在 1.7.1) 今回は例によって自宅サーバ Debian Weezyにインストールしました。 Raspberry Pi 2にもインストールされて使われている実績があるようなので将来的にはそちらに移行するかも知れません。

java ver 1.6 以上がインストールされていることが必要だということなので調べます。

$ java -version

条件を満たしていない時

$ sudo apt-get install openjdk-7-jre

$ sudo update-alternatives --config java

openHAB本体のインストールです

$ wget -qO - 'https://bintray.com/user/downloadSubjectPublicKey?username=openhab' | sudo apt-key add -
$ echo "deb http://dl.bintray.com/openhab/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/openhab.list

$ sudo apt-get update
$ sudo apt-get install openhab-runtime openhab-addon-binding-mqtt 

openHABが自動起動するようにします。私の環境の場合次のコマンドで設定しました。

$ sudo update-rc.d openhab defaults

openHAB本体のインストールが完了しましたので デモコンフィグレーションをダウンロードします。

$ wget https://bintray.com/artifact/download/openhab/bin/distribution-1.7.1-demo.zip

$ unzip distribution-1.7.1-demo.zip

$ sudo cp configurations /usr/share/openhab/
$ sudo cp addons /etc/openhab/

openHABを起動してデモを見てみます。

$ sudo /etc/init.d/openhab start

ブラウザでアクセスします。IPアドレスはインストールしたサーバのIPアドレスに読み替えてください。

http://192.168.xx.xx:8080/openhab.app?sitemap=demo

デモサイト

デモサイト

このデモを見ると照明や窓の開閉スイッチ操作だけではなく、気温や窓などのセンサーの値も表示することができるのがわかります。スイッチ操作も簡単ですね。また、ブラウザだけではなくAndroidやiOSのユーザーインターフェースも用意されています。下記はiOSのアプリです。

https://itunes.apple.com/jp/app/openhab/id492054521?mt=8

iOSへアプリをインストール後デモを見るための設定です

setting より
openHAB URL
 Local    http:サーバIPアドレス:8080
 Remode   同上

MISC
 Demo mode のチェックをはずす

 Select sitemap  Demo Houseを選択

またopenHABは非常に多くの機能をアドオンとして使うことができるようです。詳しくは下記で参照できます。

https://github.com/openhab/openhab/wiki/Linux—OS-X

さて今度はopenHabを自分の環境にカスタマイズしていきましょう。今回は動作確認ですので最低限の設定を行います。ハードは前回使用したブレッドボードに組んだ物を使います。まずサーバ側の設定です。

■opnehab.cfgの設定

$ sudo cp /etc/openhab/configurations/openhab_default.cfg /etc/openhab/configurations/openhab.cfg
$ sudo vi /etc/openhab/configurations/openhab.cfg

今回はMQTTブローカーと同じサーバにインストールしたので、下記の設定をopenhab.cfgの最下行に書き加えます。

mqtt:broker.url=tcp://127.0.0.1:1883
mqtt:broker.clientId=openHab

■sitemapの作成

$ sudo vi /etc/openhab/configurations/sitemaps/my_home.sitemap
sitemap my_home label="Main Menu"
{
   Frame label="MQTT" {
   Switch item=lamp1 label="Gatepost Lamp"
  }  
}

■itemsの作成

$ sudo vi /etc/openhab/configurations/items/my_home.items
Group All

Switch lamp1 "gatepost Lamp" (all){mqtt=">[broker:/home/gatepost/lamp/com:command:on:ON],>[broker:/home/gatepost/lamp/com:command:off:OFF],<[broker:/home/gatepost/lamp/state:state:default]"}

次にesp-wroom-02クライアント側です。 前回使用したarduinoスケッチを一部手直しします。

/* 
 Basic ESP8266 MQTT example

 This sketch demonstrates the capabilities of the pubsub library in combination
 with the ESP8266 board/library.

*/ 2015/10/12 改変
  - subscribes to the topic "/home/gatepost/lamp/com", printing out any messages
   - If the characters of the topic "/home/gatepost/lamp/com" are "ON", switch ON the ESP Led, "OFF" switch off
/*  

*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "your_SSID";
const char* password = "your_Password";
const char* mqtt_server = "192.168.xx.xx";  // mqttサーバのアドレスを設定します。

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
//unsigned char LampStatus = LOW;

void setup() {
  pinMode(5, OUTPUT);     // Initialize the 5 pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }


  // check whether ON or OFF
  if( payload[0] == 'O' && payload[1] == 'N' ) {
    digitalWrite( 5, LOW );
    client.publish("/home/gatepost/lamp/state", "ON"); 
  }
  else if( payload[0] == 'O' && payload[1] == 'F' && payload[2] == 'F' ) {
    digitalWrite( 5, HIGH );
    client.publish("/home/gatepost/lamp/state", "OFF"); 
  }
  else {
    Serial.print( "Unknown command : " );
    // Serial.println( (char*)payload );
  }
  Serial.println();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("/home/gatepost/lamp/com");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

動作のフローです。openHABがwebや携帯端末から命令をうけるとトピック名 "/home/gatepost/lamp/com"へ"ON"又は"OFF"をパブリッシュします。esp-wroom-02側は同じトピックをサブクライブしていて電灯(LED)の点灯消灯の処理を行います。またesp-wroom-02側からはトピック名"/home/gatepost/lamp/state"にて現在の状態をパブリッシュしてopenHABに知らせることができます。

■動作確認です

  • ブラウザより
    http://192.168.xx.xx:8080/openhab.app?sitemap=my_home
  • iOSアプリより
    setting -MISC
    Select sitemap Main Menuを選択

スイッチをクリック(タップ)してesp-wroom-02側のLEDが点灯又は消灯すれば成功です。

作成したサイト

作成したサイト

ESP-WROOM-02とArdinoIDEでMQTTを使った遠隔Lチカの実験

前回はESP-WROOM-02 (ESP8266)を使って気温・湿度・気圧の記録を行いました。我ながらまずまずの出来だと思ったので家族に少し自慢をしたのですが「そんな物作って何になんの?」と鼻で笑われてしまいました。(ショックです) 以前何かで読みましたが、かの発明王エジソンが蓄音機を発明し、喜び勇んでプレス発表した時の新聞記者も同じ反応だったそうです。

大エジソンと私とでは比べようもないのですが、同じ悲哀を味わえてホンの少しですが人間的に成長できたような気がします。めげずに前進したいと思います。

さて、今回はIoTに適したと言われているMQTTというプロトコルの実験を行いました。ArduinoにはMQTTのライブラリがあって、非常に簡単に実現できることがわかりました。今回はこれを使って遠隔操作でLEDの点灯・消灯を行います。

MQTTについてはこちらShiguredo Inc.様のサイトに詳しい解説があります。私は始めて勉強したのですが、メールマガジンの発行購読に似たシステムの様に感じました。

メッセージの送り手(パブリッシャー),MQTTサーバ(ブローカー),メッセージの購読者(サブクライバー)三者がいて、パブリッシャーはメッセージにトピック(識別子)を付けてMQTTサーバに送ると、MQTTサーバはそのトピックを購読しているサブクライバーにメッセージを配信する仕組みのようです。パブリッシャーは同時にサブクライバーにもなる事ができます。

何から手をつければ良いかわからなかったのですが、こちらのサイトhttp://www.element14.com/community/community/design-challenges/forget-me-not/blog/2014/09/17/fmnxx-mqtt–the-language-of-iotにとても詳しく記述されておりました。 (thanks! vish)
このサイトの記述に沿って実験を行います。

全体の構成は次の図の様になります。
構成図
Localに作るMQTTサーバは当初 Raspberry Pi と思ったのですが、今回は毎度お世話になっている自宅サーバ Debian Linux (wheezy) にインストールすることにしました。brokerソフトはmosquittoになります。次のコマンドでインストールします。

sudo apt-get install mosquitto mosquitto-clients python-mosquitto  

私の場合残念ながら上記だけではMQTT v3.1.1 Broker に対応するバージョンがインストールされず、Arduino スケッチがうまく動いてくれなかったので、ここに書いてある通り追加の作業を行いました。

wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key
sudo apt-key add mosquitto-repo.gpg.key

cd /etc/apt/sources.list.d/

sudo wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list

sudo apt-get update
sudo apt-get dist-upgrade

WindowsPC側はChrome Appの MQTT Lens を立ち上げてパブリッシャー兼サブクライバーになってもらいます。先ほどインストールしたmosquittoはデフォルトでは認証の機能はOFFになっているようなので、すぐにMQTT Lensを使いテストをする事ができます。

MQTT サーバに接続後に適当な名前でトピックを購読した後、同じトピックでメッセージを発行するとMQTTサーバから配信されたメッセージを見ることができます。
MQTlens03
MQTlens04

トピックはディレクトリの様に”/”で区切って記述するとワイルドカードを使って購読の指定をすることもできるそうです。

ESP-WROOM-02の回路ですが前回と同じくikesato氏の「ESP-WROOM-02 の Arduino 環境で I2C 制御」に書かれている回路図を参考にさせていただきました。
回路図
この実験のためにESP-WROOM-02を追加購入しようとしたのですが、SWITCH SCIENCE社の完成モジュールは在庫が無く注文できませんでした。しかたなく「ESP-WROOM-02ピッチ変換済みモジュール《フル版》 ボードのみ」秋月電子通商 Wi-Fiモジュール ESP-WROOM-02を購入して半田付けをすることにしました。大した事は無いだろうと高を括っていたのですが、老眼で不器用な私には思いのほか難しかったです。

ブレッドボードに組ましたが、SWICH SCIENCE社のブレークアウトボードではESP-WROOM-02を挿すと幅いっぱいになってしまうので、写真のように内側にジャンパ線を使い配線しました。
ESP-WROOM-02_01

MQTTのArduinoライブラリの入手先はhttp://pubsubclient.knolleary.net/index.htmlこちらです。Download GitHubへ進み V2.4 (2016/01/15現在) Source code (zip) をダウンロードし解凍後 ArduinoIDEのライブラリズ ホルダへコピーします。

ArduinoIDEを起動してファイル-スケッチの例-PubSubClient-mqtt_8266 をロードしたのちに一部手直しです。

[変更部分]
const char* ssid = "........";     --> WiFiアクセス SSID
const char* password = "........"; --> 同上         パスワード
const char* mqtt_server = "broker.mqtt-dashboard.com"; --> MTQQサーバのipアドレス

pinMode(BUILTIN_LED, OUTPUT);   --> 4ヶ所(注釈含む) の "BUILTIN_LED" を"5"へ
/*
 Basic ESP8266 MQTT example

 This sketch demonstrates the capabilities of the pubsub library in combination
 with the ESP8266 board/library.

 It connects to an MQTT server then:
  - publishes "hello world" to the topic "outTopic" every two seconds
  - subscribes to the topic "inTopic", printing out any messages
    it receives. NB - it assumes the received payloads are strings not binary
  - If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
    else switch it off

 It will reconnect to the server if the connection is lost using a blocking
 reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
 achieve the same result without blocking the main loop.

 To install the ESP8266 board, (using Arduino 1.6.4+):
  - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
       http://arduino.esp8266.com/stable/package_esp8266com_index.json
  - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
  - Select your ESP8266 in "Tools -> Board"

*/

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.

const char* ssid = "WifiエスエスID";
const char* password = "同上password";
const char* mqtt_server = "MTQQサーバIPアドレス"; //

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup() {
  pinMode(5, OUTPUT);     // Initialize the 5 pin as an output
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Switch on the LED if an 1 was received as first character
  if ((char)payload[0] == '1') {
    digitalWrite(5, LOW);   // Turn the LED on (Note that LOW is the voltage level
    // but actually the LED is on; this is because
    // it is acive low on the ESP-01)
  } else {
    digitalWrite(5, HIGH);  // Turn the LED off by making the voltage HIGH
  }

}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "hello world");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
void loop() {

  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  long now = millis();
  if (now - lastMsg > 2000) {
    lastMsg = now;
    ++value;
    snprintf (msg, 75, "hello world #%ld", value);
    Serial.print("Publish message: ");
    Serial.println(msg);
    client.publish("outTopic", msg);
  }
}

MQTTlens にてTopicを “outTopic” にしてSubcribeすると 2秒おきにESP-WROOM-02の発した”hello world”を受信します。
teraterm2

また、Publish側のTopic を”InTopc”にしてメッセージを送信するとESP-WROOM-02に配信されます。
MQTT02
ESP-WROOM-02では受け取ったメッセージの先頭の1文字が”1″ならばLEDが点灯しそれ以外では消灯します。参照先のwebではopenHABを使用して操作する方法が書かれていましたが、今回は一応の目的を達することができたのでこれで良しとします。(すみません、この状態では「LEDチカチカ」を継続させるためには、非常に努力が必要ですね。タイトルに偽りありでした。ごめんなさい。)

(2016/01/15 追記) ブレッドボードの写真には写っていませんがUSBプラグとPCの間には下記のような電源供給できるHUBが入っています。直接PCにUSBケーブルを繋ぐのでは電源容量が不足する可能性があります。