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シリアル変換モジュール。スケッチを書き込むために仮にAC100Vへ配線して通電しています。

【デプロイした2号機】USBケーブルの先端にUSBシリアル変換モジュール。スケッチを書き込むために仮にAC100Vへ配線して通電しています。

技術もないのに省スペースを追求しすぎて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ケーブルを繋ぐのでは電源容量が不足する可能性があります。

ESP-WROOM-02 を使った リモート温度・湿度・気圧計の製作

ESP-WROOM-02-1

今年はほんとに毎日暑い日が続きました。汗をかきながらWebを閲覧していると偶然ikesato氏の「ESP-WROOM-02 の Arduino 環境で I2C 制御」にたどりつきました。

初めて聞く名前なので早速検索をかけ調べると「技適OKな中華IoTモジュールを使いこなす」こちらのサイトに詳しい内容が書かれていました。

ESP-WROOM-02についてわかった事は

(1)ESP8266EXを搭載したWiFiモジュール。WiFi接続機能とCPU・フラッシュROMが一体になっていて日本の技適マークも取得済みである。3.3V動作。

(2)デフォルトではATコマンドベースの無線モジュールだがArduino IDEを使ってプログラムすることも出来る。

要はセンサー又は表示器と電源だけ用意すれば簡単に動作するWiFiに繋がるチップだということですね。以前私が実験したNRF24L01+と違い日本の法律に抵触せずに工作することができ、しかも単価が安い!私は無意識のうちに通販サイトの購入ボタンをポッチとしていました。

とりあえず2.54mmピッチに変換されている完成品、SWITCH SCIENCE社のESP-WROOM-02ピッチ変換済みモジュール《フル版》 1,080円を1個購入してしまいました。

センサーは手持ちにあった気圧センサー 秋月電子通商 SCP1000モジュール(SPI接続)と何処から購入したか分からなくなってしまった気温・湿度センサー SENSIRION SHT10を繋ぎます。電源は前回も使用した秋月電子製StepUP 3.3v DC/DCコンバータによる単三乾電池×2本による動作としました。

前回通りDebian自宅サーバにてHIGHCHARTS社製 非商用利用無料のHIGHSTOKライブラリを使用しwebから過去データを含め時系列に表示します。

結局、以前の記事の焼き直しです。オリジナリティが全く無い製作記事です。(わざわざご訪問いただき誠に申し訳ありません。<(_ _)> )

まずESP-WROOM-02のArduino IDEによる開発のセッティングですがこちらのサイト https://github.com/esp8266/Arduino に載っている通りに行います。

・Arduno Wevsite http://www.arduino.cc/en/main/software よりArduino 1.6.5をダウンロードしてインストール
・Arduino 1.6.5を起動して ファイル – 環境設定 と進みAdditional Board Manager URLs欄にhttp://arduino.esp8266.com/stable/package_esp8266com_index.json を入力
・再起動後 ツール – ボード – ボードマネージャー より下のほうのにあるesp8266 by ESP8266 Comunity をクリックしてインストール (※ 手順が抜けていました 10/25追記)
・ツール – ボード よりGeneric ESP2866 Moduleを選択

詳しくはこちらのサイト「技適済み格安高性能Wi-FiモジュールESP8266をArduinoIDEを使ってIoT開発する為の環境準備を10分でやる方法 」に書かれています。

スケッチの書き込みには秋月電子 FT232RL USBシリアル変換モジュール(AE-UM232R)を使用しました。全体の電圧が3.3VなのでVCCIOも3.3Vにします。

ここで注意すべきことは、AduinoIDEでスケッチを書き込んでしまうとデフォルトのATコマンドベースの動作はしなくなってしまうことです。元に戻すことは無いかもしれませんが、念のためにその方法を押えて置きましょう。「ねむいさんのぶろぐ」こちらに詳しく書かれています。

「ねむいさんのぶろぐ」に書かれていますが、スケッチを書き込む前にESP-WROOM-02本体のMACアドレスやSSIDなどをATコマンドを使い調べて置く方が良いと思います。AduinoIDEのシリアルモニタでボーレートを115200bps,改行コードをCRおよびLFに設定して通信します。

[ATコマンドの例]
AT+CIFSR
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"1a:fe:34:xx:xx:xx"
OK
AT+CWSAP?
+CWSAP:"ESP_xxxxxx","",1,0
OK
AT+GMR
AT version:0.25.0.0(Jun 24 2015 18:02:27)
SDK version:1.1.2
compile time:Jun 24 2015 18:15:29

ねむいさん謹製のESP-WROOM-02ピン配置表 https://dl.dropboxusercontent.com/u/34019941/images/2015/20150623/20150623_006.pngはとても役にたちました。ありがとうございました。(ただしGND PINのアサインがSWITCH SCIENCE社のブレークアウトボードでは違っていますので注意が必要です。)

回路は上記の「ESP-WROOM-02 の Arduino 環境で I2C 制御」を参考にさせていただきました。

回路図

回路図

SW1を押しながらSW2を押してリセットをかけ、SW2,SW1の順に離すとファームウエアの書き込みモードになるのでスケッチを書き込むことができます。

スケッチの書き込み

スケッチの書き込み

