ESP-WROOM-02でLinuxサーバーを操作する

オンボードLEDのLチカ CUI編


ESPからLinuxサーバー(RaspberryPiやOrangePi)を操作できたら面白いと以前から考えていました。
Linuxサーバーをリモートで操作するにはrsh/rexec/sshなどを使うのが一般的ですが、ESP側にこれらを実装するのは敷居が高い です。
いろいろ調べたら、python+pyinotifyでLinux側のファイルやディレクトリを監視できることが分かりました。
python+pyinotifyについては、こちらこちらで 紹介されています。
そこで、python+pyinotifyを使って、ESPからLinuxサーバーを操作する方法を紹介します。
手順は以下の様になります。
RaspberryPi/OrangePi ESP8266
FTPサーバを立てる
python+pyinotifyで特定のディレクトリを監視する

FTPクライアントを使ってファイルを書き込む
ファイル名に応じたコマンドを実行する

FTPサーバーを立てる

Linuxサーバ側にFTPサーバーを立てます。私はvsftpdを使いました。
インストール方法はこ ちらこちらに 紹介されています。

python+pyinotifyで特定のディレクトリを監視する

pyinotifyのインストールは以下の通りです。
$ sudo apt-get install python-pip
$ sudo pip install pyinotify

次に、監視対象のフォルダーを作ります。
私は「/home/pi/inotify」としました。

pythonのプログラム(ftp_exec.py)は以下の通りです。
こちらを 参考に作りました。
「/home/pi/inotify」ディレクトリを監視し、ファイル更新があれば、ファイル名に応じたコマンドを実行します。
「start_led」に変化があったとき:「start_led.sh」を実行
「stop_led」に変化があったとき:「stop_led.sh」を実行
「hoge」に変化があったとき:「hoge.sh」を実行
「hogehoge」に変化があったとき:「hogehoge.sh」を実行
import pyinotify
import os.path

wm = pyinotify.WatchManager()  # Watch Manager

#mask = pyinotify.IN_MODIFY  # watched events
mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY
#mask = pyinotify.IN_CREATE | pyinotify.IN_DELETE


class EventHandler(pyinotify.ProcessEvent):

    def process_IN_CREATE(self, event):
        print event
        print "Create:", event.pathname


    def process_IN_DELETE(self, event):
        print event
        print "Delete:", event.pathname

    def process_IN_MODIFY(self, event):
        print event
        print "Modify:", event.pathname
        basename = os.path.basename(event.pathname)
        print "basename:", basename
        command = "/home/pi/inotify/" + basename + ".sh"
        print "command:", command
        if os.path.exists(command):
            os.system(command)


handler = EventHandler()
notifier = pyinotify.Notifier(wm, handler)
wdd = wm.add_watch('/home/pi/inotify', mask, rec=True)
notifier.loop()

サンプルとして以下の2つのスクリプトを作ります。

「/home/pi/inotify/start_led.sh」 オンボードLEDをフラッシュするスクリプト
#!/bin/bash
sudo sh -c "echo none >/sys/class/leds/led0/trigger"
sudo sh -c "echo heartbeat >/sys/class/leds/led0/trigger"

「/home/pi/inotify/stop_led.sh」 オンボードLEDのフラッシュを停止するスクリプト
#!/bin/bash
sudo sh -c "echo none >/sys/class/leds/led0/trigger"
sudo sh -c "echo 0 >/sys/class/leds/led0/brightness"

FTPクライアントを使ってファイルを書き込む

ESP側のFTPクライアントはこ ちらを参考に作りました。(というかほぼそのままです)
エラー処理の一部に「ん??」というコードが有りますが、面倒なのでオリジナルのままです。
FTPサーバーのユーザ名、パスワード、IPアドレス(192, 168, 10, 145)は、自分の環境に合わせて変更してください。
// File: WiWi_FTP_Client.ino for ESP8266 NodeMCU
/*
   Original Arduino: FTP passive client
   http://playground.arduino.cc/Code/FTP
   Modified 6 June 2015 by SurferTim

   You can pass flash-memory based strings to Serial.print() by wrapping them with F().

   2015-12-09 Rudolf Reuter, adapted to ESP8266 NodeMCU, with the help of Markus.
*/
#include <ESP8266WiFi.h>
#include <FS.h>

// Set these to your desired softAP credentials. They are not configurable at runtime.
const char* ssid = "アクセスポイントのSSID";
const char* password = "
アクセスポイントのパスワード";
const char* hostuser = "FTPサーバーのユーザ名";
const char* hostpasswd = "FTPサーバーのパスワード";

//boolean debug = false;  // true = more messages
boolean debug = true;

