grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


ラベル IoT の投稿を表示しています。 すべての投稿を表示
ラベル IoT の投稿を表示しています。 すべての投稿を表示

2017年11月8日水曜日

Wifiカメラ付きLED電球/AliExpress

以前AliExpressから購入した人感センサ付きLED電球の延長で、またいろいろと物色していたら今度はカメラ付きのLED電球というものがありました。以下。

AliExpress.com Product - light bulb Wireless camera 960P bulb Panoramic 360 degree camera with fisheye lens use E27 Port 1.3MP LED Bulb wifi camera3283円(送料無料)
どうやらスマホを通して、照明をON/OFFしたりカメラで監視できるようです。カメラがついているため少々高価ですが、マイクやスピーカも内蔵されているようで、スマホからの呼びかけで音もでるようです。カメラやWifi技術が安価になったため、このような装置は簡単に製品化できるのでしょう。既存の電球のソケットに差し込むだけでいいのでインフラを変える必要もないし設置場所もとらずに済みます。Iotや人工知能で複雑なことをしなくても、こういった既存の安価な技術の組み合わせだけでも、まだまだ便利なものをつくることはできそうです。スマホやパソコンだけではなく、そのうちすべての電化製品にカメラとWifiが標準装備されていくのでしょう。ただ、このような装置を配置することで、人工知能に必要なデータを収集していくには便利かもしれません。オフィスや普通の家庭内の各部屋にこのようなカメラ付き照明が取り付けられるのだとは思いますが、プライベートが筒抜けになるので、その辺は気をつけたほうがいいのかもしれません。店舗などに設置すれば、セキュリティだけでなく客層や客の行動パターンなどのマーケティングに有効なデータが簡単に集められそうです。ついつい、人工知能の技術を用いて何ができるかというアウトプット側のインテリジェントデバイスばかり想像してしまいますが、このような人工知能に必要なデータ収集するインプット側のデバイスを安価につくるのもこれからは必要だと思います。アウトプット側のデバイスについては、やはり既にデータセットを持っている大企業にはかなわないので、中小企業はインプット側(データ収集側)のデバイス開発をしたほうがいいのかもしれません。そうすれば、人工知能のノウハウを持たない中小企業であっても、人工知能に便乗した製品をつくることができ存続していけるのかもしれません。まだ購入はしていないのですが、そのうち購入してみようかと。

2017年3月27日月曜日

IoTその5:音声認識でESP8266をWifi制御

いくつかのスイッチにIPアドレスを与えてスマホやパソコンのブラウザからオンオフできるようにはしてみましたが、次はそれを音声(アマゾンエコーのように?)でオンオフできないかとネットを探していました。
Google Cloud Speech APIがかなりすごいので、そのまま使えないかと思いましたが、月間60分までは無料、それ以上は15秒ごとに0.006ドルらしい(しかも月間100万分までの制限つき)。もし、ずっとつなぎっぱなしなら、1分で0.024ドル、1時間で1.44ドル、1日で34.56ドル、1ヶ月で1036.8ドルということになってしまいます。ということで、他の方法で。

Siri+Raspberry Pi+Arduinoでやっている例もありましたが、今回は単純に:
Web Speech API2012年のはここ
・ESP8266
でやってみました。

ネット上にあるWeb Speech APIのサンプルを試してみたりしましたが、まあまあの認識力があり、それなりには使えそうです。基本的にはChromeかFireFoxを使用しなければいけないようですが、MacBookからのChrome、AndroidからのChromeでも認識できました(FireFoxはまだ合成音声/発話のみらしいです)。

*デモページ(このブログ内)はこちらへ(日本語仕様)




自動タイムアウトという問題点:
Web Speech APIは、無言のままでいると約5〜6秒でタイムアウトしてしまい、その後はいくら話しかけても反応しなくなります。
大抵のサンプルは、ボタンをクリックし、タイムアウトする前に話しかけるという手順になっています。しかし、音声認識させるためにボタン操作するのであれば、電源用ボタンを画面上につくっておいて、それをクリックしたほうが早いということになってしまい二度手間です。
理想的には、音声が聞こえるまで長時間待機していられればいいのですが、この自動的にタイムアウトしてしまう機能がなんとかならないかと検索してみると

recognition.onend = function(event) {
    recognition.start();
}

このように認識コマンドが終了したら、またスタートさせる(ある意味無限ループ)と可能と書いてありました。プログラム的にはあまりよくなさそうですが、たしかに、これを試してみると、10秒経っても、あるいは数分経っても入力待機しており、その後話しかけても反応しました。
これとは別に、アマゾンエコーのようにずっと待機させておくには、同時にブラウザでそのアドレスのサイトをずっと立ち上げておかなければいけないという問題もあります。
追記:
実は上記の方法だとスマホChromeではエラーが出てフリーズしてしまうので、その後改良してエラーなしで動作するようになりました(まとめのページ中ほどに書いてあります)。

バックグラウントでも動く:
MacBookでプログラムを書いたページ(Chrome上で)をバックグラウンドで立ち上げていても反応するので(その分メモリや電力は消費され続けるけれども)、気が向いたときに話しかけても機能しました。


例えば上画像のように、このブログを書いている最中に別のタブにサイトを立ち上げておいても機能し続けます。Speech Recognitionタブの赤丸が消えると、音声認識は停止してしまいますが、ずっとつきっぱなしです。画面を最小化して隠してしまい、他のアプリケーションを立ち上げて作業していても機能しました。
ということで、実際使うかどうかは後回しにして、できるところまでやってみることに。APIの説明を見ると、文法の解析だったりとかけっこう複雑なこともできるのかもしれませんが、とりあえずこちらが用意したコマンド(言葉)を認識したら、オンオフするという程度のものをつくろうと思います。

手順としては:
ESP8266に、Web Speech API+Javascript+HTMLを書き込んで、パソコンやスマホからESP8266のIPアドレスにアクセスして、音声認識でスイッチをオンオフするという感じです。
今回は、照明器具の電源のオンオフとエアコンのオンオフ(赤外線通信)をしようと思います。
以下がESP8266に書き込んだプログラムです(その後改良したので、まだ挙動が変かも)。

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

#define relayPin 5
#define IRPin  4
#define ledPin  15

#define duty_high 8
#define duty_low 16

MDNSResponder mdns;
const char* ssid        = "*****";
const char* password    = "*****";
ESP8266WebServer server(80);

unsigned long data_on[243] = {3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 
1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550, 1050, 550, 250, 550, 300, 500, 250, 
550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300, 500, 
350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 
450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 
350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 
1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 
450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350, 450,
350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 
450, 350, 450, 400, 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 
350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 
400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350, 450, 350, 450, 
350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 
400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 
350, 450, 1200, 400, 400, 400, 400, 400, 1200, 400, 400, 400};

unsigned long data_off[99] = {3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 
1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550, 1050, 550, 300, 500, 250, 550, 300, 
500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350, 450, 300, 500, 
350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 
450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 
350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 
450, 350, 450};

String webPage="";

void handleRoot(){
  Serial.println("Access");
  contents();
  server.send(200, "text/html", webPage);
}

void lamp_on(){
  digitalWrite(relayPin,HIGH);
  contents();
  server.send(200, "text/html",webPage);
}

void lamp_off(){
  digitalWrite(relayPin,LOW);
  contents();
  server.send(200, "text/html",webPage);
}

