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

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

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

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

Arducam回路図 02

Arducam回路図 02

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

ArduCAM + NJR4265J1写真

ArduCAM + NJR4265J1

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

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

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

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

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

#include <PubSubClient.h>

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

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

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


const int CS = 16;


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

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

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

WiFiClient espClient;
PubSubClient mqtt_client(espClient);

ESP8266WebServer server(80);

ArduCAM myCAM(OV2640, CS);

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

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

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

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

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

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

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

  myCAM.CS_HIGH();
}

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

  int total_time = 0;

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


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

  total_time = 0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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



}

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

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

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

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

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

}

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

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

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

MQTTlens 01

MQTTlens 01

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

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

MQTTlens 02

MQTTlens 02

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

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

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

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

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

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

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

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

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

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

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

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

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

import java.util.concurrent.locks.ReentrantLock

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

rule "Send Mail"

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

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

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

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

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

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

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

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

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

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

OpenHABからの表示

OpenHABからの表示

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

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

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

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

Arducam回路図01

Arducam回路図01

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

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

をロードします。

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

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

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

ArduCAM Start!
Can't find OV2640 module!

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

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

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

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

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

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

画像が乱れてしまった

画像が乱れてしまった

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

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

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

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

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

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

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

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

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

ようやく表示されました

ようやく表示されました

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

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

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

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


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

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

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

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

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

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

const int CS = 16;

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

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

ESP8266WebServer server(80);

ArduCAM myCAM(OV2640, CS);

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

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

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

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

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

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

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

  myCAM.CS_HIGH();
}

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

  int total_time = 0;

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


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

  total_time = 0;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

[ERROR]: No response from device 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

OpenHABから表示されました

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

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

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

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

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

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

■itemsの編集

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

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

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

■sitemapの編集

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

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-1

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

 

 

 

 

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

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

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

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

■persistenceファイルの編集

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

Items {

light_sensor : strategy = everyMinute

}

■sitemapの編集

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

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

openhab03-2

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

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

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

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

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

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

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

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

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

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

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

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

    var Number counter = 0
    var Number lastCheck = 0

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

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

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

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

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

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

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

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

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

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

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

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

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

■gatepost.rulesの作成

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

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

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

■OpehnHABの再起動

$ sudo  /etc/init.d/openhab restart

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

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

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

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

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

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

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

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

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

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

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

回路図

回路図

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

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

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

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

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

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

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

配線01

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

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

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

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

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

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

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

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

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

失敗してしまった1号機

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

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

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

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

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

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

/*
 Basic ESP8266 MQTT example

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

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


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

// Update these with values suitable for your network.

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

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

void setup_wifi() {

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

openHABの特徴です。

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

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

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

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

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

$ java -version

条件を満たしていない時

$ sudo apt-get install openjdk-7-jre

$ sudo update-alternatives --config java

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

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

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

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

$ sudo update-rc.d openhab defaults

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

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

$ unzip distribution-1.7.1-demo.zip

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

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

$ sudo /etc/init.d/openhab start

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

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

デモサイト

デモサイト

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

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

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

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

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

 Select sitemap  Demo Houseを選択

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

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

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

■opnehab.cfgの設定

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

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

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

■sitemapの作成

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

■itemsの作成

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

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

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

/* 
 Basic ESP8266 MQTT example

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

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

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

// Update these with values suitable for your network.

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

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

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

void setup_wifi() {

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

  WiFi.begin(ssid, password);

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

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

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


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

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

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

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

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

■動作確認です

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

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

作成したサイト

作成したサイト

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

*/

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

// Update these with values suitable for your network.

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

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

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

void setup_wifi() {

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

  WiFi.begin(ssid, password);

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

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

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

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

}

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

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

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

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

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

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

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


 

Raspberry PiとArduino 間 nRF24L01+ 通信実験

一月の末に RS Compornets社に注文予約して待つこと8ヶ月、やっと Raspberry Piが先日我が家に届きました。私はケースも一緒に頼んだので送料込みで合計金額が$49.49 USD 、日本円で¥4,082でした。

早速 Debian派生のLinux、Raspbianをインストールしてみました。イントールにあたってはJunさんのホームページがとても参考になります。raspbianのバージョンですが、私は2012-09-18-wheezy-raspbianをインストールしました。蛇足になりますが、「ラズビアン」っては、日本語では少々危ない響きですね。わたしは滑舌(かつぜつ)が良くないのでとても心配です。

GUIで操作することもできて、ネットワークにも繋がる。それでいてこの価格は、すごいの一言ですね。色々と応用ができそうです。