// LED is needed for failure signalling
//const short int BUILTIN_LED2 = 16;  //GPIO16 on NodeMCU (ESP-12)
const short int BUILTIN_LED2 = 0;

// provide text for the WiFi status
const char *str_status[]= {
  "WL_IDLE_STATUS",
  "WL_NO_SSID_AVAIL",
  "WL_SCAN_COMPLETED",
  "WL_CONNECTED",
  "WL_CONNECT_FAILED",
  "WL_CONNECTION_LOST",
  "WL_DISCONNECTED"
};

// provide text for the WiFi mode
const char *str_mode[]= { "WIFI_OFF", "WIFI_STA", "WIFI_AP", "WIFI_AP_STA" };

// change to your server
IPAddress server( 192, 168, 10, 145 );

WiFiClient client;
WiFiClient dclient;

char outBuf[128];
char outCount;

const char* ESP8266File = "/esp8266.txt";

// SPIFFS file handle
File fh;

void signalError() {  // loop endless with LED blinking in case of error
  while(1) {
    if (BUILTIN_LED2 > 0) {
      digitalWrite(BUILTIN_LED2, LOW);
      delay(300); // ms
      digitalWrite(BUILTIN_LED2, HIGH);
      delay(300); // ms
    } // end if
  } // end while
}

//----------------------- WiFi handling
void connectWifi() {
  Serial.print("Connecting as wifi client to SSID: ");
  Serial.println(ssid);

  // use in case of mode problem
  WiFi.disconnect();
  // switch to Station mode
  if (WiFi.getMode() != WIFI_STA) {
    WiFi.mode(WIFI_STA);
  }

  WiFi.begin ( ssid, password );

  if (debug) WiFi.printDiag(Serial);

  // ... Give ESP 10 seconds to connect to station.
  unsigned long startTime = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startTime < 10000) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  // Check connection
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.print("WiFi connect failed to ssid: ");
    Serial.println(ssid);
    Serial.print("WiFi password <");
    Serial.print(password);
    Serial.println(">");
    Serial.println("Check for wrong typing!");
  }
}  // connectWiFi()

//----------------- FTP fail
void efail() {
  byte thisByte = 0;

  client.println(F("QUIT"));

  while (!client.available()) delay(1);

  while (client.available()) {
    thisByte = client.read();
    Serial.write(thisByte);
  }

  client.stop();
  Serial.println(F("Command disconnected"));
  fh.close();
  Serial.println(F("SD closed"));
}  // efail

//-------------- FTP receive
byte eRcv() {
  byte respCode;
  byte thisByte;

  while (!client.available()) delay(1);

  respCode = client.peek();

  outCount = 0;

  while (client.available()) {
    thisByte = client.read();
    Serial.write(thisByte);

    if (outCount < 127) {
      outBuf[outCount] = thisByte;
      outCount++;
      outBuf[outCount] = 0;
    }
  }

  if (respCode >= '4') {
    efail();
    return 0;
  }
  return 1;
}  // eRcv()

