WeMosを使ってみる

mDNSによる名前解決


WeMos(ESP8266)をサーバーとして使う場合、WeMos(ESP8266)側のIPアドレスを知る必要があります。
こちらでWeMosのIPアドレスを固定IPとする方法を紹介しましたが、 mDNS(Multicast DNS)を使えば、
ローカルホス ト名を使ってIPアドレスを知ることができます。

【WeMos側】

WeMos側のスケッチは以下の通りです。
こちらのスケッチからボールド文字の部分だけを変えています。
赤字の部分は自分の環境に合わせて変更してください。
/*
 *  Simple TCP Server
 */

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

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

#define SERVER_PORT 9876

// Create an instance of the server
// specify the port to listen on as an argument
WiFiServer server(SERVER_PORT);

void setup() {
  Serial.begin(9600);

#if 0
// config static IP
  Serial.println();
  Serial.println();
  IPAddress ip(192, 168, 111, 90);
  IPAddress gateway(192, 168, 111, 1);
  IPAddress subnet(255, 255, 255, 0);
  Serial.print(F("Setting static ip to : "));
  Serial.println(ip);
  WiFi.config(ip, gateway, subnet);
#endif
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Print the IP address
  Serial.println(WiFi.localIP());
 
  // Start the server
  server.begin();
  Serial.println("Server started");

  // Start the mDNS responder for esp8266.local
  if (!MDNS.begin("esp8266")) {
    Serial.println("Error setting up MDNS responder!");
  }
  Serial.println("mDNS responder started");
}

void loop() {

  MDNS.update();
 
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
 
  // Wait until the client sends some data
  Serial.println("new client");
  while(!client.available()){
    delay(1);
  }
 
  // Read the first line of the request
  String rmsg = client.readStringUntil('\0');
  Serial.print(rmsg);
  client.flush();

  // Prepare the response
  char smsg[128];
  memset(smsg,0,sizeof(smsg));
  for(uint32_t i = 0; i < rmsg.length(); i++) {
    if(isalpha(rmsg[i])) {
      smsg[i] = toupper(rmsg[i]);
    } else {
      smsg[i] = rmsg[i];
    } // end if
  } // end for

  Serial.print("->");
  Serial.println(smsg);

  // Send the response to the client
  client.print(smsg);
  delay(1);
  Serial.println("Client disonnected");

  // The client will actually be disconnected
  // when the function returns and 'client' object is detroyed
}

このスケッチを実行するとWeMos側のIPアドレスはDHCPから払い出されたIPアドレスとなります。
DHCPから払い出されるアドレスなので毎回変わる可能性が有ります。

【Raspberry側】

こちらの 記事に従ってavahi-daemon と libnss-mdns をインストールし、/etc/nsswitch.conf を修正します。
あとはpingが「esp8266.local」に通れば終わりです。


こちらのプログラムでは、IPアドレスの代わりに「esp8266.local」を使っ て、WeMosと通信することができます。
プログラムを起動すると以下の表示となりますので、サーバ側(WeMos)のローカルホスト名を指定します。


【Windows10側】

こちらの 記事によると、Windows10 1803(April 2018 Update/Redstone 4/RS4)では
初期状態でmDNSが正常に動くようになったと書いてありますが、私の環境では
pingが通りませんでした。

そこで、次回はWindowsのNetBIOS名を使ったIPアドレスの調査方法を紹介します。



mDNSの機能を使うと、同じサービスエンドポイントをもつノードがネットワーク内にいるかどうかをスケッチの中で判定することができます。
こちらがサーバー側のスケッチで、DHCPを使ってIPを取得し、addService()を使って[_esp8266_wifi_tcp]の サービスエンドポイントを登録しています。
/*
 *  Simple TCP Server
 */

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

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

#define SERVER_PORT 9876

// Create an instance of the server
// specify the port to listen on as an argument
WiFiServer server(SERVER_PORT);

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println(WiFi.localIP());
 
  // Start the server
  server.begin();
  Serial.println("Server started");

  char myDomainName[16] = {0};
  sprintf(myDomainName, "ESP_%06X", ESP.getChipId());
  Serial.print("myDomainName: ");
  Serial.println(myDomainName);

  // Set up mDNS responder:
  // - first argument is the domain name, in this example
  //   the fully-qualified domain name is "esp8266.local"
  // - second argument is the IP address to advertise
  //   we send our IP address on the WiFi network
  if (!MDNS.begin(myDomainName)) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  // Add service to MDNS-SD
  Serial.println("mDNS responder started");
  MDNS.addService("esp8266_wifi", "tcp", SERVER_PORT);
}

