秋月電子通商で購入したドップラーセンサーモジュールを前回製作したArduCAMの回路に追加しました。
■ドップラーセンサーモジュール(24GHz)DIP化セット NJR4265 J1(24GHz)
OpenHABにはメールを送るアクションがあります。訪問者があった時、ドップラーセンサーで感知して画像をメールする一連の動作を考えました。が、しかし。結論から言いますと、上手く動作しませんでした。今回は失敗ですが、備忘のために作業した記録を残します。
NJR4265J1は説明によると、24GHz帯マイクロ波ドップラーによる移動体検知を行う人感センサ(最大10m)で、単一電源3.3V~5.0V動作です。シリアルインターフェースはCMOSレベル、10KΩ抵抗でプルアップされてます。
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
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
続いて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 が便利です。