ESP-WROOM-02をArduino IDEで使うことが始めてでしたので、まずsht10センサーの値の読み込みが動作するか確認しました。こちらからダウンロードして解凍したたファイルSHTx1-masterをArduinoをインストールしたディレクトリ下にあるlibariesのなかにコピーしますとReadSHT1xValuesをスケッチの例から読み出すことができるようになります。

ここは良く分からなかったのですが、このようなパラメータで設定しました

良く分からなかったのですが、とりあえずこんな感じで

ArduinoIDE にセットすべきパラメータに分からない項目がありましたが、とりあえず図のようにして書き込みました。

今回はSHT10のdataPin がESP-WROOM-02の IO2に、同じくclockPinが IO4に繋がりますのでReadSHT1xValuesスケッチのその部分を変更します。
測定結果がシリアルモニタに表示されました。よかったです。

/**
* ReadSHT1xValues
*
* Read temperature and humidity values from an SHT1x-series (SHT10,
* SHT11, SHT15) sensor.
*
* Copyright 2009 Jonathan Oxer &lt;jon@oxer.com.au&gt;
* www.practicalarduino.com
*/


#include <SHT1x.h>

// Specify data and clock connections and instantiate SHT1x object
#define dataPin 2    //オリジナルから変更
#define clockPin 4   //オリジナルから変更
SHT1x sht1x(dataPin, clockPin);

void setup()
{
Serial.begin(38400); // Open serial connection to report values to host
Serial.println("Starting up");
}

void loop()
{
float temp_c;
float temp_f;
float humidity;

// Read values from the sensor
temp_c = sht1x.readTemperatureC();
temp_f = sht1x.readTemperatureF();
humidity = sht1x.readHumidity();

// Print the values to the serial port
Serial.print("Temperature: ");
Serial.print(temp_c, DEC);
Serial.print("C / ");
Serial.print(temp_f, DEC);
Serial.print("F. Humidity: ");
Serial.print(humidity);
Serial.println("%");

delay(2000);
}

SCP1000 気圧センサー部分も同様に
https://github.com/PaulStoffregen/Arduino-1.6.1-Teensyduino/blob/master/hardware/arduino/avr/libraries/SPI/examples/BarometricPressureSensor/BarometricPressureSensor.inoを参考にさせていただきました。

SPI通信のPIN結線ですが今回は

DRDY— NC (使用しない)
CSB — 5
MOSI — 13
MISO — 12
SCK — 14

と結線しています。スケッチ中の該当する値を下記の通り変更します。

//const int dataReadyPin = 6; //オリジナルから変更
const int chipSelectPin = 5;

dataReadyPinを使わないのでその部分を修正して変わりにディレイをいれています。必要な時間の長さが分からなかったのでとりあえず 1000mmSとしてしまいました。(お気付きになられたかと思いますが「とりあえず」を多用するのが私の特徴です、さらに大抵の場合その後、変化はありません。そう、予想通りです。)

上記のスケッチをまるまる使用させていただき、さらにArduino IDE のスケッチの例からはESP8266WiFi-WiFiClientを修正して目的の下記スケッチが完成いたしました。(ほとんど全て copy & pasteです。作者の皆様ありがとうございました。)

#include <SPI.h>
#include <SHT1x.h>
#include <ESP8266WiFi.h>

//SCP1000 Sensor's memory register addresses:
const int PRESSURE = 0x1F;      //3 most significant bits of pressure
const int PRESSURE_LSB = 0x20;  //16 least significant bits of pressure
const int TEMPERATURE = 0x21;   //16 bit temperature reading
const byte READ = 0b11111100;     // SCP1000's read command
const byte WRITE = 0b00000010;   // SCP1000's write command

//SCP1000 pins used for the connection with the sensor
// the other you need are controlled by the SPI library):
//const int dataReadyPin = 6;
const int chipSelectPin = 5;

// Specify data and clock connections and instantiate SHT1x object
#define dataPin  2
#define clockPin 4
SHT1x sht1x(dataPin, clockPin);  

// wifi
const char* ssid     = "エスエスid";
const char* password = "パスword";
 
const char* host = "自宅server IP-address";

void setup()
{

 delay(10);
 
  // We start by connecting to a WiFi network
 
  Serial.println();
  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());

//SCP100 start the SPI library:
  SPI.begin();
// initalize the  data ready and chip select pins:
//  pinMode(dataReadyPin, INPUT);
  pinMode(chipSelectPin, OUTPUT);

  //Configure SCP1000 for low noise configuration:
  writeRegister(0x02, 0x2D);
  writeRegister(0x01, 0x03);
  writeRegister(0x03, 0x02);
  // give the sensor time to set up:
  delay(100);

   Serial.begin(115200); // Open serial connection to report values to host
   Serial.println("Starting up");
}

int value = 0;