void loop() {
  MDNS.update();

  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
 
  // Wait until the client sends some data
  Serial.println("new client");
  while(!client.available()){
    delay(1);
  }
 
  // Read the first line of the request
  String rmsg = client.readStringUntil('\0');
  Serial.print(rmsg);
  client.flush();

  // Prepare the response
  char smsg[128];
  memset(smsg,0,sizeof(smsg));
  for(uint32_t i = 0; i < rmsg.length(); i++) {
    if(isalpha(rmsg[i])) {
      smsg[i] = toupper(rmsg[i]);
    } else {
      smsg[i] = rmsg[i];
    } // end if
  } // end for

  Serial.print("->");
  Serial.println(smsg);

  // Send the response to the client
  client.print(smsg);
  delay(1);
  Serial.println("Client disonnected");

  // The client will actually be disconnected
  // when the function returns and 'client' object is detroyed
}


こちらがクライアント側のスケッチでqueryService()を使って、[_esp8266_wifi_tcp]のサービスエンドポイントを 検索しています。
一度では見つからないことが有るので、10回繰り返しています。
/*
 *  Simple TCP Client
 */

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

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

#define INTERVAL       5000
#define SOCKET_PORT    9876             // You have to change
#define TIME_OUT       10000

IPAddress remoteIp;

WiFiClient client;

unsigned long nextMillis;

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println(WiFi.localIP());

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

  char myDomainName[16] = {0};
  sprintf(myDomainName, "ESP_%06X", ESP.getChipId());
  Serial.print("myDomainName: ");
  Serial.println(myDomainName);
  // Set up mDNS responder:
  // - first argument is the domain name, in this example
  //   the fully-qualified domain name is "esp8266.local"
  // - second argument is the IP address to advertise
  //   we send our IP address on the WiFi network
  if (!MDNS.begin(myDomainName)) {
    Serial.println("Error setting up MDNS responder!");
  }

  bool valid = false;
  for(int i=0;i<10;i++) {
    // Send out query for esp tcp services
    Serial.println("Sending mDNS query");
    int n = MDNS.queryService("esp8266_wifi", "tcp");
    Serial.println("mDNS query done");
    if (n == 0) {
      Serial.println("no services found");
      delay(1000);
      continue;
    } else {
      Serial.print(n);
      Serial.println(" service(s) found");
      for (int i = 0; i < n; ++i) {
        // Print details for each service found
        Serial.print(i + 1);
        Serial.print(": ");
        Serial.print(MDNS.hostname(i));
        Serial.print(" (");
        Serial.print(MDNS.IP(i));
        Serial.print(":");
        Serial.print(MDNS.port(i));
        Serial.println(")");
 
        if (MDNS.port(i) == SOCKET_PORT) {
          remoteIp = MDNS.IP(i);
          valid = true;
          break;
        }
      }
    }
    if (valid) break;
  } // endo for
 
  if (valid) {
    Serial.print("remoteIp=");
    Serial.println(remoteIp);
  } else {
    while(1) delay(100);
  }
}


void loop() {
  static int num = 0;
  char smsg[30];
  char rmsg[30];
  int rflag;
  unsigned long timeout;
  unsigned long now;
 
  now = millis();
  if ( long(now - nextMillis) > 0) {
    nextMillis = millis() + INTERVAL;
    Serial.print("Client connect....");
    if (!client.connect(remoteIp, SOCKET_PORT)) {
      Serial.println("failed");
    } else {
      Serial.println("ok");
      sprintf(smsg,"data from ESP8266 %05d",num);
      num++;
      client.write(smsg, strlen(smsg));

      // wait for responce
      rflag = 1;
      timeout = millis();
      while(client.available() == 0) {
        now = millis();
//        Serial.println("now="+String(now));
//        Serial.println("timeout="+String(timeout));
        if (long(now - timeout) > TIME_OUT) {
          rflag = 0;
        }
      } // end while

      Serial.print("Server response....");
      if (rflag == 0) {
        Serial.println("failed");
      } else {
        Serial.println("ok");
        int size;
        while((size = client.available()) > 0) {
          Serial.print("TCP Client Receive Size=");
          Serial.println(size);
          size = client.read((uint8_t *)rmsg,size);
          Serial.write((uint8_t *)smsg,size);
          Serial.write("->");
          Serial.write((uint8_t *)rmsg,size);
          Serial.println("");
        } // end while
      }

      //disconnect client
      Serial.println("Client disconnect");
      client.stop();
    } // end if
  } // end if
}

以下の例は、2回目の検索で[_esp8266_wifi_tcp]のサービスエンドポイントを見つけて、
ドメイン名(ESP_2BBEBE.local)、IPアドレス(192.168.10.121)、ポート番号(9876)を取り出していま す。
このように、サービスエンドポイントの名前さえ分かれば、相手のドメイン名やIPアドレスを知ることができます。


続く....