その後、Raspbery Piの GPIO で nRF24L01 を動かす記事を発見しました。

Raspberry Pi View topic – NRF24L01 RF Transceiver

Beaglebone 用に書かれたnRF24L01 Libraryを Raspberry Pi で動かせるように書き直したと書かれています。( Thanks Mr. Purinda Gunasekara & Mr./Ms. direk! ) この記事を参考に、Raspbery Pi と Arduinoとの通信実験を行いました。

※ 2.4GHz帯 ISMバンドを使用する小電力機器でも日本国内で使用する為には、電波法により技術基準に適合していることを、特定の認証機関で証明してもらう必要があります。違反しますと、1年以下の懲役または100万円以下の罰金という刑事罰の対象になり得ますので、くれぐれもご注意ください。

こちらより nrf.tar.gz をダウンロードします。(Click here to start download from sendspace をクリックするとダウンロードできます)

以下、私はRaspberry PiにSSHで接続して作業を行いました。

$ tar -xzvf nrf.tar.gz
$ cd nrf3

解凍してできた nrf3ディレクトリにある main.cpp がRaspberry Pi側のサンプルコードです。pingを打ってArduino側から戻ってくるreplayを待ちます。ArduinoのRF24ライブラリのサンプルプログラムを元に作られているようですね。roleピンの機能は省かれているみたいです。

また、pingpair_dyn_arduino_pongbackディレクトリにあるコードはArduino側のものになります。Raspberry Pi からのping を待ち、受け取ったら応答を返す動作をします。ちなみにArduinoにはRF24ライブラリが必要です。

$ make

コンパイルすると dist/Debug/GNU_Arm-Linux-x86/ ディレクトリに rf24bb が作成されます。これが実行するプログラムになります。

次に上記プログラムを実行するために、Raspberry PiでSPIを使えるようにします。「竹本 浩氏のページ」を参考にさせていただきました。

/etc/modules に spidev の1行を追加して、/etc/modprobe.d/raspi-blacklist.confのblacklist spi-bcm2708を#でコメントアウトします。

</etc/modules>

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

snd-bcm2835
spidev

</etc/modprobe.d/raspi-blacklist.conf>

# blacklist spi and i2c by default (many users don't need them)

#blacklist spi-bcm2708
blacklist i2c-bcm2708

リブート後、

# ls -l /dev/spidev* を実行して /dev/spidev0.0 and 0.1 が表示されることを確認します。これでソフトウエアの準備は完了です。


ハードウエアの結線ですが、raspberry pi 側のmain.cppの中に RF24 radio(8, 25); の記述があるのでnRF24L01のCEをGPIO 8,CSNをGPIO 25に繋ぎます。

同様に Arduino側 pingpair_dyn_arduino_pongback.ino 中に RF24 radio(4,3); の記述があるのでnRF24L01のCEをdigital 4,CSNをdigital 3に結線しました。必要であれば数値を変更してピン・アサインを変えることができます。

$ sudo ./nrf3/dist/Debug/GNU_Arm-Linux-x86/rf24bb

全てをセットした後に、Raspberry Pi 側ではルート権限で rf24bbを走らせます。同じくArduino側もスタートさせておきます。

Raspberry Pi のターミナルに以下のように表示され、Raspberry Pi と Arduino 間の通信を確認することができました。

STATUS = 0x0e RX_DR=0 TX_DS=0 MAX_RT=0 RX_P_NO=7 TX_FULL=0
RX_ADDR_P0-1 = 0xf0f0f0f0e1 0xf0f0f0f0d2
RX_ADDR_P2-5 = 0xc3 0xc4 0xc5 0xc6
TX_ADDR = 0xf0f0f0f0e1
RX_PW_P0-6 = 0x20 0x20 0x00 0x00 0x00 0x00
EN_AA = 0x3f
EN_RXADDR = 0x03
RF_CH = 0x78
RF_SETUP = 0x25
CONFIG = 0x0f
DYNPD/FEATURE = 0x3f 0x04
Data Rate = Model = CRC Length = PA Power = Now sending length 4...Got response size=4 value=ABCD
Now sending length 6...Got response size=6 value=ABCDEF
Now sending length 8...Got response size=8 value=ABCDEFGH
Now sending length 10...Got response size=10 value=ABCDEFGHIJ
Now sending length 12...Got response size=12 value=ABCDEFGHIJKL
Now sending length 14...Got response size=14 value=ABCDEFGHIJKLMN

・・・

NRF24L01+ と DS18B20+と Arduinoでリモート温度計 3