void air_on() {
  int dataSize = sizeof(data_on) / sizeof(data_on[0]);
  for (int i = 0; i < dataSize; i++) {
    unsigned long duration = data_on[i];
    unsigned long start_time = micros();
    while (start_time + duration > micros()){
      digitalWrite(IRPin, 1-(i&1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
  contents();
  server.send(200, "text/html",webPage);
}

void air_off() {
  int dataSize = sizeof(data_off) / sizeof(data_off[0]);
  for (int i = 0; i < dataSize; i++) {
    unsigned long duration = data_off[i];
    unsigned long start_time = micros();
    while (start_time + duration > micros()){
      digitalWrite(IRPin, 1-(i&1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
  contents();
  server.send(200, "text/html",webPage);
}

void setup() {
  webPage="";  
  pinMode(IRPin, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(relayPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  digitalWrite(relayPin, LOW);

  Serial.begin(115200);
  WiFi.begin(ssid, password);
  WiFi.mode(WIFI_STA);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  WiFi.config(IPAddress(192,168,3,10),IPAddress(),IPAddress());
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  if (MDNS.begin("mirror")) {
    Serial.println("MDNS responder started");
  }
  server.on("/", handleRoot);
  server.on("/index.html", handleRoot);
  server.on("/lamp_on", lamp_on);
  server.on("/lamp_off", lamp_off);
  server.on("/air_on", air_on);
  server.on("/air_off", air_off);
  server.begin();
  MDNS.addService("http", "tcp", 80);    
}

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

void contents(){
webPage="<!DOCTYPE html><html><head><meta charset='UTF-8'/>\
<title> Speech Recognition</title><script type='text/javascript'>\
var rec=new webkitSpeechRecognition();\
rec.continuous = true;\
rec.interimResults = false;\
rec.lang = 'ja-JP';\
rec.start();\
var apiSpeech=new SpeechSynthesisUtterance();\
apiSpeech.lang = 'ja-JP';\
apiSpeech.rate=1.2;\
apiSpeech.text='こんにちは';\
var mode=0;\
rec.onresult = function (e) {\
for (var i = e.resultIndex; i < e.results.length; ++i) {\
if (e.results[i].isFinal) {\
var youSaid= e.results[i][0].transcript;\
var apiSaid='';\
var apiHtml = document.getElementById('api');\
var youHtml = document.getElementById('you');\
if(mode==0){\
if(youSaid=='こんにちは'){mode=1;\
apiSaid=youSaid+'、どうぞ';\
}else{apiSaid=youSaid+'?';}}else{\
if(youSaid=='ライトスイッチオン'){mode=0;\
apiSaid='OK、'+youSaid;\
window.location.href = 'lamp_on';\
}else if(youSaid=='ライトスイッチオフ'){mode=0;\
apiSaid='OK、'+youSaid;\
window.location.href = 'lamp_off';\
}else if(youSaid=='エアコンスイッチオン'){mode=0;\
apiSaid='OK、'+youSaid;\
window.location.href = 'air_on';\
}else if(youSaid=='エアコンスイッチオフ'){mode=0;\
apiSaid='OK、'+youSaid;\
window.location.href = 'air_off';\
}else{apiSaid='もう一度';}}\
apiSpeech.text=apiSaid;\
speechSynthesis.speak(apiSpeech);\
apiHtml.innerHTML ='API:  '+apiSaid;\
youHtml.innerHTML='あなた:  '+youSaid;}}};\
var count = 0;\
var countup = function(){console.log(count++);};\
apiSpeech.onstart=function(){rec.stop();};\
apiSpeech.onend=function(){\
setTimeout(countup, 3000);\
rec.start();};\
rec.onend = function(){\
setTimeout(countup, 5000);\
rec.start();}\
</script>\
</head><body>\
<div style='font-size:30px; text-align:center;'>\
<p> Speech Recognition</p>\
<p id='api'>「こんにちは」で開始</p>\
<p id='you'>.....</p>\
</div><div style='font-size:18px; text-align:center;'>\
<p>MENU</p>\
<p>「こんにちは」:操作開始</p>\
<p>「ライトスイッチオン」</p>\
<p>「ライトスイッチオフ」</p>\
<p>「エアコンスイッチオン」</p>\
<p>「エアコンスイッチオフ」</p>\
</div></body></html>";}

最初はHTMLやJavascriptを外部読み込みさせようと思いましたが、読み込みの際にエラーが起きてしまったので、そのまま全部一つのプログラムに書き込んでしまいました。

HTML、Javascriptの部分でエラー:
後半のJavascriptの部分でもなぜかエラーがでてしまい、試行錯誤していると、どうやらこのHTMLやJavascriptが、改行なしにブラウザに渡されてしまうと、ブラウザ側が読み込めなくなるようでした。
たとえば、

apiSpeech.onstart=function(){rec.stop();};


この一行の最後の部分↑ですが、通常は波括弧「}」のあとにはセミコロンをつけないで書いていますが、あえて「};」というようにセミコロンを付け足してあります。これがないと、なぜかブラウザ上で上記のブログラムが読み込めなくなってしまって機能しませんでした。どうやらプログラム上の分節が上手くできないようで、セミコロンを何箇所かにつけたという感じです。これで一応機能するようになりました。

いろいろスペースあけたり改行したりしなかったり、エスケープシーケンス記号をつかったり試していたので、Javascriptの部分は読みにくくなっています。
以下にさらに改良した内容としてJavascriptの部分だけ書いておきます。

基本的には、最初に「こんにちは」というと、「こんにちは、操作メニューをどうぞ」と言われて操作メニューモードに入るという感じです。
そして、「エアコンスイッチオン」などというと、「OK、エアコンスイッチオン」と返答して、赤外線信号のページへ移行し信号を発するという仕組みです(まだ4つのメニューしかありません)。
認識されない場合は、「にちわ?」などと認識した言葉に?マークをつけて返事してきたり、「もう一度、操作メニューを」などと言ってきます。以下だけのHTML+Javascriptだけでも、音声認識だけなら確かめることができると思います。

追記/正規表現:
その後、音声入力の部分で正規表現を使えばいいということが分かったので、さらに改良中(というか、正規表現も勉強中)。いまのところ(以下のコードも)、こちらが求めている言葉通りでなければ反応しない仕様になっており、そのキーワードの前後に余計な言葉があっても反応しないということになってしまいます。正規表現を使えば、キーワードが含まれていればOKにできたり(あるいは除外することもでき)、余計な言葉(助詞なども)が前後に入っていても大丈夫のようです。
例えば、「ライトスイッチオン」や「エアコンスイッチオン」の場合なら、「ライト」「エアコン」「オン」「オフ」が重要ワードとなり、「オフ」や「(オンに)しない」などという言葉が入っていれば、反応しないようにもできます。「スイッチ」は共通なので、今回の場合なくてもいいかもしれません。

youSaid.match(/^(?!.*(オフ|ない)).*(?=ライト).*(?=(オン|on)).*$/)

たぶん、「ライト・オン」の場合はこんな感じでしょうか?
まだ、この条件に合わせた正規表現については確実ではないのですが、以前よりは確かに受け入れる言葉の幅が広がり、いままでは、「エアコンスイッチオン」と言わないとダメでしたが、「エアコンのスイッチをオンにして下さい」でも反応するようになり、「エアコンをオンにしないで」と言えば、重要ワード「エアコン」と「オン」が含まれていても「〜ない」が含まれているので、ダメという判定を出せるようになりました(まだ改良中なので、このサンプルはなしです)。


*これまでのデモページ(このブログ内)であれば、こちらへ(ブラウザはChromeで)

<!DOCTYPE html><html>
<head><meta charset="UTF-8"/>
<title> Speech Recognition</title>
<script type="text/javascript">
var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition
var rec=new SpeechRecognition();
rec.continuous = true;
rec.interimResults = false;
rec.lang = "ja-JP";
//'en-US','en-GB','de-DE','fr-FR','it-IT','cmn-Hans-CN','ko-KR' 
rec.start();

var apiSpeech=new SpeechSynthesisUtterance();
apiSpeech.lang = "ja-JP";
apiSpeech.rate=1.2;
apiSpeech.volume=0.8;
apiSpeech.text="こんにちは";

var mode=0;
var count = 0;
var countup = function(){
  console.log(count++);
}

rec.onresult = function (e) {
  var youSaid= e.results[0][0].transcript;
  var apiSaid="";
  var apiHtml = document.getElementById("api");
  var youHtml = document.getElementById("you");
  if(mode==0){
    if(youSaid=="こんにちは"){
      mode=1;
      apiSaid=youSaid+"、操作メニューをどうぞ";
    }else{
      apiSaid=youSaid+"?";
    }
  }else{
    mode=0;
    if(youSaid=="ライトスイッチオン"){
      apiSaid="OK、"+youSaid;
      window.location.href = "lamp_on";
    }else if(youSaid=="ライトスイッチオフ"){
      apiSaid="OK、"+youSaid;
      window.location.href = "laml_off";
    }else if(youSaid=="エアコンスイッチオン"){
      apiSaid="OK、"+youSaid;
      window.location.href = "air_on";
    }else if(youSaid=="エアコンスイッチオフ"){
      apiSaid="OK、"+youSaid;
      window.location.href = "air_off";
    }else if(youSaid=="こんにちは"){
      apiSaid="操作メニューをどうぞ";
      mode=1;
    }else{
      apiSaid="もう一度、操作メニューを";
      mode=1;
    }
  }
  apiSpeech.text=apiSaid;
  speechSynthesis.speak(apiSpeech);
  apiHtml.innerHTML ="API:  "+apiSaid;
  youHtml.innerHTML="あなた:  "+youSaid;
  setTimeout(countup, 2000);
  count=0;
}

apiSpeech.onstart=function(){
  rec.stop();
}
apiSpeech.onend=function(){
  setTimeout(countup, 3000);
  count=0;
  rec.start();
}
rec.onend = function(){
  setTimeout(countup, 6000);
  count=0;
  rec.start();
}
</script>
</head><body>
<div style="font-size:30px; text-align:center;">
<p> Speech Recognition</p>
<p id="api">「こんにちは」で開始して下さい</p>
<p id="you">ここに認識された言葉が出ます</p>
</div>
<div style="font-size:18px; text-align:center;">
<br/>
<p>操作メニュー:</p>
<p>「ライトスイッチオン」</p>
<p>「ライトスイッチオフ」</p>
<p>「エアコンスイッチオン」</p>
<p>「エアコンスイッチオフ」</p>
</div>
</body></html>

実は、先ほどの無限ループのイベントハンドラが上手くいかず、たまにAPI自身がしゃべったことに反応して、さらに音声入力されてしまうということがあります。多少ディレイをつけてみたのですが、たまに変になります(まだ慣れていないJavascriptの問題かもしれません)。
しかし認識力は高く、ほぼ一発で反応してくれます。感度のいいマイクを用意すれば、数m離れた場所から話しかけても大丈夫かもしれません。期待以上に簡単にできたので、さらに完成度をあげて他のことにも応用できるようになればいいと思います。

これを生活のなかで実用化するには、Raspberry Piで常時このサイトを立ち上げておき、感度のいいマイクをつけておけばいいのかもしれません。Raspberry Pi用のマイクがないので、まだ確かめてはいませんが、Raspberry Piをステーションにして、複数のESP8266がぶらさがっているような仕組みにしてもいいのかもしれません。Raspberry PiならJuliusという音声認識もあるようなので、Raspberry Pi用のUSBマイクが手に入ったら試してみようと思います。
AliExpress.com Product - 2015 New Mini USB 2.0 Microphone MIC Audio Adapter Driver Free For MSN PC Notebook
157円(送料無料)。とりあえずこんなものでもいいのでUSBマイクを買っておこうかと。
AliExpress.com Product - 7.1 Channel 3D External USB Audio Sound Card Mic Adapter 3.5mm Jack Stereo Headset For Win XP / 7 8 Android Linux for Mac OS
133円(送料無料)。あるいは、マイクを差し込めるソケットつきの音声入力カードなら、こんな感じ。なぜか安い。

AliExpress.com Product - Free Shipping 5PCS APW7142KI-TRG APW7142KI APW7142 SOP8 in stock new and Original IC 2186円(送料無料)。Arduinoなどに直接音声認識させるならこのような音声認識モジュールがいいかと。マニュアルはこちら。実際、使用してみたときの内容はこちら


AliExpress.com Product - LD3320 ASR Voice Recognition Professional SP Voice Recognition Voice Module
1680円(送料無料)。こちらは少し安いモジュールですが、ネットを検索しても使い方が見つけられず。マニュアルは一応ここのはずなんだけど、なぜかない。基板は違うけどこれもLD3320

関連:
続きの改良案はこちら
音声認識Wifiスイッチ/ESP8266使用(まとめ)についてはこちら

2017年3月24日金曜日

IoTその4:再度ESP8266+Blynk

もともとはCNCマシンのワイヤレス化(bCNCのPendant機能Bluetooth装備など)から始まったのですが、bCNCのカメラ機能をIPカメラでも撮影可能にしようとしたところ、
Pythonの再学習(bCNC自体がPythonで書かれているため)
・Wifiデバイス(ESP8266ESP32)の利用
ということになり、今まで使っていたArduino IDEだけではなく、
ESP-IDF
PlatformIOAtom Editor
もやってみようかと。
要はパソコンやスマホのブラウザからもいろんなものを操作可能にしたいということなので、
・HTML
・Javascript
も同時に学習していかなければいけない。やらなければいけないことが一気に増えてしまったという感じです。

その結果、ある程度はESP8266を使って、
・エアコンのオンオフ(赤外線信号)
・温度と湿度の読み込み
・照明器具のオンオフ(あるいは、CNCマシンのメイン電源のオンオフ)
などをスマホのChrome上からも操作できるようになりました。

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

#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define RelayPin 5
#define IRLedPin 15
#define DHTPin   2
#define LedPin   4

#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11

DHT_Unified dht(DHTPin, DHTTYPE);

uint32_t delayMS;
String temp;
String humid;

const char* ssid = "*****";
const char* password = "*****";

MDNSResponder mdns;
ESP8266WebServer server(5900);

String webPage = "";

unsigned long airOn[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550, 
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long airOff[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup() {
  pinMode(IRLedPin, OUTPUT);
  pinMode(LedPin, OUTPUT);
  pinMode(RelayPin, OUTPUT);
  digitalWrite(LedPin, HIGH);
  digitalWrite(RelayPin, LOW);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  temp_humid();
  webContents();
  
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  delay(1000);
  WiFi.begin(ssid, password);
  Serial.println("");
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }
  WiFi.config(IPAddress(192,168,3,10),IPAddress(192,168,3,1),IPAddress(255,255,255,0));
  
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  
  if (mdns.begin("mirror", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/index.html", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
  });
  server.on("/on", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, LOW);
    digitalWrite(RelayPin, HIGH);
    delay(1000);
  });
  server.on("/off", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(LedPin, HIGH);
    digitalWrite(RelayPin, LOW);
    delay(1000);
  });
  server.on("/airon", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_on();
    delay(1000);
  });

  server.on("/airoff", []() {
    temp_humid();
    webContents();
    server.send(200, "text/html", webPage);
    air_off();
    delay(1000);
  });

  server.on("/temphumid", []() {
    webContents();
    server.send(200, "text/html", webPage);
    delay(1000);
  });

    ArduinoOTA.onStart([]() {
    Serial.println("Start");
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();  
  server.begin();
  Serial.println("HTTP server started");
  delayMS = sensor.min_delay / 1000;
}

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

void air_off() {
  int dataSize = sizeof(airOff) / sizeof(airOff[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOff[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_on() {
  int dataSize = sizeof(airOn) / sizeof(airOn[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOn[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRLedPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRLedPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid() {
  delay(delayMS);
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp = "Error reading!";
  } else {
    temp = String(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid = "Error reading!";
  } else {
    humid = String(event.relative_humidity);
  }
}

void webContents() {
  webPage = "";
  webPage += "<html><header><title>WIFI SWITCH</title></header>";
  webPage += "<body style=\"text-align:center;font-size:48px\"><div>ESP8266 WEB SERVER</div><br/>";
  webPage += "<div><div>AC100V SWITCH:</div>";
  webPage += "<div><a href=\"on\"><button style=\"width:80%;font-size:60px\">AC100V:  ON</button></a></div><br/>";
  webPage += "<div><a href=\"off\"><button style=\"width:80%;font-size:60px\">AC100V: OFF</button></a></div><br/>";
  webPage += "<div>AIR-CON SWITCH:</div>";
  webPage += "<div><a href=\"airon\"><button style=\"width:80%;font-size:60px\">AIR-CON: ON</button></a></div><br/>";
  webPage += "<div><a href=\"airoff\"><button style=\"width:80%;font-size:60px\">AIR-CON: OFF</button></a></div>";
  webPage += "<div>Temperature: ";
  webPage += temp;
  webPage += " *C</div>";
  webPage += "<div>Humidity: ";
  webPage += humid;
  webPage += " %</div>";
  webPage += "</div></body></html>";
}


これは前回からの改良で、
・ESP8266
・赤外線LED(エアコン用)
・温度湿度センサー
・リレー(照明器具AC100V用)
を使っています。OTAのコードも含めたので、ワイヤレスでコードの書き換えが可能です。そのぶんコードも長く、メモリ消費量も多いプログラムとなりました。
基本的にはローカルネットワーク内でしか使えないのですが、
Wifi.config()を使えば固定IPアドレスにできるので、ルーターをポートフォワーディングしてみると、外部からもつながりました(スマホ4G通信で確認)。以下はブラウザ上の操作画面。


LANだけでなくWANからも操作できてよかったのですが、以前みつけたスマホアプリのBlynkでやってみたらどうなるかも試してみました。このアプリ見つけたのはいいのですが、しばらく使っていませんでした。どのくらい便利なのか?


以前、ざっと説明を見た限りでは、アプリ上でボタンやスイッチ部品を配置して、余計なコードは書かずにプログラムをアップロード(ワイヤレスでスマホから?)という感じでしたが、
実際手順に沿ってやってみると:
・スマホアプリをインストール+メールアドレスを登録
・New Project作成(使うハードウェアを指定:ESP8266など)
・セキュリティコードがメールへ届く
・Arduino IDEにBlynkライブラリをアップロードする
・Arduino IDEでセキュリティコードとWifiのIPアドレス+パスワードを専用プログラムに追記する
・専用プログラムをパソコンからUSB経由でESP8266へアップロード
・再度スマホアプリに戻り、好きなピンを割り当てながらボタンやスイッチを配置
・LANだけでなくWANからも操作可能
という感じです。


FirmataのようなBlynkファームウェア:
いちいちArduino IDEでプログラムを書かなくても済むというのが予想外に便利でした。最初は、スマホでボタンやスイッチへピンを割り当てて、それにあわせたプログラムをArduino IDEのほうでも書かなければいけないのかと思っていましたが、そうではなく、Blynkの専用プログラムがFirmataのようなファームウェアとして機能しているため、後からでも自由自在にBlynkアプリでESP8266のピンの機能割り当てができるという感じです。デジタル入力にするかデジタル出力にするか、それともPWM出力にするかなど、いちいちESP8266にプログラムをアップロードしなくても、その場で自由に変えられるということです。
ESP8266の場合は、
スケッチ例>Blynk>Boards_WiFi>ESP8266_Standalone
を選択し、
このプログラム内の、
YourAuthToken:メールで送られてきたセキュリティコード
YourNetworkName:自宅Wifiネットワーク名
YourPassword:自宅Wifiのパスワード
を書き込むだけでOKです。すべてのピンへの機能割り当てはBlynkのほうでしてくれるので、他のプログラムを書き込む必要はありません。
最初に送られてくるセキュリティコードはブロジェクトごとに使えるので、ボタンの配置や機能の割り当てを変えてもずっと使えます。

ということで、先ほどのプログラムをBlynkに適用させてみようかと。
しかし、赤外線信号の部分はBlynkではできなさそうなので、Blynkの応用的な機能であるVirtual Pinというものを使ってみました。このVirtual Pinは、実際のESP8266上のピンとは無関係で、Blynk上で使える変数やトリガーのようなものです。今回の赤外線信号の場合であれば、ボタンがオンで1、オフで0という値を割り当てておいて、Virtual Pinが1ならエアコンをオンにする赤外線信号を発する、0ならオフの信号を発するという感じにしておきます。そんな感じで、一度Virtual Pinを介して動作するプログラムにしてみました。

#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#define IRPin  4
#define DHTPin  2
#define duty_high 8
#define duty_low 16
#define DHTTYPE DHT11
DHT_Unified dht(DHTPin, DHTTYPE);
int temp;
int humid;

char auth[] = "*****";
char ssid[] = "*****";
char pass[] = "*****";

unsigned long data_on[243] = {
 3400, 1400, 600, 250, 550, 250, 550, 1050, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 250, 550, 300, 500, 250, 550, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 300,
 500, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350,
 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1150, 450, 1200, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 1200, 450, 350,
 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 400,
 400, 1200, 400, 400, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400,
 400, 400, 400, 400, 400, 400, 400, 400, 450, 350, 450, 1150, 450, 1150, 450, 350, 450, 1200, 400, 400, 400,
 400, 400, 1200, 400, 400, 400};
unsigned long data_off[99] = {
 3400, 1450, 550, 250, 550, 250, 500, 1100, 550, 250, 550, 1050, 550, 250, 550, 250, 550, 250, 550, 1050, 550,
 1050, 550, 300, 500, 250, 550, 300, 500, 1100, 500, 1100, 500, 300, 500, 300, 500, 300, 500, 300, 500, 350,
 450, 300, 500, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150, 450, 350, 450, 350,
 450, 350, 450, 350, 450, 350, 450, 400, 450, 350, 450, 1150, 450, 350, 450, 350, 450, 350, 450, 350, 450, 1150,
 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450, 350, 450};

void setup(){
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  pinMode(IRPin, OUTPUT);
  dht.begin();
  sensor_t sensor;
  dht.temperature().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
  dht.humidity().getSensor(&sensor);
}

BLYNK_WRITE(V0){
  int value = param.asInt();
  if (value) {
    air_on();
  } else {
    air_off();
  }
}

BLYNK_READ(V1){
  temp_humid();
  Blynk.virtualWrite(V1, String(temp)+" °C");
  Blynk.virtualWrite(V2, String(humid)+" %");
  Blynk.virtualWrite(V3, temp);
}

void air_on() {
  int dataSize = sizeof(data_on) / sizeof(data_on[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_on[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_off() {
  int dataSize = sizeof(data_off) / sizeof(data_off[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = data_off[cnt];
    unsigned long us = micros();
    while (us + len > micros()){
      digitalWrite(IRPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(IRPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void temp_humid(){
  sensors_event_t event;  
  dht.temperature().getEvent(&event);
  if (isnan(event.temperature)) {
    temp=0;
  }else {
    temp=int(event.temperature);
  }
  dht.humidity().getEvent(&event);
  if (isnan(event.relative_humidity)) {
    humid=0;
  }else {
    humid=int(event.relative_humidity);
  }
}

void loop(){
  Blynk.run();
}
こんな感じで、BLYNK_WRITE(V0)内に条件分岐をつくって赤外線信号のオンオフ操作をしています。BLYNK_READ(V1)でも、温度センサから読み取った値をBlynkアプリへ渡すようにしています。

スマホアプリのほうでは、こんな感じのボタン配置にしました。
一番上の黄色いスイッチが照明用、その下の白いスイッチがエアコン用、その下に温度と湿度表示、さらに温度のグラフもあります。一応4G通信で外部からアクセスも可能でした。
しかし、温度グラフに関しては、一旦このアプリを消してしまうとそれまでの記録も消えてしまってダメでした。もしかしたら配列を使って過去の記録を覚えさせていけないのかもしれません。このグラフ機能以外にもヒストリーグラフというウィジットがあって、それならいいのかもしれませんが、実はこのアプリ、各ウィジットにはポイント(コスト)があり、例えばボタン一個で200エナジー、グラフが400エナジーであり、合計の上限が決まっています。

ヒストリーグラフは何と900エナジーもします(赤い数値)。この段階で、ボタン2個なので400エナジー、温度と湿度数値表示で400エナジー、そしてグラフで400エナジー、このプロジェクトだけで合計1200エナジー消費しています。もう一つ別のプロジェクトもあって、そっちで500エナジー消費しているので、すべてのプロジェクトを合わせて1700エナジー使っていることになります。残り200エナジーしかありません(画面上部)。
そして、このエナジー量を増やすには課金が必要ということでした。すっかりオープンソースで無料なのかと思っていたら、なるほど、よくある一部有料というアプリでした。

ということで、温度グラフのかわりに消費エナジー500のビデオストリーミングも試してみました。
以前使ったIP WebCamというアプリでタブレットのカメラから配信させてみました。
LAN通信ですが、ちゃんと映ります。これはビデオなのでESP8266とは無関係。このビデオストリーミングの場合、カメラのIPアドレスの欄にhttp://192.168.3.2:8080/videoを入れると大丈夫でした。温度グラフよりも、こっちのほうがよさげ。
ただし、Blynkのサイトにも書いてありますが、Blynkサーバはストリーミングサーバを提供していないためWANからは見れないということです。その場合は他のサービスを使えと書いてあります。

とはいったものの、Blynkで当初予定していた内容はほぼできてしまいました。しかも設定なども簡単です。自前でHTMLやJavascriptまで書いてESP8266をサーバにしてもいいのですが、たしかに面倒。当然自分の思い描いているような内容にすることはできますが、Blynkだとあっというまにできてしまったので、これはこれでいいのかもしれません。この手のサービスはWANからもアクセス可能という部分が便利かもしれません。自宅のルーターをポートフォワーディングさせれば済むことですが、セキュリティなどのことも考えると面倒なので、外部サーバーを利用できるのであれば、それに越したことはありません。

次のステップとしては、ESP8266かESP32にカメラモジュールを接続して小型のIPカメラをつくろうと思っていますが、なかなか思うように進みません。その小型IPカメラをCNCマシンに搭載し、bCNCのカメラ機能をIP化するところまで行くというのがとりあえずの目標です。
ただ、そのついでにその他の照明や電源などもIP化しようとしているので、いろいろやることが増えているという現状です。あわよくば、スマホ上のボタン操作ではなく、音声入力で照明をオンにできないかとも考えていて、Python、HTML5、Javascript、場合によってはPHPなども同時進行という感じです。さらには、それらの開発環境となるAtom EditorやPlatformIOの使い方も学習中です。

2017年3月16日木曜日

IoTその2:ESP8266(Wifi赤外線リモコン:仮)

以前IoTについて投稿しましたが、その時にAliExpressで注文したESP8266を触ってみることにしました。届くまで数週間かかるのですが、その時間差がある分、他のことに興味が湧いていたりするので、届いてもすぐに開封しないで放置しておくことがよくあります。
Arduino関係はCNC以外にしばらくやっていなかったので、今更ESP8266なのですが、そのおかげでネットで調べるとたくさんサンプルがあるので、すぐにできそうです。

575円(送料無料)。ESP-WROOM-02、直接USB接続できるタイプのものです。この画像でもかすかに見えますが、一応技適マークがついていました。

とりあえず、これでやりたいことは、学習型赤外線リモコンにしてエアコンのオンオフをスマホやMacBookのブラウザからすること。それだけだと勿体無いので、温度センサーやリレーによるAC100V電源のオンオフ(照明器具用)。どちらかというと趣味や興味というよりも、実用的な意味でこういったデバイスが必要なので、これもまた自作しようかと。
できれば、もう一つ作って、CNCマシンの電源(AC100V)もブラウザ経由でオンオフすることで、bCNCのPendant機能やBluetoothによる制御も含め、全てワイヤレス化しようかと思っています。

以前、BroadLinkの以下のWifi赤外線リモコンも使えるかなと思いましたが、


eRemote miniに置き換えられたようで、MACアドレスの識別によって専用アプリからは操作できなくなるようです。しかし、中国版は安いのに、日本版はかなり高い(5000円くらい)。ちなみにこれはリモコンだけで温度センサーはついていないようです。
いずれにしても、このようなWifiリモコンであれば、ESP8266と赤外線LEDがあれば1000円以下ですぐにつくることができる他、温度センサーやその他の機能も追加できます。
RM mini 3やeRemote miniは、Marvel88MC200というかなり高性能なCortex M3プロセッサと88W8801というMarvel Wifiチップが使われており(ここに書いてありました)、ESP8266やArduinoで改造もしにくい感じです。GitにもPythonMQTTで操作する方法など載っていましたが、そこまでやる必要もないかと。
ということで、RM mini3は見送って、ESP8266を使って代替品をつくった方がいいという結論になりました。


赤外線リモコンパーツ:
以前にもArduinoで赤外線を使ったことがあったので、赤外線LED赤外線受信モジュールが残っていました。
昔、秋月で買ったパーツ。安い。右の黒いのが赤外線受信モジュール(数十円)。
これらのパーツとESP8266をつなげばWifiリモコンができるはず。さらに温度センサーやリレーを追加してもESP8266も含め1000円以下。


IRremoteライブラリ:
ネットを調べるとArduinoのIRremoteライブラリを使っている人が多そうだったので使って見ました。FUJITSUのエアコンなのですが、簡単に読み込むことができました。各メーカーごとのデータもこのライブラリ内に揃っているようなので、大体は認識してくれるようです。
あとは、このデータを使って実際にエアコンがオンオフできるか試して見ると、あっさり動きました。
そのまま赤外線受信モジュールで読み込ませたRAWデータを再生させる方法もあったので、これで複数のボタンを覚えさせれば、どんなリモコンであっても簡単に操作できそうです。
しかし、よく調べて見ると、赤外線LEDを取り付けるピンの指定があるようです。要はAVRのタイマーに対応したピンでなければならないようです。基本的にはPWMピンの3番ピンや9番ピンを使えという感じです。
通常のデジタル出力のピン(例えば8番ピンなど)でdelayMicroseconds()でパルス生成してもできるのかもしれませんが、もしかすると安定しないのかもしれません。


38kHz:
赤外線リモコンは、パルスのオンオフで信号を送っているのですが、38kHzでさらに細かいパルス(キャリア)を使わなければいけないようです。前もやったことがあったので、何となく思い出してきました。確かこの38kHzの細かいパルス生成の精度やタイミングが難しかったような。
IRremoteライブラリは、この細かいキャリア周波数を精度よくArduinoで実現している代わりに、ピンに制約があるという感じです。
周期が26.3usで、さらに1/3のデューティ比なので、HIGHの時間はたった8.7usとなるようです。ArduinoのdelayMicroseconds()は3usが限界のようで、おそらく8.7usも処理の仕方や割り込みによってはずれてしまいそうです。Arduino Unoの解像度の限界に近い処理なので、C言語で書いた方が良さそうです。
以前、TVの信号をArduinoでつくった時も、そんな感じでタイミングがずれてしまって、画面が歪んでしまったことがありました。


自前でパルス生成:
ライブラリを使えば簡単でしかも安定した周期を実現できそうですが、動けばいいという程度の精度で十分なので、一度delayMicroseconds()を使って自前でやってみることにしました。どのピンでも使えた方が便利なので。
ネットで調べると、ライブラリを使わずに自前でこの周期をつくっている例もあったので、それらを参考に(こちらのサイト)、Arduino Unoで試してみることに。

digitalWrite(pin,HIGH);
delayMicroseconds(9);
digitalWrite(pin,LOW);
delayMicroseconds(17);

26.3usの1周期は、タイミングのずれを考えなければ数値的にはこんな感じでいいのですが、これだと全くエアコンは反応しませんでした。おそらくArduino Unoの内部処理でずれまくっているのでしょう。もう少し調べて見ると、デューティ比は1/3ではなくても大丈夫そうなので、おそらくこの1周期内にあるHIGHとLOWの経過時間の合計の26.3usを重視した方が良さそう。
Arduino Unoは16MHzなので、1クロックが1,000,000us(1sec)/16,000,000=1/16us(0.0625us)。ただ、必ずしも1クロックで全てを処理してくれるわけではないので、記述の仕方ではかなりロスが出るはず。確かdelayMicroseconds()よりも、C言語の_delay_us()を使った方が速かったような。for文など多用するとかなりロスがあったような記憶があります。しかし参考にしたこの方のサンプルでは単純にdelayMicroseconds()を使っており、

digitalWrite(pin,HIGH);
delayMicroseconds(8);
digitalWrite(pin,LOW);
delayMicroseconds(7);  //UNO:7us, ESP8266:16us

合計で理論上26.3usのところ、8+7=15usですが、このタイミングでやって見るとエアコンが反応しました。ロスが11usくらいあるという感じですが、これで機能するのでこのまま行くことに。C言語やアセンブラ言語で動かした方が確実かもしれませんが、ここはあえて平易なArduino言語で簡単に済ませられるのあれば、それに越したことはないという感じです。業務用ではないので、精度よりも簡単さで。
どうやらESP8266の方がクロック数はArduino Unoよりも上なので、遅いArduino Unoで動くのなら大丈夫なはずです。
追記:
その後ESP8266で試したところ、Arduino UnoではdelayMicroseconds(7)の部分が、デューティ比約1/3のdelayMicroseconds(16)でも動きました。ESP8266のクロック数は80MHzなので、Arduino Unoのようなタイミングのズレは考慮しなくても大丈夫そうです。


送信用プログラム:
ということで、以下が送信用の試験用コードです。まだArduino Unoで試している段階なのでESP8266用のコードではありません。
数値の羅列がエアコンのオンとオフの信号(単位us)です。この信号は、次にある受信用プログラムで読み込んだ値です。この信号データを元に、赤外線LEDがパルスをエアコンに送りオンとオフをします。例えば、data_on[243]の方であれば、配列の最初の3456がHIGHを3456us、そしてLOWに切り替えて1412us、またHIGHで580usという感じで、配列の偶数番目(最初は0番目からスタート)がHIGHで、奇数番目がLOWという繰り返しになります。
Arduinoのシリアルモニタ上で、"a"を送信でオン、"b"を送信でオフ。ちなみにFUJITSUのエアコンです。なぜかオンの信号の方が多い。PWM用のピンではなく、8番ピンでHIGH/LOWを単純に切り替えつつdelayMicroseconds()を使っています。

#define send_pin 8
#define duty_high 7
#define duty_low 7  //UNOの場合は7, ESP8266の場合は16

unsigned int data_on[243] = {
  3456,1412,580,216,580,212,572,1036,564,236,556,1044,552,244,544,252,536,256,532,1072,
  524,1080,512,284,512,280,504,292,504,1108,500,1108,492,296,500,296,500,292,500,292,
  504,292,500,296,500,296,496,296,500,296,500,296,500,296,496,300,504,288,500,1108,
  496,324,476,316,476,320,472,320,476,324,476,316,476,320,476,1132,476,316,476,316,
  476,320,476,324,476,320,464,1136,456,1148,452,1156,448,1160,448,1156,448,1156,448,348,
  448,348,444,348,444,1160,440,352,444,352,444,348,448,352,444,352,444,348,444,360,
  436,356,444,1156,444,1160,444,356,444,348,444,1160,448,348,440,356,444,348,440,352,
  444,352,444,352,444,1164,444,348,444,352,444,1160,444,352,444,352,444,352,444,352,
  440,352,444,352,444,352,444,352,440,348,444,352,440,348,440,356,440,352,444,352,
  444,348,444,356,444,348,444,352,444,352,440,348,444,352,440,356,440,356,440,356,
  440,352,440,376,412,360,436,380,416,356,436,356,440,356,436,364,432,376,416,380,
  416,376,412,380,412,384,412,1196,412,1192,408,380,408,1192,412,384,412,380,412,1196,
  412,388,412};

unsigned int data_off[99] = {
  3492,1400,604,192,596,196,588,1020,580,220,576,1024,572,224,564,232,556,236,552,1052,
  552,1056,552,244,548,244,544,252,540,1064,536,1072,532,264,528,268,524,268,524,268,
  524,268,528,268,528,268,528,268,524,268,524,272,524,272,524,296,500,288,500,1088,
  520,296,496,292,500,300,496,296,500,300,496,292,496,296,500,1112,496,296,496,296,
  496,300,496,296,500,1104,500,296,500,296,496,300,496,300,492,304,492,304,488};
  
void setup() {
  pinMode(send_pin, OUTPUT);
  Serial.begin(57600);
}

void loop() {
  if(Serial.available()>0){
    int val=Serial.read();
    if(val=='a'){
      ir_send(data_on,243);
      Serial.println("on");
    }else if(val=='b'){
      ir_send(data_off,99);
      Serial.println("off");   
    }
  }
}

void ir_send(unsigned int data[], int d){
  for (int i = 0; i < d; i++) {
    unsigned long duration = data[i];
    unsigned long start_time = micros();
    while (start_time + duration > micros()){
      digitalWrite(send_pin, 1-(i&1));
      delayMicroseconds(duty_high);
      digitalWrite(send_pin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

C言語(あるいはArduino言語)の場合、配列の長さを取得するには、Javaなどのarray.lengthのようなコマンドはなく、sizeof(array)を使わなければいけないのを始めて知りました(それと動的に配列の長さを変えられないということも)。しかも、sizeof()がメモリのバイト数なので、intの場合sizeof(array)/sizeof(array[0])というように書かないといけないらしく、配列の長さの変数をir_send()関数に入れると、なぜか処理が滞ってしまうので、データの配列data[]とその配列の長さdをir_send(unsigned int data[], int d)という形で直接渡すことにしています。元々はオンとオフの二つ別々の関数をつくっていたのですが、その後ボタンも追加されて信号用配列データも増えるかもしれないので、ir_send()という関数にしてしまいました。そのせいで、また少し悩んでしまったということです。元々、電子回路の制御用プログラムでは、複雑なコードを書かないので、また勉強になったという感じです。
digitalWrite(send_pin,1-(i&1))のHIGH/LOW切り替えの部分は、&でビットマスクすることで奇数か偶数かを分けつつ反転させていますが、digitalWrite(send_pin,!(i%2))でもいいと思います。ただ、そうすると微妙に処理速度が変わってしまうかもしれないので、参考にしたコードのままにしてあります。


受信用プログラム:
読み取りの方はIRremoteでやってもいいのですが、一応こちらもライブラリは使わず。このプログラムで得たエアコンのオンとオフの信号が、上記送信プログラムに使われています。
送信用のデータでは、配列の一個目(配列における0番目)はHIGHですが、受信モジュールで読み込む際には、HIGHとLOWが反転しています。つまり受信用データ配列の0番目はLOWを検出します。

#define read_pin 8
#define led_pin 13
#define data_size 256
unsigned long ir_data[data_size]={};
boolean state;
unsigned int count;
boolean ready_data;

void setup() {
  Serial.begin(57600);
  pinMode(read_pin,INPUT);
  pinMode(13,OUTPUT);
  state=HIGH;//first:HIGH, then:LOW(pulse starts)
  count=0;
  ready_data=false;
}

void loop() {
  if(Serial.available()>0){
    unsigned long start_time=micros();
    while(state==digitalRead(read_pin)){
       ready_data=true;  
    }
    if(ready_data==true){
      if(micros()-start_time<100000){//count over
          output_data();     
        }
      }else{//time over
        state=!state;
        Serial.println("TIME OVER");
        output_data();
      }
      ready_data=false;
    }
  }else{
    digitalWrite(13,HIGH);
    delay(100);
    digitalWrite(13,LOW);
    delay(100);
  }
}

void output_data(){
  Serial.println("ir_data:");
  for(int i=0;i<data_size;i++){
    if(ir_data[i]==0){
      Serial.println("");
      Serial.print("TOTAL:");
      Serial.println(i);
      break;
    }else{
      Serial.print(ir_data[i]);
      if(i%20==19){
        Serial.println(",");
      }else{
        if(i<data_size-1 && ir_data[i+1]!=0){
          Serial.print(",");
        }   
      }
    }
  }
  Serial.println("END");
  Serial.println("");
  for(int i=0; i<data_size; i++){
    ir_data[i]=0;
  }
  count=0;
  Serial.read();//read 3 bytes
  Serial.read();
  Serial.read();  
}

受信用プログラムで得た送信用データをIRremoteライブラリで得たデータと比較してみましたが、それほど大きなずれもなく使えるので、そのままこのプログラムを使ってデータを読み取りました。

光量不足?:
一応、これら二つのプログラムでエアコンを動かすことができましたが、パルスのせいか赤外線LEDの出力が全開ではないため、2mくらいまで近づけないと反応しない時がありました。それもあってLED用の抵抗は外してしまったのですが、もう一つLEDを増やすか電力アップしないといけないかもしれません。データの読み取り精度の誤差によって反応しにくくなっていることも多少考えられます。ノイズ対策は一切行っていないのでコンデンサーを入れるだけでも少しはましになるかもしれません。


制御用ブラウザ画面:
これら赤外線受信/送信用プログラムを元にESP8266用にブラウザで制御可能なプログラムを書いてみました。まだ赤外線でエアコンをオンオフするだけで、AC100V電源のオンオフはリレーをつけていませんが、以下のような感じ。

これもネットにあったサンプルに必要な部分を追加しただけ。
温度計測はこれから追加する予定です。
以下が、ESP8266のプログラム。14、15行目のssidとpasswordに使っているWifi名とパスワードを入れて、ESP8266にArduino IDEからアップロードし、シリアルモニタを開くとIPアドレスが出てきます。あとはブラウザでそのIPアドレスにアクセスすれば、上の画面が現れるはずです。
ブラウザ上に表示させるHTMLはwebPageという変数に入れてあります。HTML内の「\"」は、「"」のことです。そのまま「"」で送るとArduinoのプログラム上で使われる「"」なのか、それとも送られる文字列としての「"」なのか判別できなくなるために、「\"」を使わなければいけません。

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

#define gpio13Led 13
#define gpio12Relay 12
#define sendPin 14
#define duty_high 8
#define duty_low 7 //UNO:7us, ESP8266:16us


MDNSResponder mdns;

const char* ssid = "*****";
const char* password = "*****";

ESP8266WebServer server(80);

String webPage = "";

unsigned int airOn[243] = {
  3456,1412,580,216,580,212,572,1036,564,236,556,1044,552,244,544,252,536,256,532,1072,
  524,1080,512,284,512,280,504,292,504,1108,500,1108,492,296,500,296,500,292,500,292,
  504,292,500,296,500,296,496,296,500,296,500,296,500,296,496,300,504,288,500,1108,
  496,324,476,316,476,320,472,320,476,324,476,316,476,320,476,1132,476,316,476,316,
  476,320,476,324,476,320,464,1136,456,1148,452,1156,448,1160,448,1156,448,1156,448,348,
  448,348,444,348,444,1160,440,352,444,352,444,348,448,352,444,352,444,348,444,360,
  436,356,444,1156,444,1160,444,356,444,348,444,1160,448,348,440,356,444,348,440,352,
  444,352,444,352,444,1164,444,348,444,352,444,1160,444,352,444,352,444,352,444,352,
  440,352,444,352,444,352,444,352,440,348,444,352,440,348,440,356,440,352,444,352,
  444,348,444,356,444,348,444,352,444,352,440,348,444,352,440,356,440,356,440,356,
  440,352,440,376,412,360,436,380,416,356,436,356,440,356,436,364,432,376,416,380,
  416,376,412,380,412,384,412,1196,412,1192,408,380,408,1192,412,384,412,380,412,1196,
  412,388,412};
  
unsigned int airOff[99] = {
  3492,1400,604,192,596,196,588,1020,580,220,576,1024,572,224,564,232,556,236,552,1052,
  552,1056,552,244,548,244,544,252,540,1064,536,1072,532,264,528,268,524,268,524,268,
  524,268,528,268,528,268,528,268,524,268,524,272,524,272,524,296,500,288,500,1088,
  520,296,496,292,500,300,496,296,500,300,496,292,496,296,500,1112,496,296,496,296,
  496,300,496,296,500,1104,500,296,500,296,496,300,496,300,492,304,492,304,488};

void setup(void) {
  webPage += "<html><header><title>WIFI SWITCH</title></header>";
  webPage += "<body style=\"text-align:center;font-size:20px\"><div><font size=\"2\">WIFI SWITCH</font></div><br/>";
  webPage += "<div style=\"font-size:24px\"><div>AC100V SWITCH:</div>";
  webPage += "<div><a href=\"on\"><button style=\"width:80%;font-size:20px\">POWER  ON</button></a></div><br/>";
  webPage += "<div><a href=\"off\"><button style=\"width:80%;font-size:20px\">POWER OFF</button></a></div><br/>";
  webPage += "<div>AIR-CON SWITCH:</div>";
  webPage += "<div><a href=\"airon\"><button style=\"width:80%;font-size:20px\">AIR-CON ON</button></a></div><br/>";
  webPage += "<div><a href=\"airoff\"><button style=\"width:80%;font-size:20px\">AIR-CON OFF</button></a></div>";
  webPage += "</div></body></html>";
  
  pinMode(sendPin, OUTPUT);
  pinMode(gpio13Led, OUTPUT);
  digitalWrite(gpio13Led, HIGH);
  pinMode(gpio12Relay, OUTPUT);
  digitalWrite(gpio12Relay, HIGH);

  Serial.begin(115200);
  delay(500);
  WiFi.begin(ssid, password);
  Serial.println("");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (mdns.begin("esp8266", WiFi.localIP())) {
    Serial.println("MDNS responder started");
  }

  server.on("/", []() {
    server.send(200, "text/html", webPage);
  });
  server.on("/on", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(gpio13Led, LOW);
    digitalWrite(gpio12Relay, HIGH);
    delay(1000);
  });
  server.on("/off", []() {
    server.send(200, "text/html", webPage);
    digitalWrite(gpio13Led, HIGH);
    digitalWrite(gpio12Relay, LOW);
    delay(1000);
  });
  server.on("/airon", []() {
    server.send(200, "text/html", webPage);
    air_on();
    delay(1000);
  });

  server.on("/airoff", []() {
    server.send(200, "text/html", webPage);
    air_off();
    delay(1000);
  });

  server.begin();
  Serial.println("HTTP server started");
}

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

void air_off() {
  int dataSize = sizeof(airOff) / sizeof(airOff[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOff[cnt];
    unsigned long us = micros();
    while (us + len > micros()){
      digitalWrite(sendPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(sendPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

void air_on() {
  int dataSize = sizeof(airOn) / sizeof(airOn[0]);
  for (int cnt = 0; cnt < dataSize; cnt++) {
    unsigned long len = airOn[cnt];
    unsigned long us = micros();
    while (us + len > micros()) {
      digitalWrite(sendPin, 1 - (cnt & 1));
      delayMicroseconds(duty_high);
      digitalWrite(sendPin, 0);
      delayMicroseconds(duty_low);
    }
  }
}

こちらのコードでは、エアコンのオンオフは、air_on()とair_off()の二つ関数に分けて書いてあります。試行錯誤して多少前後したので、実は一個前のコードのままです。今後さらに改良して行きます。まだ実験段階という感じです。
それにしても、こういったコードをブログに載せるとエスケープシーケンスでいくつかの記号を置き換えなければいけないので面倒です。
ちなみにGoogle code-prettifyを使ってコードを載せています。

あとは、IPカメラとも接続可能にできるといいのですが、以前やっていたbCNCのIPカメラ化も中断しているので、そのうちやろうと思います。

2017年1月30日月曜日

IoTデバイスについて

以前CNCマシンで以下のようなことをしましたが、
Bluetoothで無線化
bCNCのPendant機能でスマホから遠隔操作
Laserweb3でWifi経由でスマホから遠隔操作(ページ最後)
と似た内容として、いわゆるIoT(Internet of Things)が最近気になっています。

少し前から、IoTのデバイスを見かけるようになったけれども、せいぜい従来の赤外線リモコンがスマホ1台で操作可能になって、別の部屋のエアコンや照明のつけ忘れをON/OFFするくらいかなと、オフィスビルなどなら、いろんなものを自動化したり、遠隔操作や遠隔管理可能にした方が便利だけれども、実際の生活ではあまり必要ないだろうと考えていました。

仕組みとしては、それぞれの電化製品がWifiに接続して、固有のIPアドレスにスマホからアクセスすれば、スイッチのON/OFF、温度や湿度などの状況、カメラで監視、また状況の変化や設定した入力に連動してSNSにメッセージを送信など。カメラ監視やSNSへメッセージ送信などはプライバシーが筒抜けになりすぎてあまり好ましくないかもしれないけれど、普通に照明器具や電化製品のON/OFFくらいならあってもいいかもしれない。

ということで、いつものAliExpressでIoTデバイスになりそうなものを探してみました。一応国内のAmazonとも比較しながら。

AliExpress.com Product - Broadlink Home Automation System A1 e-Air Smart Air Quality Detector+ RM2 Pro,smart home Remote Control本体だけで4358円(送料込み)
黒いUFOのような本体(昔のAirMacみたいな)が学習型赤外線リモコンを内蔵しており、Wifiを通してスマホから複数の電化製品のON/OFFや出力調整(TVの音量やエアコンの温度など)できるらしい。温度センサーも内蔵しているみたい。いわゆるこれ1台を部屋に置いておけば、日常的な電化製品を操作管理できる感じ。

 
Amazonだと、やはり2倍くらいの値段。レビューがあるので、使い勝手の参考にはなります。

しかし、見た目がカッコ悪いのと値段もやや高い(安いのかもしれないけど)。
もう少し探してみると(というかどこまで安くなるのかという感じで探してみると)、

AliExpress.com Product - 2016 Broadlink RM Mini3 Black Bean Smart Home Universal Intelligent WiFi/IR/4G Wireless Remote Controller By Smart Phone先ほどの一つランク下の製品:1495円(送料込み)これも学習型赤外線リモコン内蔵で同じようなことができるけれども、温度センサーなどはないみたい。しかし、急にここまで安くなって値段的にはお手頃かもしれない。

Amazonでは、4000円以上もする。高い。7000円くらいで売っているところもあり、もしかして人気なのかも。画像を見るとかなり小さい。
複数の部屋に置くなら(赤外線リモコンがそれぞれの電化製品に届くように)、先ほどの大きいタイプ1個買うより、こっちを2個買った方がいいのかもしれない。
しかし、Amazonでは高すぎる。


AliExpress.com Product - Xiaomi Mi Universal Smart Remote Controller Home Appliances WIFI+IR+RF Switch 360 Degree Smart for Air Conditioner TV DVD Player2127円(送料込み)
これはXiaomi製で、値段もそんなに高くないということから機能的にはBroadlink Mini3と似たような感じ。これ自体には温度センサーはついていないみたい(次に挙げる機種と連携で可能らしい)。
Xiaomiといえば、おしゃれで今っぽいスマホや電化製品を次々と出している中国のメーカーなので、なんとなくイメージとしてはいいのかも。
ちなみにこれはAmazonでは売っていない。

AliExpress.com Product - Xiaomi MI Smart Home Suite Multifunction Security Gateway Human Body Temperature and Humidity Sensor Smart home wireless switch 7217円(送料込み)大きな丸いのが本体で間接照明のように周囲が光るみたい(この本体だけ別売りで3333円程度)。そして温度センサー、人体感知センサー、ワイヤレス電源スイッチが含まれており、連携して使うことができるらしい。先ほどの黒い製品もこれと連携できるような。普通に買うなら、このXiaomiの製品が便利そう。個別にセンサーも購入可能なので。
ただ、ここまで高機能でなくてもいいという感じで探して見ると、 AliExpress.com Product - 2016 New Sonoff Wifi Switch Remote Control Smart Home Automation/ Intelligent WiFi Center for APP Smart Home Controls 10A/2200W734円(送料込み)。これは単純なWifi電源スイッチ。つまり電化製品とコンセントの間に挟んでスマホなどからON/OFFするというもの。赤外線リモコンはついていないので、エアコンなどをリモコン操作することはできない。照明器具のON/OFFに使うならこれでいいかと。値段はその分お手頃。これなら試しに買ってみてもいいかもしれない。本体左側にコンセント、右側に電化製品という感じで、ソケットとプラグをつないでおけば、他の電化製品に付け替えもできる。10Aまで大丈夫らしい。

Amazonでも売っているけど、やはり2倍以上の値段。



こんなのも売っている。これはすぐに使えて便利かもしれない。しかしプラグのピンは3本でアースつき。普通の家庭用コンセントに差すなら3Pプラグを2Pプラグに変換するアダプターも必要。


Raspberry Piで作ればいいのでは?:ここまで見てくると、ArduinoやRaspberry Piで自作できないかと考えてしまう。当然Wifiモジュールがあれば作ることはできるのだけれども、部品を集めると意外にお金がかかる。Raspberry Pi3なら、WifiもBluetoothも内蔵されているので、あとは赤外線LEDやリレーなどつければすぐに同じようなものが出来上がる。ネットにもたくさん参考例が出ている。しかし、Raspberry Pi3だけでも4000〜5000円くらいするので、それなら製品を買った方が早いかもしれない。もちろん、作ることを趣味として楽しむというのであれば、また話は別となるけれども。
AliExpress.com Product - 2016 New Original Raspberry Pi 3 Model B Board 1GB LPDDR2 BCM2837 Quad-Core Ras PI3 B,Ras PI 3B,Ras PI 3 B with WiFi&Bluetooth4243円(送料込み)

Arduino+ESP-WROOM-02で作る?:Arduinoなら、中国製クローンが数百円なのでそれほどコストはかからないはず。Arduinoの場合ならWifiモジュールも必要。そうなると、ESP-WROOM-02あたりが安くて便利そう。ESP-WROOM-02を使ったサンプルもネットではたくさん見かける。
AliExpress.com Product - ESP8266 serial WIFI module ESP-WROOM-02 AI Home 32Mbit FCC CE TELEC + Adapter plate420円(送料込み)Wifiモジュールがこんなに安く手に入るようになったのはすごいけど、これでもArduinoクローンとその他部品を合わせて予算的には1000円程度で作れるという感じでしょうか?

ESP-WROOM-02開発ボード(Arduinoボード不要):しかしよく調べて見たら、WifiチップであるESP8266にArduinoのプログラムが書き込めるらしく、ESP-WROOM-02開発ボード(MiniUSB端子ならびにUSBシリアル変換チップ付き)を買えば、これだけですぐにできるみたい。しかも安い。確かにこんなに安いのならあらゆる電化製品につけてもいいのかもしれない(AirMacExpress1台なら50台までつなげられるはず)。 AliExpress.com Product - V3 Wireless module NodeMcu 4M bytes Lua WIFI Internet of Things development board based ESP8266 for arduino Compatible343円(送料込み)、Arduinoコンパーチブル。つまり、以下のAmazon(スイッチサイエンス)で売っているものと同じらしい。値段を比較するとかなり安い。

日本だと、このくらいの値段になってしまうようです。ちゃんと技適マークもついています。
さらに探すと、同じもの(クローン?)がAliExpressでも売っています(以下:技適マークはついているけどこれが現物とは限らない)。

先ほどの343円のものよりは少し高いですが、それでもオリジナルよりはるかに安い。
これも、この基板上のESP8266にArduinoのプログラムを書き込んで、あとはスイッチやセンサーなどとつなげればいいだけ。それにしてもESP8266にプログラムを書き込めるのは知らなかった。単なるWifiモジュールだけなのかと思っていました。

ESP-WROOM-02開発ボードで何ができるか?:
個人的には、Bluetooth、Xbee、Arduino Wifiシールドなどは使ったことがあるのですが、ESP8266(ESP-WROOM-02)に関してはまだ使ったことがありません。
とりあえずできそうなこと(実際に使う前提で)、
・赤外線LEDでWifi学習型赤外線リモコン
・カメラモジュールをつけてWifi監視カメラ
・リレーやフォトカプラでWifi電源スイッチ
・温度や明るさセンサをつける
・何かと連動してメッセージなど送信
という感じでしょうか。

具体的には、
・エアコンのリモコン代行
・室温測定
・照明のON/OFF
・ペットの監視
くらいでしょうか。
これらが、1000円程度で可能であるなら、試してみてもいいかもしれません。

COSカメラOV7670:
特に、監視カメラについては、
AliExpress.com Product - New OV7670 VGA Camera Module Lens CMOS 640X480 SCCB w I2C Interface Auto Exposure Control Display Active 386円(送料込み)、このOV7670というCMOSカメラ(640x480px)が安く手に入ります。国内でパーツとしてのカメラを買うと3000円前後と結構高い。中古Webカメラを分解すればいいのだけれど(確か5〜6個持っている)、信号など色々解析するのが面倒。
このOV7670カメラを使ったサンプルを探して見ると、ArduCamというのがあって、ESP-8266用にGitにソースが出ています(これをArduino IDEのボードマネージャーからインストールすると、プログラムを書き込む際にESP-8266がボード選択可能になるみたい)。ということから、ESP-WROOM-02開発ボード+OV7670でWifiカメラがすぐに作ることができそうです。どちらも小型なのでかなりコンパクトなものが出来上がりそう。このカメラの問題は、FIFOという取り込んだ画像データを一旦貯めておくメモリがついているものとついていないものがあるようで、AuduCamのGitには、OV7670FIFOのソースもあることから多分なんとかなりそう。あとは、カメラ機能だけでなく、その他のセンサー、赤外線リモコン、フォトカプラなどもつけることができるかどうか?端子が足りるかどうか、メモリがいっぱいにならないかどうか?というあたりでしょうか。しかし、スペックを調べて見ると、
この↑最新型WEMOS D1mini Pro778円:送料込み)は16MBもフラッシュメモリがあるみたい。ProではないWEMOS D1miniでも4MB(314円:送料込み)、Arduino Unoは32KBしかない。ということは、Wifi、小型、パワフルそして安価ならこれを使った方がいい。
Proは少々高いけれども、4MBの方であればかなり安いので、一つにまとめて多機能にするよりも、モジュール化して、それぞれ別々に動いていてもいいかも。
それぞれ複数のIPアドレスに分かれてしまうけど、ブラウザ上ではiframeを使って一つのページに複数のURL画面をまとめて表示することは可能なので大丈夫なはず。ただ、個数が増えると電源の数も増えてしまうのが面倒かもしれない。
レーザー加工中の監視カメラ:なぜ監視カメラが気になるかというと、レーザー加工の際にカメラ越しに位置決めや加工中の様子を見ることができると便利(というか安全)と思ったからです。普段利用しているbCNCでもOpencvを使ったProbe Camera Alignmentという機能があって、OpencvをPythonにインストールするところまではしてあるのですが、まだ手付かず。今なら、スマホのカメラをPCへミラーリングすることは、AirPlayAirMoreなどでも可能なので、そんな感じで使ってもいいのですが、スマホとは別に小型のWifiカメラがあると色々便利そうです。ということから、CNCマシンには関係が薄いことですが(基板制作などできるかもしれませんが)、ESP-WROOM-02開発ボードあたりを中心にちょっとやってみようかなと思っているところです。
その他ESP8266利用のサイト:Arduinoとスマホが連携してからはあまり電子工作はしていなかったのだけれども、調べて見るとスマホ系Arduinoも進化していて、Blynkというサイトはかなり充実していました。


スマホ用アプリで操作画面を作ることが簡単にできるようです。さらには、ESP8266(あるいはArduino Unoなど)に書き込むソフトもスマホと連携してブラウザベースで行えるようです(以下)。

これがあれば、すぐにESP8266を使ったIoTデバイスを開発できそうです。スマホ+Wifiという環境、しかも以前よりも安価で簡単にどこででも作業できるというのがすごい。
もう少し調べて見ると、WEMOSというところから出ている以下のようなシリーズ。 AliExpress.com Product - free shipping d1 mini reley module + d1 w2812 rgb module +d1 ds18b20 module + d1 dht11 + di mini wifi module1495円(送料込み)ESP8266(Mini NodeMcu/Wemos)モジュール他、気圧(気温)、RGB-LED、MicroSDカード、温度、湿度(温度)、リレースイッチのモジュールセット。温度系が多いけど、遊ぶにはちょうどよさそう。モジュール単体でも200円前後で売っているので、必要なものだけ買ったほうがいいかも。簡単なIoTデバイスなら、500円くらいですぐに作れるという感じですね。
AliExpress.com Product - OLED Display SSD1306 64x48 0.66" Shield Wemos D1 Mini ESP8266 NodeMcuちなみこれは64x48pxのOLEDディスプレイ、568円(送料込み)ディスプレイだけあって他のモジュールよりは高いけど、それでもこんな値段。
画面は小さいけどそれなりに文字も見えますね。つい買ってしまいそう(遊び用)。

さらに、以下のような超小型リニアアクチュエータ(ステッピングモーター)を組み合わせれば、カメラの首振り機能など作れそうです。 AliExpress.com Product - Micro slide up motor vane screw 2 phase 4 wire stepper motor121円(送料込み)
いろいろできそうですが、IoTの使い方を検索すると、冷蔵庫で賞味期限切れの食品を教えてくれるとか、Amazon Dashのように足りなくなった洗剤を注文してくれるとかあるけど、家ではそこまでは必要ない感じ。オフィスなどで定期的に消耗品を購入しなければいけない時は便利かもしれません。

リマインダー機能で言えば、SiriやAmazon Echoのような音声認識でのやり取りの方が面白そう。カップラーメンの3分間であっても、従来は時計やキッチンタイマーを使っていたけど、最近はSiriを通してリマインダー登録してます。牛乳を買った時も賞味期限を登録しておけば教えてくれるので、短期長期関わらずスケジュール上に登録しておけばいいという感じです。

人気の投稿