JUGEMテーマ:趣味

 

JUGEMテーマ:Arduino
ESP-WROOM-02 開発ボード-WEB画面からの操作でLEDを点灯(クライアント編)
今回はESP-WROOM-02をクライアントにしてインターネット上の他のサーバから指示を受けて100Vの電源をON/OFFする
インターネット上のサーバはWEB画面から指示を受け付けてESP-WROOM-02に連絡する機能を持つ。
完成図映像

システム構成図
システム構成


 
今回は100Vの電源を扱うことから感電や、パーツを破損する確立が格段に高くなるので高度な注意力を要する。
気を引き締めていこう。
課題は以下の通り。
・100Vのドライブ
・インターネット上のサーバーはどうする?
・クライアントのプログラムはどうする?
一つ一つ解決して行こう。
 
100Vのドライブ
これはリレーを使う方法が1番に思い浮かんだのだが、今回のESP-WROOM-02は3Vである事とリレーをドライブする電流が出せない可能性が高いので却下。
SSRを見て行くといいのが見つかる。入力電圧が3V~8Vで動作可能。
http://akizukidenshi.com/catalog/g/gK-00210/
ページの説明を見ると入力は4.8V~となっているけど店頭で黄色い説明書を見ると3V~って書いてあったんです。
と思い、秋月のページの写真を拡大してみると、ちゃんと3V~って書いてあるじゃん。間違っているのね。などと思いつつ次へ進める。
説明書の回路図。
SSRキットの回路図

 
なんでも、ゼロクロス回路っていうのが入っていて、交流電圧が0Vの時にON/OFFの操作を行うことでスイッチングノイズを大幅減するって事です。
すげーーーー。ま、結構高いしね。
ていうか、
http://akizukidenshi.com/catalog/g/gK-00203/
の方が実験で使うには安くてコンパクトで良かった。(泣
気を取り直して組み立ててみた。
でいつも通りにちゃんと半田付けできているかテスターを当てて行くと、
なんと、回路図と、基盤のパターンがあっていない箇所を発見!!
色々考えてみると回路図のゼロクロスの上のピンが6番、下のピンが4番となっているけど
これが逆ですよー秋月さん。ま、ちゃんと動けば文句は無いです。
ちゃんと半田付けが出来ていないのかと思ってびびっただけです。
SSRの写真
SSR写真

 
INに3.3Vを入れてあげるとちゃんと電気が流れました。めでたしめでたし。
インターネット上のサーバーはどうする?
サーバに関する課題は2つ。
1.WEB方式で行くのか?それとも、TCPとかUDPで直で行くか?
2.サーバはどこにおくのか?自宅サーバーかレンタルサーバーか?
1の課題に関してはTCPとかUDPを直で操作するのは敷居が高いのでパス。
それにESP-WROOM-02のクライアント側のサンプルプログラムが良さげだったのでWEB方式に決定。
2の課題は自宅サーバーをWEBに公開するとセキュリティー的に不味そうなのとDDNSの契約とかもメンドウなんで今現在借りているhttp://www.sakura.ad.jp/に決定します。
今現在使っているCGIを改造すれば行けそうだし既にノウハウがあるほうが話は早いし。
というわけで、WEB方式で、レンタルサーバー方式で行きたいと思います。
商用向けにはWEB方式は効率的に悪いので、TCPまたはUDP直で行ったほうがお勧めですが、これは実験ですので。効率は度外視で行きます。
久々にperlでプログラムを作ったぞっと。
control.cgi : システム構成図のWebServerの右側のCGIでパソコン等から指示を出して結果を表示するプログラムです。
wroom.cgi : システム構成図のWebServerの左側のCGIでESP-WROOM-02からの指示読み出し要求と結果の送信の処理を行います。
control.cgiの画面
control.cgi画面
 

The current state : 表示領域です。現在のESP-WROOM-02の状況を表示します。
The present order is  : 表示領域です。現在の指示内容を表示します。
Please set a state of the LED. : 操作領域です。ON または OFFの指示を出します。
Reload : 最新の画面を表示します。

control.cgiのプログラム

#! /usr/bin/perl

############################
require 'jcode.pl';
############################

$webmaster = 'xxxx@xxxxxx.xxx';

&parse_form_data(*simple_form);
$led  = $simple_form{'led'};

 

LockON();
if($led ne ""){
 if($led eq "on"){
  $OutRec = "HIGH";
 }
 else{
  $OutRec = "LOW";
 }
 if(!open(present_file, "> present.dat")){
  EmergencyStop(3000);
 }
 print present_file "$OutRec¥n";
 close(present_file);
}

if(!open(status_file, "status.dat")){
 EmergencyStop(1000);
}
if($InRec=<status_file>){
 $nLedStatus = SpaceCut($InRec);
}
close(status_file);

if(!open(present_file, "present.dat")){
 EmergencyStop(1000);
}
if($InRec=<present_file>){
 $nLedPresentStatus = SpaceCut($InRec);
}
close(present_file);

# $nLedStatus = "HIGH";
# $nLedPresentStatus = "HIGH";

LockOFF();

 

# HTML出力
print "Content-type: text/html¥n¥n";
print "<html>¥n";
print "<head>¥n";
print " <META http-equiv=Control content=no-cache>¥n";
print " <META http-equiv=Pragma content=no-cache>¥n";
if($led ne ""){
 print " <meta http-equiv='Refresh' content='0;URL=control.cgi'>¥n";
}
if($nLedStatus ne $nLedPresentStatus){
 print " <meta http-equiv='Refresh' content='1;URL=control.cgi'>¥n";
}
print " <META http-equiv=Content-type content='text/html; charset=Shift_JIS'>";
print "<title>LED-SWITCH</title>¥n";
print "</head>¥n";
print "<body>¥n";

print "The current state ";
print "<BLOCKQUOTE> ";
print "<FONT SIZE=+10>";
if($nLedStatus eq "HIGH"){
  print "ON.";
}
else{
  print "OFF.";
}
print "</FONT>";
print "</BLOCKQUOTE> ";

print "The present order is ";
print "<BLOCKQUOTE> ";
print "<FONT SIZE=+10>";
if($nLedPresentStatus eq "HIGH"){
  print "ON.";
}
else{
  print "OFF.";
}
print "</FONT>";
print "</BLOCKQUOTE> ";

print "Please set a state of the LED.";
print "<BLOCKQUOTE>";
print "<a href=¥"/arduino/control.cgi?led=on¥">LED ON</a><br>";
print "<a href=¥"/arduino/control.cgi?led=off¥">LED OFF</a><br>";
print "</BLOCKQUOTE>";

print "Reload.";
print "<BLOCKQUOTE>";
print "<a href=¥"/arduino/control.cgi¥">RELOAD</a><br>";
print "</BLOCKQUOTE>";


# HTML出力
print "</body>¥n";
print "</html>¥n";

exit;

sub LockON
{
 if(!open(LOCK, "> LOCK")){
  EmergencyStop(1000);
 }
 flock(LOCK, 2);      
}

sub LockOFF
{
 close(LOCK);
}

sub EmergencyStop
{
 local($ErrorNo) = @_;

 print "System Error $ErrorNo¥n";
 print "";

 print_footer();

 exit(1);
}

sub SpaceCut
{
 local($String) = @_;

 $String =~ s/ //g; #// 全角空白を削除
 $String =~ s/ //g; #// 半角空白を削除
 $String =~ s/¥n//g; #// enterを削除

 return ($String);
}


# SPECIAL LIB
#
sub parse_form_data
{
 local(*FORM_DATA) = @_;
 
 local( $request_method, $query_string, @key_value_pairs, $key_value, $key, $value);
 
 $request_method = $ENV{'REQUEST_METHOD'};
 
 if($request_method eq "GET"){
  $query_string = $ENV{'QUERY_STRING'};
 } elsif($request_method eq "POST"){
  read (STDIN, $query_string, $ENV{'CONTENT_LENGTH'});
 } else {
  &return_error(500, "Server Error", "Server uses unsupported method");
 }
 
 @key_value_pairs = split(/&/, $query_string);
 
 foreach $key_value (@key_value_pairs){
  ($key, $value) = split(/=/, $key_value);
  $value =~ tr/+/ /;
  $value =~ s/%([¥dA-Fa-f][¥dA-Fa-f])/pack("C",hex($1))/eg;
  
  if(defined($FORM_DATA{$key})){
   $FORM_DATA{$key} = join("¥0", $FORM_DATA{$key}, $value);
  } else {
    $FORM_DATA{$key} = $value;
  }
 }
}

sub return_error
{
 local($status, $keyword, $message) = @_;
 
 print "Content-type: text/html¥n¥n";
 print "Status: ", $status, " ", $keyword, "¥n¥n";
 
 print <<End_of_Error;
 
<title>CGI Program - Unexpected Error</title>
<h1>$keyword</h1>
<hr>
$message
<hr>
Prease contact $webmaster for more information.

End_of_Error

 exit(1);
}

 

解説
LockONとLockOFFで排他制御します。
ledのパラメータが入ってきた場合は指示が有った場合です。
present.datファイルにHIGHまたはLOWを記録します。
status.datを読んで画面のThe current state部分に表示します。
present.datを読んで画面のThe present order is部分に表示します。
content-typeにtext/htmlを指定してHTMLでの出力であることを示します。
METAの記述以下を実施します。
 キャッシュをOFF
 指示ありの場合はすぐにReloadさせます。
 The current stateとThe present order isのステータスが異なる場合は1秒単位でReloadさせます。

wroom.cgiのプログラム

#! /usr/bin/perl

############################
require 'jcode.pl';
############################

$webmaster = 'xxxx@xxxx.xxx;

&parse_form_data(*simple_form);
$led  = $simple_form{'led'};

# HTML出力
print "Content-type: text/plain¥n¥n";

LockON();
if($led ne ""){
 if($led eq "on"){
  $OutRec = "HIGH";
 }
 else{
  $OutRec = "LOW";
 }
 if(!open(status_file, "> status.dat")){
  EmergencyStop(3000);
 }
 print status_file "$OutRec¥n";
 close(status_file);
}

if(!open(present_file, "present.dat")){
 EmergencyStop(1000);
}
if($InRec=<present_file>){
 $nLedPresentStatus = SpaceCut($InRec);
}
close(present_file);

LockOFF();

if($nLedPresentStatus eq "HIGH"){
  print "ON";
}
else{
  print "OFF";
}

exit;

sub LockON
{
 if(!open(LOCK, "> LOCK")){
  EmergencyStop(1000);
 }
 flock(LOCK, 2);      
}

sub LockOFF
{
 close(LOCK);
}

sub EmergencyStop
{
 local($ErrorNo) = @_;

 print "System Error $ErrorNo¥n";
 print "";

 exit(1);
}

sub SpaceCut
{
 local($String) = @_;

 $String =~ s/ //g; #// 全角空白を削除
 $String =~ s/ //g; #// 半角空白を削除
 $String =~ s/¥n//g; #// enterを削除

 return ($String);
}


# SPECIAL LIB
#
sub parse_form_data
{
 local(*FORM_DATA) = @_;
 
 local( $request_method, $query_string, @key_value_pairs, $key_value, $key, $value);
 
 $request_method = $ENV{'REQUEST_METHOD'};
 
 if($request_method eq "GET"){
  $query_string = $ENV{'QUERY_STRING'};
 } elsif($request_method eq "POST"){
  read (STDIN, $query_string, $ENV{'CONTENT_LENGTH'});
 } else {
  &return_error(500, "Server Error", "Server uses unsupported method");
 }
 
 @key_value_pairs = split(/&/, $query_string);
 
 foreach $key_value (@key_value_pairs){
  ($key, $value) = split(/=/, $key_value);
  $value =~ tr/+/ /;
  $value =~ s/%([¥dA-Fa-f][¥dA-Fa-f])/pack("C",hex($1))/eg;
  
  if(defined($FORM_DATA{$key})){
   $FORM_DATA{$key} = join("¥0", $FORM_DATA{$key}, $value);
  } else {
    $FORM_DATA{$key} = $value;
  }
 }
}

sub return_error
{
 local($status, $keyword, $message) = @_;
 
 print "Content-type: text/html¥n¥n";
 print "Status: ", $status, " ", $keyword, "¥n¥n";
 
 print <<End_of_Error;
 
<title>CGI Program - Unexpected Error</title>
<h1>$keyword</h1>
<hr>
$message
<hr>
Prease contact $webmaster for more information.

End_of_Error

 exit(1);
}

 

content-typeにtext/plainを指定してtextでの出力であることを示します。
LockONとLockOFFで排他制御します。
ledのパラメータが入ってきた場合は指示が有った場合です。
status.datファイルにHIGHまたはLOWを記録します。
present.datを読んでONまたはOFFを表示します。
 

クライアントのプログラムはどうする?

さっきもちょっと触れたけどWEBクライアントでの良い感じのサンプルがありました。
例として選んだのがESP8266HttpClientの中からBasicHttpClient。
これを変更して今回の仕様に合わせて改造します。

クライアントのプログラム


#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ESP8266HTTPClient.h>

ESP8266WiFiMulti WiFiMulti;

int nLedStatus = LOW;
const int PIN_LED = 13;

void setup() {
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);

    Serial.begin(115200);

    Serial.println("WebClient");

    for(uint8_t t = 3; t > 0; t--) {
        Serial.printf("[SETUP] WAIT %d...¥n", t);
        Serial.flush();
        delay(1000);
    }

    WiFiMulti.addAP("xxxxxxxx", "xxxxxxxx");

}