void loop()
{
  delay(5000);
  ++value;

//SHT10
  float temp_c;
  float temp_f;
  float humidity;

  // Read values from the sensor
  temp_c = sht1x.readTemperatureC();
  temp_f = sht1x.readTemperatureF();
  humidity = sht1x.readHumidity();

  // Print the values to the serial port
  Serial.print("Temperature: ");
  Serial.print(temp_c, DEC);
  Serial.print("C / ");
  Serial.print(temp_f, DEC);
  Serial.print("F. Humidity: ");
  Serial.print(humidity);
  Serial.println("%");


//SCP1000

 //Select High Resolution Mode
  writeRegister(0x03, 0x0A);

    delay(1000);
    //Read the temperature data
    int tempData = readRegister(0x21, 2);

    // convert the temperature to celsius and display it:
    float realTemp = (float)tempData / 20.0;
    Serial.print("Temp[℃]=");
    Serial.print(realTemp);


    //Read the pressure data highest 3 bits:
    byte  pressure_data_high = readRegister(0x1F, 1);
    pressure_data_high &= 0b00000111; //you only needs bits 2 to 0

    //Read the pressure data lower 16 bits:
    unsigned int pressure_data_low = readRegister(0x20, 2);
    //combine the two parts into one 19-bit number:
    long pressure = ((pressure_data_high << 16) | pressure_data_low) / 4;

    // display the temperature:
    Serial.println("\tPressure [Pa]=" + String(pressure));
    
  // Use WiFiClient class to create TCP connections
  Serial.print("connecting to ");
  Serial.println(host);
 
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
 
  // We now create a URI for the request
  String url = "/tempera/tempera_log.php";
         url = url + "?tempera=" +String( temp_c);
         url = url + "&humidi=" + String(humidity);
         url = url + "&puress=" + String(pressure);
  Serial.print("Requesting URL: ");
  Serial.println(url);
 
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");
  delay(10);
 
  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }
 
  Serial.println();
  Serial.println("closing connection");

 //SCP1000 Select Powere Down Mode
  writeRegister(0x03, 0x00);
 //ESP8266 Deep Sleap mode
 //1:μ秒での復帰までのタイマー時間設定 (30分)  2:復帰するきっかけの設定(モード設定)
  ESP.deepSleep(30 * 60 * 1000 * 1000 , WAKE_RF_DEFAULT);  

  //deepsleepモード移行までのダミー命令
  delay(1000);
}

//Read from or write to register from the SCP1000:
unsigned int readRegister(byte thisRegister, int bytesToRead ) {
  byte inByte = 0;           // incoming byte from the SPI
  unsigned int result = 0;   // result to return
//  Serial.print(thisRegister, BIN);
//  Serial.print("\t");
  // SCP1000 expects the register name in the upper 6 bits
  // of the byte. So shift the bits left by two bits:
  thisRegister = thisRegister << 2;
  // now combine the address and the command into one byte
  byte dataToSend = thisRegister & READ;
//  Serial.println(thisRegister, BIN);
  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);
  // send the device the register you want to read:
  SPI.transfer(dataToSend);
  // send a value of 0 to read the first byte returned:
  result = SPI.transfer(0x00);
  // decrement the number of bytes left to read:
  bytesToRead--;
  // if you still have another byte to read:
  if (bytesToRead > 0) {
    // shift the first byte left, then get the second byte:
    result = result << 8;
    inByte = SPI.transfer(0x00);
    // combine the byte you just got with the previous one:
    result = result | inByte;
    // decrement the number of bytes left to read:
    bytesToRead--;
  }
  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
  // return the result:
  return(result);
}
//Sends a write command to SCP1000

void writeRegister(byte thisRegister, byte thisValue) {

  // SCP1000 expects the register address in the upper 6 bits
  // of the byte. So shift the bits left by two bits:
  thisRegister = thisRegister << 2;
  // now combine the register address and the command into one byte:
  byte dataToSend = thisRegister | WRITE;

  // take the chip select low to select the device:
  digitalWrite(chipSelectPin, LOW);

  SPI.transfer(dataToSend); //Send register location
  SPI.transfer(thisValue);  //Send value to record into register

  // take the chip select high to de-select:
  digitalWrite(chipSelectPin, HIGH);
}

約30分に1回自宅サーバにアクセスしてデータを渡します。ディープスリープモードに関しては

ESP8266の真骨頂Deep-Sleepモードの使い方こちらを参考にさせていただきました。ありがとうございました。

サーバーサイドのスクリプトはESP-WROOM-02からデータを受け取ってファイルに書き込むphpファイルとデータをグラフにして表示させるhtmlファイル2個です。当然ですがwebサーバが稼動していてPHPが使えなければなりません。

PHPファイルですが、 NRF24L01+ と DS18B20+と Arduinoでリモート温度計 3 で書かれている物を手直ししました。

tempera_log.php

<?php
//   ini_set( 'display_errors', 1 );
   $strGetTempera = time();
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['tempera'];
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['humidi'];
   $strGetTempera .= ",";
   $strGetTempera .= $_GET['puress'];
   $strGetTempera .= "\n";
   $strDataFilePath = '/ファイルのpath/atmosphere_log.csv';  //ログを記録するファイルの指定です
   $fp = fopen($strDataFilePath, "ab");
   fwrite($fp, $strGetTempera);
   fclose($fp);
?>

動作確認のためにブラウザから

http://サーバアドレス/パス/tempera_log.php?tempera=1234.567&humidi=12&puress=34
とアドレス欄に入力して atmosphere_log.csvが作成され、中身が下記の様になるか確かめます。

1439546219,1234.567,12,34 //左から アクセスした時のUniX時間,temperaの値,humidiの値,puressの値