さて、シリアルモニタに温度を表示させるだけではつまらないので、現在動いている自宅サーバにログを取って、その結果をweb上からグラフにして表示させてみることにします。

Kenji.Yさんが構築された素晴らしいシステムには遠く及びませんが、とにかく頑張りましょう。Ethernetシールドを使用してHTTP Getメソッドでサーバにデータをcsv形式で記録し、それを基にグラフ作成です。グラフの描写はhighstockで行うことにしました。

サーバ側のコードはphpで書きました。これは、Mapo堂さんの記事を参考にさせていただきました、ありがとうございます。現在私のところのPHPは Version 5.3です。

tempera_log.php
[PHP]

[/PHP]
動作確認のためにブラウザから

http://サーバアドレス/パス/tempera_log.php?tempera=1234.5

などとアドレス欄に入力してtempera_log.csvが作成され、数値が記録されるか確かめます。私の場合データの投稿はLAN内からのアクセスですので、外部からこのファイルにアクセスできないようにアクセス制限を掛けておきました。もし将来仮に、インターネット側からデータを送らなければいけないときは、安全性を考慮して別の手立てをしなければならないでしょう。

ログファイルの置かれているディレクトリはwebサーバを動かしているユーザーから書き込む事ができるようにアクセス権を設定します。私はいつもこのことで失敗してしまいます。(学習能力がまるで無い)

続いて、先日作成したRx_data.inoにwebサーバアクセス用のコードを付け加えます。 Arduino 1.0 の Examplesにあるwebclientスケッチを元に作成しました。

Rx_data_log.ino ( Rx_log_data.zip )

Rx_data.ino スケッチの冒頭に追加します

[C]
#include

// Ethernet configration

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //ほとんどの場合macアドレスはこのままで大丈夫です
byte ip[] = { 192, 168, 1, 201 }; //イーサネットシールドのIPアドレス:LAN環境に合わせて設定変更が必要です
byte gateway[] = { 192,168,1,1 }; //デフォルトゲートウエイアドレス: 同上
byte subnet[] = { 255, 255, 255, 0 }; //サブネットマスク: 同上

IPAddress Logging_server(192,168,1,100); //logを記録するwebサーバのIPアドレス

EthernetClient client;   // イーサーネットクライアントライブラリ をイニシャライズ
[/C]

void loop(void)内の最後に追加します

[C]
// start the Ethernet connection:
Ethernet.begin(mac, ip ,subnet,gateway ); //subnet,gatewayの順番はArduino1.0のためのものです(バグ?)
delay(1000); // イニシャライズの為のディレイです

if (client.connect(Logging_server, 80)) {
Serial.println(“connected to Logging server”);

client.print(“GET /cgi-bin/tempera_log.php?tempera=”); // /cgi-bin/tempera_log.phpへ HTTP GETリクエストです:
client.print( got_data);
client.println(” HTTP/1.0″);
client.println();
}
else {
Serial.println(“Logging server connection failed”);
}
client.stop();
[/C]

さて最後に一番手こずったのがグラフ描写です。今回試したhighstockは以前使ったRRDtoolsとは違いJavaScriptで書かれていて、クライアント側でグラフを描くものです。

Highsoft社が開発元で、個人利用であればフリーで使うことができます。また、姉妹版のhighchartsというライブラリもあって、豊富な種類かつ表現豊かなグラフを描くことができます。highstockは時系列のデータに対して相性が良いようです。

こちらからライブラリをダウンロードして適切なディレクトリに解凍して設置します。

ドキュメントを参考にしてプログラムを組んだのですが、CSVファイルの取り込みがどうしてもうまくいかなくて苦労しました。サポートフォーラムの中を必死に探してようやく動かすことができました。でも、ひょっとすると、もっと根本的な恥ずかしい間違いを犯しているかもしれません。

このソフトに関しては日本語の資料があまりなく、へっぽこメモさんのwebに助けていただきました。ありがとうございます。

highstock.jsとtempera_log.csvのファイルのパスに注意します。文字コードセットをUTF8で保存します。

temperature.html





Highstocks Example






こちらにグラフサンプル


NRF24L01+ と DS18B20+と Arduinoでリモート温度計2

無事DS18B20+センサを使って温度を計測することができましたので、NRF24L01+無線モジュールをつかって無線でデータを飛ばすことにします。 送り手側は電池駆動で試してみました。きっと、将来他の事例にも応用が利くと思います。

今回の実験のためにもう一台Arduino互換機を作る事にしました。秋月電子からATMEGA168/328用マイコンボード(1枚150円)、水晶振動子、抵抗、コンデンサ、ピンソケット等、必要最小限の部品を購入して組み立てました。USBインターフェースと電源レギュレーターは省略します。