void loop() {
    // wait for WiFi connection
    if((WiFiMulti.run() == WL_CONNECTED)) {
      Serial.println("Connected using IP:");
      Serial.println(WiFi.localIP());
 
      Serial.println("And MAC address:");
      Serial.println(WiFi.macAddress());

      String sUrlParam="";
      while(1){
        HTTPClient http;

        Serial.print("[HTTP] begin...¥n");
        String sUrl="http://xxxxxxxxx.sakura.ne.jp/arduino/wroom.cgi";
        sUrl += sUrlParam;
        http.begin(sUrl); //HTTP

        Serial.print("[HTTP] GET...¥n");
        // start connection and send HTTP header
        int httpCode = http.GET();

        sUrlParam="";
        // httpCode will be negative on error
        if(httpCode > 0) {
            // HTTP header has been send and Server response header has been handled
            Serial.printf("[HTTP] GET... code: %d¥n", httpCode);

            // file found at server
            if(httpCode == HTTP_CODE_OK) {
                String payload = http.getString();
                Serial.println(payload);
                if(payload=="ON"){
                  if(nLedStatus==LOW){
                    digitalWrite(PIN_LED, HIGH);
                    nLedStatus = HIGH;
                    sUrlParam="?led=on";
                    continue;
                  }
                }
                if(payload=="OFF"){
                  if(nLedStatus==HIGH){
                    digitalWrite(PIN_LED, LOW);
                    nLedStatus = LOW;
                    sUrlParam="?led=off";
                  }
                }
            }
        } else {
            Serial.printf("[HTTP] GET... failed, error: %s¥n", http.errorToString(httpCode).c_str());
        }

        http.end();
        if(sUrlParam!=""){
          continue;
        }
        break;
      }
    }

    delay(1 * 1000);
}

一定時間間隔で自分のレンタルサーバーのwroom.cgiにGETさせます。
wroom.cgiから受け取った内容(ONまたはOFF)が現在の自分が持っているステータスと異なる場合は以下を行います。
 ・指示内容に合わせてdigitalWriteで出力電圧を変更します。
 ・新しいステータスをwroom.cgiに伝えるためにパラメーター「?led=on」または「?led=off」を付けてwroom.cgiをGETします。
 ・新しいステータスをグローバル変数に保持します。

回路図
ESP-WROOM-02 開発ボード-WEB画面からの操作でLEDを点灯(クライアント編)回路図

 
写真
写真