グラフを表示させるhtmlファイルも前回作成したものをベースにしています。


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "xhtml11.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Highstocks temperature-humidity-pressure</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" type="text/javascript"></script>
<script src="http://code.highcharts.com/stock/highstock.js"></script>
<script type="text/javascript"> 
var chart1; // globally available
$(document).ready(function() {
$.get('atmosphere_log.csv', function(data) {
    // Split the lines
        var lines = data.split('\n');
        var temperature = [];
        var humidity = [];
        var pressure = [];
        var js_time 
    // Iterate over the lines and add categories or series
    $.each(lines, function(lineNo, line) {
        var items = line.split(',');
	if(parseInt(items[0],10)){
         js_time = parseInt(items[0])*1000+ (9*60*60*1000) ;
	temperature.push([js_time,parseFloat(items[1])]);
	humidity.push([js_time,parseFloat(items[2])]);
	pressure.push([js_time,parseInt(items[3]/100)]);
        }
    });
    var seconds = (js_time /1000)%(24 * 60 * 60);
    var hour  =  Math.floor(seconds / (60 * 60));
    var minit =  Math.floor((seconds % (60 * 60))/60);
    var options = {
     title : {
       text : '[ ' + hour +'時'+ minit +'分 現在] ' + '気温: ' + Math.floor(temperature[temperature.length -1][1]*10)/10 + "℃ "
                       + '湿度: ' + Math.round(humidity[humidity.length -1][1]) + "% "
                     + '気圧: ' + pressure[pressure.length -1][1] + "hPa" ,
       style : {
            color: '#6Faa6F',
            fontSize: '20px'
            }
        },
     navigator: {
         baseSeries: 2
         },
     legend: {
         enabled: true,
         align: 'left',
         backgroundColor: '#EFFFC5',
         borderColor: 'black',
         borderWidth: 2,
        layout: 'vertical',
        verticalAlign: 'top',
        y: 100,
        shadow: true
        }, 
    chart: {
        renderTo: 'container' ,
        zoomType: 'x'
    },
    xAxis: {
        type: 'datetime',
        range:  24*3600 * 1000 // 24 hour
    },
     yAxis: [{
       title: {
         text: '湿度 (%)'
       },            
       opposite: true
    },{
       title: {
              text: '気圧 (hPa)'
              },
       offset : 10,
       opposite: true
    },{
       title: {
         text: '気温 (°C)'
       },
      opposite: false
	}],
    series:[{
             yAxis: 0,
	     data: humidity,
         name: '湿度',
         type: 'area',
	     color: '#ccccff',
             tooltip: {
                    valueDecimals: 1,
                    valueSuffix: '%'
                      }
           },{
             yAxis: 1,
	     data: pressure,
         name: '気圧',
         type: 'spline',
	     color: '#000000',
         tooltip: {
                    valueDecimals: 1,
                    valueSuffix: 'hPa'
                      },
         dashStyle: 'shortdot',
           },{
             yAxis: 2,
	         data: temperature,
             name: '気温',
             color: '#00bbff',
             lineWidth:3,
	         tooltip: {
                    valueDecimals: 1,
                    valueSuffix: '°C'
                   }
	      }]
};
    var chart1 = new Highcharts.StockChart(options);
});
});
</script>
</head>
<body>
<div id="container" style="width: 100%; height: 400px"></div>
</body>
</html>

いままで大気圧などは気にしたことがなかったのですが、今回の件で少しだけ学習しました。大気圧は海抜が低い所では標高差8mで1hPa変化するそうです。私の住んでいるところは東京都下ですが標高約200m弱のところにあります。その割合で計算すると確かに千代田区にある気象庁の発表する値と大体一致します。また、しばし観測した結果、同じ場所では気圧の変動幅はあまり広くないことがわかりました。気圧に関しては大気の状態による変動よりも、標高差による変動がとても大きくなるようです。蛇足ですが血圧計の単位 mmHgはトリチェリの水銀柱由来だそうです。知りませんでした。

何ヶ所かのディレイの値を調整することとWiFiに接続を固定IPにすることにより消費電力をもう少し抑えることができると思います。今回はこの設定で百円ショップで購入した単三電池がどれだけ長持ちするか観察いたします。(2015/8/17より稼動)
※ 2015/10/14 追記
数日前に異常な湿度の値が記録されていたので怪しげに思っていたのですが、2015/10/13午前6時過ぎに電池が切れてしまったようです。上記の設定で単三乾電池2本により動作させ約57日間稼動しました。
※ 2016/04/03 追記
後日3端子レギュレターを追加して電池からAC電源に変更した際分ったのですが、ESP-WROOM-02は連続動作させると結構発熱します。ESP-WROOM-02のそばにセンサーがあると正確に温度を測れない可能性があります。この記事では、Sleepモードを使った間欠動作だったので気付きませんでした。ご注意ください。

こちらに拡大したグラフ

Highstocks temperature-humidity-pressure


 

GeForce GTX 460 ディスプレイドライバの応答停止と回復エラー

zotac_gtx460
私はゲーマーではないのでVideo Card にはあまり興味がなく、写れば良い程度に考えていました。Windows 7 Pro 64bit Pentium E6500 Memory 4G に nvida GT430 Video Card を載せた構成で2画面のディスプレイを表示させて満足です。実際、ワープロや表計算、動画再生では全然問題が生じません。

