もともとはCNCマシンのワイヤレス化(
bCNCのPendant機能、
Bluetooth装備など)から始まったのですが、bCNCのカメラ機能を
IPカメラでも撮影可能にしようとしたところ、
・
Pythonの再学習(bCNC自体がPythonで書かれているため)
・Wifiデバイス(
ESP8266や
ESP32)の利用
ということになり、今まで使っていた
Arduino IDEだけではなく、
・
ESP-IDF
・
PlatformIO(
Atom 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の使い方も学習中です。