ATmegaマイコンボードとAVRISP MkII

この基盤にはICSP端子がついていますので、電源とAVRプログラマAVRISPmkII を繋ぐとArduino1.0 IDEからブートローダとスケッチ双方が書き込めますので大変便利です。

さらに秋月電子からは 昇圧型DC-DCコンバーター(3.3Vタイプ)を仕入れました。入力電圧が0.7~3.3V出力電圧が3.3V最大出力電流が200mAという性能です。広告には電解コンデンサを1個追加するだけで使えると書いてありました。

センサー、無線モジュールの他にMCUの電源も DC-DCコンバータから供給するように配線します。

送信側ハードです

ハードは整いました。ソフトウエアはJ. Colizさんが書かれた RF24ライブラリにバンドルされている「pingpair_sleepy」スケッチを基にして作りました。この「pingpair_sleepy」は電池駆動のために消費電力を抑えるAtmegaのスリープモードとウオッチドッグタイマを利用しています。

ATmegaのスリープモードは

SLEEP_MODE_IDLE  アイドルモード
SLEEP_MODE_ADC   ADCノイズ低減モード
SLEEP_MODE_PW_SAVE パワーセーブモード
SLEEP_MODE_STANDBY スタンバイモード
SLEEP_MODE_PWR_DWN パワーダウンモード

5種類あって、パワーダウンモードが一番消費電力が少ないモードになっています。また、スリープモードから復帰するためにWatchdog(WDT)タイマーを使って割り込みを発生させます。

ウオッチドッグタイマーは、スリープモード中でも動いている内蔵128KHz発信器のクロックをカウントして指定のタイムアウト時に割り込み又はシステムリセットを行う機能を持ちます。

タイムアウト時間は16ms,32ms,64ms,128ms,250ms,500ms,1s,2s,4s,8sの中から選ぶことができまして、またそれをループさせることにより任意の間隔で一連の処理を行うことができます。

「pingpair_sleepy」スケッチでは setup_watchdog()のパラメータでタイムアウト時間を指定し、sleep_cycles_per_transmissionの値の倍数の期間だけスリープモードを繰り返すように組んであります。ただ、内蔵発信器のクロックなので時間の精度はあまり良くないようです。ATmega MCU内部動作の詳細はデータシートの訳 こちら を参考にさせていただきました。awawaさんありがとうございました。

スリープモードの時間はいろいろな値を設定することができますが、現在私は setup_watchdog(wdt_4s)そして sleep_cycles_per_transmission = 75として、5分に一回データを送るように設定しました。

以下にarduino1.0用に作成したスケッチを記述します。オリジナルから変更点は、温度センサーの値を読み込む処理、送受信役割分担(ロールピン)の部分を削除した所です。私のハードがNRF24L01+無線モジュールのCE,CSNがそれぞれデジタル8,9ピン、DS18B20+温度センサのDQがデジタル6ピンにつががっていますので、スケッチの該当する部分もそれに合わせてあります。温度センサの解像度は10bitに設定してあります。

送信側スケッチ Tx_data.zip
受信側スケッチ Rx_data.zip

このスケッチを動かすためには前述のOneWireライブラリRF24ライブラリをインストールする必要があります。

送信側と受信側双方をセットして動かすとシリアルモニターに温度が無事表示されました。シリアルモニタの通信速度はNRF24L01+無線モジュールの関係で57600bpsになっています。

実際に回路の電流を測ってみると、待機時に流れる電流が約0.2mAでした。どのくらい電池が持つか計算してみましょう。こちらの記事を参考にさせてもらいました。

スケッチが動いているときの消費電流は

 ATmega328P-PU         20mA
 NRF24L01+ モジュール   11mA
 DS18B20+  センサ        2mA
 DC-DCコンバータ         6mA (変換効率が85%と言うことで)
 
 計      おおむね 40mA
 

スケッチが動いている実働時間は2秒程度、待機している時間が約300秒(5分)なので平均の消費電流は

(2s/300s)*40mA+0.2mA ≒ 0.5mA

単三電池の容量が2000mAh程度なので

2000mAh÷0.5mA = 4000h(時間) ≒ 166日

計算では結構長く電池が持ちそうです。いずれこの期間についても検証してみたいと思っています。

※ 上記のハード・ソフトで 2012年3月3日 から 2012年8月17日まで、電池交換なしで5.5ヶ月間動き続けました。8月17日に落雷が原因で受け取り側のPC(サーバ)が不調にならなければ、もっと長い期間の記録になったはずでした。