しかし、動画編集を行う時は、さすがにPCのパワー不足を感じます。私が使っているソフトはadobe premiere pro CS6 なのですが、ある時 adobe認定外のNvidia ビデオカードでもGPU アクセラレーションができるという情報をつかみました。adobe After Effectsでも可能とのことです。

こちらのサイト
http://www.studio1productions.com/Articles/PremiereCS5.htm
にすごく詳しく記述されています。(秀逸です)

早速書いてあるとおりにしてみました。しかし詳しく計測したわけではないのですが、GT430では体感的にはあまり変わりないような気がしました。

ならば「上位のビデオカード」で、ということなのですが、新しいビデオカードを買う予算が捻出できません。そんな折ヤフーオークションをみると「動作確認無し」という商品説明で、ZOTAC GTX460中古品 が出品されていました。少々迷いましたが、格安(2300円也)でしたのでポチとクリックして落札してしまいました。

<gtx 460 をヤフオクで>

GTX460はCUDAコア数がGT430と比較して3.5倍の336個、メモリータイプもGT430のDDR3に対してDDR5です、メモリーインターフェースのBit数も2倍です。最上級クラスの GTX Titan やGTX780,GTX680と比較するとかなり見劣りしますが、軽くお試しというならGTX460でも十分期待が持てます。

商品が届いて早速PCにとりつけます。緊張しながらスイッチをONにしました。VGAの画面が写し出されて一安心です。どうやら当たりだったようです。nVidiaのサイトから最新ドライバをダウンロードしてインストールしました。ところが…です。

しばらく使っていると表記の「ディスプレイ ドライバーの応答停止と回復」エラーが頻発します。出現するのは高負荷の時に限らず発生して、再現性がありません。通常は数秒後回復するのですが、画面が真っ黒になり戻って来ない時もあります。これでは使い物になりません。

googleで検索をかけて調べますと、皆さん苦労しているようで多くの報告がありました。安く仕入れた物ではありますがこのまま断念では気持ちが治まりません。最大限努力することにしました。

  • 電源を400Wから600Wに変更 — (推奨値以下でしたので必須でした、が) …NG
  • グラフィックドライバをバージョンダウン — (あるwebサイトに書かれていた対策でしたが) …NG
  • ケースの放熱対策 — (CPUID HWMonitorで調べるとGPUの温度が高い時で75℃ほど、高すぎるわけではなさそうですが) …NG
  • メインメモリー交換 — (あまりその可能性が考えられないですがメモリーの不具合説を発見、しかし) …NG
  • ディスプレイドライバの残骸が悪さをしている説 — (しばらく前の情報でしたが、藁にもすがる気持ちで) …NG
  • Fermiアーキテクチャに配慮したベータ版ドライバ 331.93 — (5分で) …NG

何度ビデオカードを付け直した事か。もう対策はないとあきらめていたところに次の記事を発見しました。

  • コアクロックを25MHz、メモリークロックを 50MHz落とす。それで解決できないときはコア電圧を0.012~0.013V上げてみること。

http://answers.yahoo.com/question/index?qid=20121216001252AATsP0W

ヤッホー!これはいままで見たことの無かった新説でした。すごく説得力がある文章です。私はまるで芥川龍之介が描いたカンダダのようになってしまいました。ひょっとしたら、ひょっとします。さっそく記事の通り GPU-Z と MSI afterburnerをダウンロードして、期待を込めて設定しました。が … 撃沈。

何をやってもうまく動いてくれません。もうあきらめました。本当です。あーぁ。ガッデム。

いつものごとく沈んだ気分でディスプレイカードを元通りに付け替えます。「処分」の2文字が頭に浮かびました、が、どうせなら中を分解して構造を眺めてからと。

このビデオカードは8本のネジをはずすと簡単に分解できました。ここで大発見、GPUと放熱板の間に塗ってある熱伝導グリス(サーマルコンパウンド)が少し変です。固形化していて、まるで劣化したゴムのようです。しかも随分厚塗りです。厚く塗れば良いって物でもないでしょう。

分解

かすかな希望がわいてきました。マイナスのドライバーで固まったグリスを剥ぎ取り、アルコールの染みたウエットティッシュで綺麗に掃除しました。手元にあった安物熱伝導グリスを薄く塗布してから元に戻し、再度PCにセッテイングです。

20時間連続運転後にこの文章を書いていますが、いまのところきわめて順調です。動画のエンコーディングも同時に行っていますが、かなりの時間短縮になりそうです。

※追記 (お詫び) ※
その後しばらくは順調でしたが、最終的には調子が悪くなり使用に耐えなくなってしまいました。上記は根本的な解決方法にはなりませんでした。

Raspberry Pi USB webcamera 動画ストリーミング配信(オマケで音声も配信)

mjpg-streamer寝ている赤ちゃんやラヴリィな愛玩動物、さらには徘徊する御老人等々を見守るために、ラズベリーパイとUSB webcamを使いWifiで画像と音声のストリーミング配信をする実験を行いました。
いろいろ試してみたのですが、なかなか動作が安定せず現在も試行錯誤の段階です。その過程でまた新たに512MB RAMのRPiを買ってしまいました。