//--------------- FTP handling
byte doFTP(char* HostFile) {
  char cmd[64];
 
  fh = SPIFFS.open(ESP8266File, "r");
  if (!fh) {
    Serial.println(F("SPIFFS open fail"));
    return 0;
  }

  if (!fh.seek((uint32_t)0, SeekSet)) {
    Serial.println(F("Rewind fail"));
    fh.close();
    return 0;
  }

  if (debug) Serial.println(F("SPIFFS opened"));

  if (client.connect(server, 21)) {  // 21 = FTP server
    Serial.println(F("Command connected"));
  } else {
    fh.close();
    Serial.println(F("Command connection failed"));
    return 0;
  }

  if (!eRcv()) return 0;
  if (debug) Serial.println("Send USER");
  sprintf(cmd, "USER %s",hostuser);
  client.println(cmd);

  if (!eRcv()) return 0;
  if (debug) Serial.println("Send PASSWORD");
  sprintf(cmd, "PASS %s",hostpasswd);
  client.println(cmd);

  if (!eRcv()) return 0;
  if (debug) Serial.println("Send SYST");
  client.println(F("SYST"));

  if (!eRcv()) return 0;
  if (debug) Serial.println("Send Type I");
  client.println(F("Type I"));

  if (!eRcv()) return 0;
  if (debug) Serial.println("Send PASV");
  client.println(F("PASV"));

  if (!eRcv()) return 0;

  char *tStr = strtok(outBuf, "(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL, "(,");
    array_pasv[i] = atoi(tStr);
    if (tStr == NULL) {
      Serial.println(F("Bad PASV Answer"));
    }
  }
  unsigned int hiPort, loPort;
  hiPort = array_pasv[4] << 8;
  loPort = array_pasv[5] & 255;

  if (debug) Serial.print(F("Data port: "));
  hiPort = hiPort | loPort;
  if (debug) Serial.println(hiPort);

  if (dclient.connect(server, hiPort)) {
    Serial.println(F("Data connected"));
  }
  else {
    Serial.println(F("Data connection failed"));
    client.stop();
    fh.close();
    return 0;
  }

  if (debug) Serial.println("Send STOR filename");
  client.print(F("STOR "));
  client.println(HostFile);

  if (!eRcv()) {
    dclient.stop();
    return 0;
  }

  if (debug) Serial.println(F("Writing"));
  // for faster upload increase buffer size to 1460
//#define bufSizeFTP 64
#define bufSizeFTP 1460
  uint8_t clientBuf[bufSizeFTP];
  //unsigned int clientCount = 0;
  size_t clientCount = 0;
 
  while (fh.available()) {
      clientBuf[clientCount] = fh.read();
      clientCount++;
      if (clientCount > (bufSizeFTP - 1)) {
        dclient.write((const uint8_t *) &clientBuf[0], bufSizeFTP);
        clientCount = 0;
        delay(1);
      }
  }
  if (clientCount > 0) dclient.write((const uint8_t *) &clientBuf[0], clientCount);

  dclient.stop();
  Serial.println(F("Data disconnected"));

  if (!eRcv()) return 0;

  client.println(F("QUIT"));
  if (!eRcv()) return 0;

  client.stop();
  Serial.println(F("Command disconnected"));

  fh.close();
  if (debug) Serial.println(F("SPIFS closed"));
  return 1;
}  // doFTP()


void display_Running_Sketch (void){
  String the_path = __FILE__;
  int slash_loc = the_path.lastIndexOf('/');
  String the_cpp_name = the_path.substring(slash_loc+1);
  int dot_loc = the_cpp_name.lastIndexOf('.');
  String the_sketchname = the_cpp_name.substring(0, dot_loc);

  Serial.print("\nArduino is running Sketch: ");
  Serial.println(the_sketchname);
  Serial.print("Compiled on: ");
  Serial.print(__DATE__);
  Serial.print(" at ");
  Serial.print(__TIME__);
  Serial.print("\n");
}

void setup() {
  delay(1000);
  Serial.begin(9600);
  display_Running_Sketch();
  if (BUILTIN_LED2 > 0) {
    // signal start
    pinMode(BUILTIN_LED2, OUTPUT);
    digitalWrite(BUILTIN_LED2, LOW);
    delay(100); // ms
    digitalWrite(BUILTIN_LED2, HIGH);
    delay(300); // ms
  }

  Serial.println ( "Connect to Router requested" );
  connectWifi();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("WiFi mode: ");
    Serial.println(str_mode[WiFi.getMode()]);
    Serial.print ("WiFi Status: " );
    Serial.println (str_status[WiFi.status()]);
    if (BUILTIN_LED2 > 0) {
      // signal WiFi connect
      digitalWrite(BUILTIN_LED2, LOW);
      delay(300); // ms
      digitalWrite(BUILTIN_LED2, HIGH);     
    }
  } else {
    Serial.println("");
    Serial.println("WiFi connect failed, push RESET button.");
    signalError();
  }

  if (!SPIFFS.begin()) {
     Serial.println("SPIFFS failed, needs formatting");
     signalError();
  }

  if (!SPIFFS.exists(ESP8266File)){
    Serial.println(F("SPIFFS File Create"));
    fh = SPIFFS.open(ESP8266File, "w");
    fh.close();
  }
 
}  // setup()


void loop() {
  byte inChar;
  char HostFile[64];
 
  if (Serial.available() > 0) {
    inChar = Serial.read();

    if (inChar == '0') {
      strcpy(HostFile, "inotify/start_led");
      Serial.print("HostFile=");
      Serial.print(HostFile);
      if (doFTP(HostFile)) Serial.println(F("FTP OK"));
      else Serial.println(F("FTP FAIL"));
    } 

    if (inChar == '1') {
      strcpy(HostFile, "inotify/stop_led");
      Serial.print("HostFile=");
      Serial.print(HostFile);
      if (doFTP(HostFile)) Serial.println(F("FTP OK"));
      else Serial.println(F("FTP FAIL"));
    }
  } 

  delay(10);  // give time to the WiFi handling in the background
}

Arduino-IDEのシリアルコンソールで[0]を入力するとRaspberryのオンボードLEDがフラッシュし、
[1]を入力するとLEDのフラッシュが止まります。

同様のライブラリにwatchdogが有ります。
python+watchdogについてはこちらで紹介しています。

HSES-LCD-24を使うと、簡単にタッチパネルが使えるようになります。
そこで、次回はタッチ操作のGUIを追加してみます。

続く....