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がインストールされていれば同時に入っているかもしれません。(※ Windows10ではBonjourは必要ないようです)

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から表示されました