スペックです。

Raspberry Pi Model B
Downloaded image: 2012-12-16-wheezy-raspbian.zip
uname -a output: 3.2.27+ #250 PREEMPT Thu Oct 18 19:03:02 BST 2012 armv6l GNU/Linux

Webcam : Logitech C270
Wireless adapter: BUFFALO WLI-UC-GNM2

lsusb output:
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]
Bus 001 Device 005: ID 046d:0825 Logitech, Inc. Webcam C270

cat /proc/cpuinfo

Hardware : BCM2708
Revision : 000f

電源は 5V 2A

まず動画の配信ですが、ffmpeg ffserverを使う方法やMotion, MJPG-Streamerなど何種類かを色々なサイトを参考にして実験してみました。

Motion(http://www.lavrsen.dk/foswiki/bin/view/Motion/WebHome) は apt-get で簡単にインストールでき、コンフィグ設定が簡単、しかも多機能なのでとても良いのですが、Raspberry Piの処理速度に起因してか最大で352×288 pixel の画像しか見ることができません。さらに問題なのは、私の環境では起動から数時間経過するとuvcvideo関係のエラーで動作が止まってしまう事でした。

しかしMotionは、玄箱HG(KURO-BOX HG) + C270 webcam + Debian squeeze + 640×480(画像)に設定して安定して動いていた経験があります。RPiでのこのエラー原因は、現versionのRaspbianの実装の問題か、またはwebcamとの相性なのか、良くわかりません。(ちなみに archlinux-hf-2012-09-18.zip でも試してみましたが、結果は同じでした)

結局、現在はディレイが少なく画像も640×480 pixelを軽快に再生できるMjpg-streamerを使っています。webcamは家電量販店で安く売っていたELECOM UCAM-DLD200BAも試してみたのですが、私の場合Logitech C270との方が相性が良いみたいです。ちなみにこの組み合わせで1280×960 pixelの再生も可能でした。でも、動画再生の様子ははカクカクという感じです。これは無線LANの通信速度にも大きく依存すると思います。

Mjpg-streamerのインストールについては多くの方が書かれているのですが、私はSLB Labs さんのサイト(http://www.slblabs.com/2012/09/26/rpi-webcam-stream/)を参考にさせていただきました。

作業手順は上記サイトに記載されている通りです。Raspbianインストール後、初期設定を行った後に、まずアップデートしておきました

# apt-get update
# apt-get upgrade

そして、こちらからmjpg-streamer-r63.tar.gzをダウンロード

任意のディレクトリで解凍
# tar xvzf mjpg-streamer-r63.tar.gz

コンパイルするためにlibjpeg8-devをインストール
# apt-get install libjpeg8-dev

シンボリックリンクを作成
# ln -s /usr/include/linux/videodev2.h /usr/include/linux/videodev.h

コンパイル
# cd mjpg-streamer-r63
# CFLAGS+=”-O2 -march=armv6 -mfpu=vfp -mfloat-abi=hard” make

/etc/rc.localファイルの# By default this script does nothing.の下あたりに下記を追記するとブート時に自動起動します。

cd /'ファイル パス'/mjpg-streamer-r63/
export LD_LIBRARY_PATH=.
./mjpg_streamer -i './input_uvc.so -d /dev/video0 -r 320x240 -f 3' -o './output_http.so -w www -p 8080'

(2013/02/24 -r -f の値を変更しました。 thanks D.K )

クライアントPCのブラウザで
http://ラズベリーパイのip address:8080
にアクセスすると、FirefoxやChomeさらに IEでもjavascriptを利用して動画を見ることができます。

また、スカイの社長日記さんのサイト(http://skysh.kyo2.jp/e391120.html) を参考にさせていただき、

/boot/cmdline.txt に smsc95xx.turbo_mode=N を追加 ===> (2013/02/03追記へ)

# raspi-config にて

overclock –> Turbo 1000MHz ARM, 500MHz core, 600MHz SDRAM, 6 overvolt
memory_split –> 16

にしてあります。また無線LANの設定は前回のエントリー(修正アリ)の通りに設定しました。

さて、C270 webcamにはマイクも付いていますので音声も同時ストリーミングに挑戦してみました。

こちら pruperさんのサイト (http://prupert.wordpress.com/2010/08/02/stream-live-audio-from-a-microphone-in-near-real-time-in-ubuntu/)や

こちらのサイト (http://ffmpeg.org/trac/ffmpeg/wiki/Capturing%20audio%20with%20FFmpeg%20and%20ALSA) を参考にしました。

まずffmpeg と lame をインストールします

# apt-get install ffmpeg lame

# arecord -l で Audio Input device を表示させます

**** List of CAPTURE Hardware Devices ****
card 1: U0x46d0x825 [USB Device 0x46d:0x825], device 0: USB Audio [USB Audio]
Subdevices: 0/1
Subdevice #0: subdevice #0

C270 webcamの音声(Microphone on the USB web cam)は上記の通り card 1 ですので ffmpeg の -i オプションで指示する値を hw:1 にします。

# ffmpeg -f alsa -ac 1 -i hw:1 -acodec libmp3lame -ab 32k -re -f rtp rtp://239.5.5.5:1234 >/dev/null 2>&1 &

上記を実行した後にクライアントPCから VLC mdedia player を起動して

メディア (Media)- ネットワークストリームを開く(Open Network Stream) – ネットワークURL欄に rtp://239.5.5.5:1234 – 再生 (Play)

の順にすすめば音声も聞くことができます。

【映像・音声の遅延について】
ネットワークの通信速度にもよるのでしょうが、私の実験では映像に1秒-3秒くらいのディレイがあります。さらに遅れて音声が届くという感じです。

【CPU等の使用率: topの抜粋です】

画像のみの時 (Mjpg-streamer)

top - 21:03:22 up 2 min,  1 user,  load average: 0.37, 0.26, 0.11
Tasks:  60 total,   1 running,  59 sleeping,   0 stopped,   0 zombie
%Cpu(s):  4.1 us, 10.7 sy,  0.0 ni, 85.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    497764 total,    68868 used,   428896 free,     8976 buffers
KiB Swap:   102396 total,        0 used,   102396 free,    39144 cached

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND
 2187 root      20   0 45760 1652 1468 S   5.2  0.3   0:02.35 mjpg_streamer

画像+音声 (Mjpg-streamer + ffmpeg)

top - 20:59:27 up 33 min,  1 user,  load average: 1.04, 1.12, 0.82
Tasks:  63 total,   2 running,  61 sleeping,   0 stopped,   0 zombie
%Cpu(s): 59.2 us, 15.4 sy,  0.0 ni, 25.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    497764 total,    99988 used,   397776 free,    12920 buffers
KiB Swap:   102396 total,        0 used,   102396 free,    57880 cached

  PID USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND
15352 root      20   0 37268  10m 4380 R  56.7  2.1   7:56.70 ffmpeg
 2170 root      20   0 53952 1840 1468 S   5.5  0.4   1:43.38 mjpg_streamer

ffmpeg はCPUの使用率が高いです。

調子良く運用できそうだったのですが、やはりエラーメッセージを出して止まってしまいます。 (涙)

Jan 30 17:51:35 raspberrypi MJPG-streamer [2209]: Error grabbing frames
Jan 30 17:51:35 raspberrypi kernel: [12810.889315] uvcvideo: Non-zero status (-5) in video completion handler.

この方法で音声も送るのには無理があるのでしょうか?今後の課題です。

キャプチャ

(2013/02/03 追記)

残念ながらしばらく動かしていると止まってしまい安定しません。どうやらUSB webcamだけでなくUSB無線アダプタの方も安定しないみたいです。smsc95xx問題関係かと思い対策方法を検索してみたところ、次の記事が目に留まりました。

http://www.raspberrypi.org/phpBB3/viewtopic.php?t=21963&p=259518

bgirardot さんがdevelopment firmwarenへのアップデートについて記載されていたので、カーネルパラメータに追記する方法に変えて試してみました。参照先にもかかれているように一度開発版のファームウエアにアップデートすると、元に戻すのはとても難しいので注意が必要です。(bgirardotさんはSDメモリカードのコピーを勧めています)

https://github.com/Hexxeh/rpi-update へアクセスしてこちらに書かれている通りに作業しました。(スパーユーザーの権限で行います)

■ [preparaitons(準備作業)]
私にはこの作業は必要なかったですが、行うとすれば

# apt-get update

# apt-get install ntpdate

# ntpdate -u ntp.nict.jp

# apt-get install ca-certificates

■ [rpi-update コマンドのインストール]

# wget http://goo.gl/1BOfJ -O /usr/bin/rpi-update && sudo chmod +x /usr/bin/rpi-update

# apt-get install git-core

■ [rpi-update の実行]

# rpi-update

# reboot

■ [上記コマンド実行の結果]

# uname -a の表示です。

Linux raspberrypi 3.6.11+ #366 PREEMPT Wed Jan 30 12:59:10 GMT 2013 armv6l GNU/Linux

まだ、長時間テストしているわけではないのですが安定性は良いみたいです。ただ、mjpg-streamerを実行した時の再生スピードが遅くなってしまったような感じがします(バラツキがある)。

(2013/02/05 さらに追記) 残念ながら上記設定でも長時間運用していると下記のエラーを出して動画配信が止まってしまいました。安定的に連続で動かせるようになるには、しばらくかかりそうです。

Feb 5 05:19:23 raspberrypi kernel: [78477.340097] uvcvideo: Non-zero status (-5) in video completion handler.

Raspberry Pi 無線LAN (wifi) SSH接続ができない

WLI-UC-GNM2
Raspberry pi を有線LAN経由でSSH接続してHeadless運用(モニタとかキーボードを接続しない)していたのですが、無線LANでSSHが使えれば離れたところにも設置できて便利です。そこでwifiで使うために、BUFFALOの無線アダプタを購入いたしました。型番はWLI-UC-GNM2。Amazon.co.jpで891円でした。無線LANに関してはWeb上で多くの方が報告されていますので問題ないハズと思ったのですが、なかなかうまくいきませんでした。

まず私のRPi環境のスペックです

Raspberry Pi Model B
Downloaded image: 2012-10-28-wheezy-raspbian.zip
Kernel version: 3.2.27+
Wireless adapter: BUFFALO WLI-UC-GNM2
lsusb output: Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]

board revision
root@raspberrypi:~# cat /proc/cpuinfo

Hardware : BCM2708
Revision : 0003

電源は 5V 2A
無線アダプタはセルフパワードハブ( powered hub)に接続
(電源に問題があって通信できない事例をWebで見ていましたので、この構成で試してみました。が、結果的には直接RPiに無線アダプタを挿しても同じでした。)

アクセスポイント側 BUFFALO WHR-HP-GN

無線アダプタは接続するとすぐ認識できました。

root@raspberrypi:~# lsusb
Bus 001 Device 004: ID 0411:01ee BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM2 Wireless LAN Adapter [Ralink RT3070]

root@raspberrypi:~# dmesg
[    3.263009] usb 1-1.2: new high-speed USB device number 4 using dwc_otg
[    3.409389] usb 1-1.2: New USB device found, idVendor=0411, idProduct=01ee
[    3.423267] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[    3.437499] usb 1-1.2: Product: 802.11 n WLAN
[    3.448902] usb 1-1.2: Manufacturer: Ralink
[    3.460167] usb 1-1.2: SerialNumber: 1.0

IP address ,SSID, 事前共有Key等をセットします。(IPアドレス等は実際とは異なる便宜上のものです)LANケーブルが差し込まれている時は eth0 から通信して、また反対に無線アダプタが差し込まれている時はwlan0から通信する事を目的としています。 (1/29 追記: 後日、新しいSD-CARDに再構築してみたところ、下記の設定だけではデフォルトゲートウエイがwlan0に切り替わりませんでした。何の設定をしたのか思い出せないので、しかたなくeth0の設定をコメントアウトしました。また、アクセスポイントはWPA-PSK 暗号化はAESで試してみました。 )

</etc/network/interfaces>

auto lo
iface lo inet loopback

# auto eth0
# allow-hotplug eth0
# iface eth0 inet static   
# address 192.168.11.200
# network 192.168.11.0
# netmask 255.255.255.0
# broadcast 192.168.11.255
# gateway 192.168.11.1


auto wlan0
allow-hotplug wlan0
iface wlan0 inet static
address 192.168.11.201
network 192.168.11.0
netmask 255.255.255.0
broadcast 192.168.11.255
gateway 192.168.11.1

wpa-ssid "エスエスID"
wpa-psk "事前共有key"

</etc/resolv.conf>

nameserver 192.168.11.1
root@raspberrypi:~# shutdown -r now

無線アダプタをセットして、LANケーブルは抜いた状態でリブートします。

root@raspberrypi:~# iwconfig
lo        no wireless extensions.

wlan0     IEEE 802.11bgn  ESSID:"エスエスID"
          Mode:Managed  Frequency:2.412 GHz  Access Point: 00:24:A5:xx:xx:xx
          Bit Rate=48 Mb/s   Tx-Power=20 dBm
          Retry  long limit:7   RTS thr:off   Fragment thr:off
          Encryption key:off
          Power Management:on
          Link Quality=70/70  Signal level=-19 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:1  Invalid misc:16   Missed beacon:0

eth0      no wireless extensions.

リンクはOKみたいです

root@raspberrypi:~# route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         192.168.11.1      0.0.0.0         UG    0      0        0 wlan0
192.168.11.0      *               255.255.255.0   U     0      0        0 wlan0

ルーティングテーブルもOKみたいです。おや?簡単に繋がりました。インターネットもOK。Raspberry Pi側から通信する事は全然問題は無いですね。ですが・・・
クライアントのWindowsPCから有線LANでは繋がったSSHが無線LANでは繋がりません。(涙)

かなりの時間を費やしてようやく原因がわかりました。

(1)Windows PC   --- ping ---> Raspberry Pi  <NG>
(2)Raspberry Pi --- ping ---> Windows PC    <OK>
(3)Windows PC   --- ping ---> Raspberry Pi  <OK> (しばらくの間だけ)

上記のような状況です。Raspberry PiからクライアントPCに向けてPingを打った直後のみ、クライアントPCからRaspberry Piに通信できます。クライアント側からの ARP Request に Raspberry Pi の wlan0 が応答を返さない事が原因みたいです。

この対策を探すために必死にラズベリーパイのフォーラム等を探しまくりましたが、一番有力な解決策と思われるものは定期的にRaspberry Pi側からPingを打つ方法でした。簡単と言えばそうですが、これはスマートとはいえない解決策ですね。たいした成果も得られず、しかも夜遅くまで作業していたので、私はくたくたになってしまいました。(意識もうろう)

そこで後日、状況を整理するために、普段使っていなかった無線ルータ(PCI GW-MF54G2)で実験環境を作ってみましたところ、驚いたことに、何の問題もなく無線LANでクライアントマシンからSSH通信ができました。Raspberry Pi側ではなく、アクセスポイント側 BUFFALO WHR-HP-GNの仕様に問題があったということが結論でした。

バッファロのサイトを見たら新しいファームウエア【Ver.1.85 (R1.18/B1.00)】があって、「broadcast通信が出来なくなる場合がある問題を修正しました。」と書かれていましたので早速アップデートしたところ、現在使用している無線LANアクセスポイント (WHR-HP-GN)でも使えるようになりました。めでたしめでたしです。