Arduino M0 Pro でマルチタスクプログラミング  : Wifi通信

# はじめに

この記事では，[TOPPERS/R2CA](http://dev.toppers.jp/trac_user/contrib/wiki/rtos_arduino)によるWifi通信の方法について説明します．タスクは1個のみのシングルタスク版を説明します．

TOPPERS/R2CAの説明とサンプルの動作方法は次の記事を見て下さい．

 - [Arduino M0 Pro でマルチタスクプログラミング : インストールとサンプルの実行](http://qiita.com/exshonda/items/093870ecdcda6c79678c)
 - [Arduino M0 Pro でマルチタスクプログラミング : マルチタスク](http://qiita.com/exshonda/items/7afb12b56696cf44895e)


# 変更履歴
 - 2016/10/23
    - ファイルの変更
 - 2016/5/05
  -  アクセスポイントの設定を別ファイルとした．
 - 2016/3/31
  -  ESP8266のドライバの見直しによりサンプルプログラムを変更．
  -  シングルタスク版について説明

# ハードウエア
[この記事](http://qiita.com/exshonda/items/7afb12b56696cf44895e)で説明しているハードウェア以外にESP8266が必要です．

ESP8266のボードは多くの種類が出ています．基本的にはどれでも良いですが，動作確認したものは次の通りです．

- スイッチサイエンス [ESP-WROOM-02](https://www.switch-science.com/catalog/2341/)
  - ブレッドボードを使って接続しました．
  
- [NCES WiFi ボード](http://homepage3.nifty.com/fpga/files/board/nces_shield/)
  - ESP8266をXbee互換の基盤にしたものです．
  - Arduino ワイヤレスプロトシールドや，[NCES CAN シールド](http://homepage3.nifty.com/fpga/files/board/nces_shield/)を使うと，簡単にArduinoと接続可能です．
  - ロゴから分かるようにTOPPERS/R2CA用に作成しました．

![nces_can.jpg](https://qiita-image-store.s3.amazonaws.com/0/106870/b5ec9c54-ca54-3278-0892-92d55159deb7.jpeg)



#サンプルのビルド

R2CAのパッケージの\examples\WifiEchoにあるサンプルを実行します．このサンプルはポート80に入力された文字列をそのまま返すエコーサーバーです．

まずexamples_gdef.hを開き，アクセスポイントの情報をSSIDとPASSWORDに設定します．

```\examples\examples_gdef.h
#define STA_SSID       ""
#define STA_PASSWORD   ""
```

次にr2ca_app.hを開き，シングルタスク版にするためマクロをコメントアウトします．

```\examples\WifiEcho\r2ca_app.h
//#define MULTI_ECHO_SERVER
```

ビルドは同じフォルダにあるdo_make.bat をダブルクリックすると開始されます．ファイルが色々作成されますが，rca.elfが出来るとビルド成功です．

#実行

実行はdo_run.batをダブルクリックするとダウンロードが開始され，実行されます．Teratermには次のように表示されます．アクセスポイントに接続して，IPアドレスをDHCPから取得します．取得したアドレスはコンソールに出力されます．下記の例では，192.168.1.27となっています．

![WS000000.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/0af215ac-875c-7714-3316-c7616fe09978.jpeg)


PCから上記のIPのポート80に接続して適当な文字を入力してエンターを押すと入力した文字がそのまま送られて来ます．終了するには，Ctrl-Q入力してエンターを押します．

![WS000001.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/82beb8e1-c041-0fe7-afc3-804a545d997d.jpeg)

ボード側のコンソールにもは，受信した旨が表示されます．
![WS000002.JPG](https://qiita-image-store.s3.amazonaws.com/0/106870/2503fcbc-3600-659c-3bfe-8f3fa3c8116e.jpeg)



#プログラム

##初期化処理

初期化処理ではアクセスポイントに接続してIPを取得しています．また，ポート番号80番でTCPサーバーを実行します．#define WMODE_STATION の定義をコメントアウトすると，アクセスポイントとして動作します．

```\examples\WifiEcho\r2ca_app.cpp
void setup()
{
    int ret;
    
    Serial.println("Echo Server : Start!");
    
    ret = WiFi.begin(Serial5, 115200);

    if(ret == 1) {
        Serial.print("Cannot communicate with ESP8266.");
        while(1);        
    } else if(ret == 2) {
        Serial.println("FW Version mismatch.");
        Serial.print("FW Version:");
        Serial.println(WiFi.getVersion().c_str());
        Serial.print("Supported FW Version:");
        Serial.println(ESP8266_SUPPORT_VERSION);
        while(1);
    } else {
        Serial.print("begin ok\r\n");
    }

    Serial.print("FW Version:");
    Serial.println(WiFi.getVersion().c_str());
    
#ifdef  WMODE_STATION
    if (WiFi.setOprToStation()) {
        Serial.print("to station ok\r\n");
    } else {
        Serial.print("to station err\r\n");
    }
    
    if (wifi.joinAP(STA_SSID, STA_PASSWORD)) {
        Serial.print("Join AP success\r\n");
        Serial.print("IP: ");
        Serial.println(WiFi.getLocalIP().c_str());    
    } else {
        Serial.print("Join AP failure\r\n");
    }
#else /* !WMODE_STATION */ 
    if (WiFi.setOprToSoftAP()) {
        Serial.print("to softap ok\r\n");
    } else {
        Serial.print("to softap err\r\n");
    }    
    if(WiFi.setSoftAPParam(AP_SSID, AP_PASSWORD, 7, 0)){
        Serial.print("Set SoftAP success\r\n");
        Serial.print("IP: ");
        Serial.println(WiFi.getLocalIP().c_str());            
    }
    else {
        Serial.print("Set SoftAP failure\r\n");
    }
#endif /* WMODE_STATION */
    
    if (WiFi.enableMUX()) {
        Serial.print("multiple ok\r\n");
    } else {
        Serial.print("multiple err\r\n");
    }

    if (WiFi.startTCPServer(80)) {
        Serial.print("start tcp server ok\r\n");
    } else {
        Serial.print("start tcp server err\r\n");
    }

    if (WiFi.setTCPServerTimeout(60)) { 
        Serial.print("set tcp server timout 60 seconds\r\n");
    } else {
        Serial.print("set tcp server timout err\r\n");
    }

    Serial.print("setup end\r\n");

    setup_done = true;
}
```

##エコーサーバー処理

まず，コネクションが変化したかをチェックします．コネクションは最大5個あります．このプログラムではコネクション0に対してデータを受け取り，エコーバックを行います．コネクション0に接続があれば，mux_idに0を設定します．

次に，コネクションが確立しているかをチェックして，80番のポートからデータを受け取ります．有効なデータを受け取った場合は，コンソールに出力した後，データをエコーバックしています．また，受信したデータをチェックして，Crtl-Qを受け取った場合は，終了処理も行います．

```\examples\WifiEcho\r2ca_app.cpp
uint8_t mux_id_ptn;

uint8_t mux_id = MUX_NULL;
uint8_t task1_mux_id = MUX_NULL;

void loop()
{
    uint8_t buffer[128] = {0};
    uint8_t pre_mux_id_ptn;    
    uint32_t len;
    uint32_t i;

    delay(1);    
    /* Check Connection Status */
    pre_mux_id_ptn = mux_id_ptn;

    if(!Wifi.getMuxCStatus(&mux_id_ptn)) {
        Serial.println("getMuxCStatus(&mux_id_ptn) : Error!");
    }
    else {        
        if (pre_mux_id_ptn != mux_id_ptn) {
            Serial.print("Connection Status changed! : 0x");
            Serial.println(mux_id_ptn, HEX);
            if (mux_id_ptn & 0x01) {
                mux_id = 0; 
            }
            if (mux_id_ptn & 0x02) {
                task1_mux_id = 1;
            }
        }
    }

    if (mux_id == MUX_NULL) {
        return;
    }
    
    if (!WiFi.isConnected(mux_id)) {
        Serial.print("Echo Server : Port is closed: ");
        Serial.println(mux_id);
        mux_id = MUX_NULL;
        return;
    }
    
    if((len = WiFi.recv(mux_id, buffer, sizeof(buffer))) == 0) {
        return;
    }

    /* Recived Data */

    for(i = 0; i < len; i++) {
        /* If Recive Ctrl-q(17) */
        if(buffer[i] == 17) {
            Serial.print("Echo Server : Close port : ");
            Serial.println(mux_id);            
            WiFi.releaseTCP(mux_id);
            mux_id = MUX_NULL;
            return;
        }
    }

    Serial.print("Echo Server : Recive Data from mux : ");
    Serial.println(mux_id);
    Serial.print("Echo Server : Recive len   : ");
    Serial.println(len);
    Serial.print("Echo Server : Recive Data  : ");
    Serial.println((char*)buffer);
    
    if(!WiFi.send(mux_id, buffer, len)) {
        Serial.println("Echo Server : send(mux_id, cmd) : Error!");
    }
}
```

##API

ESP8266のライブラリは[ITEADLIB_Arduino_WeeESP8266](https://github.com/itead/ITEADLIB_Arduino_WeeESP8266)をベースとしていますが，API幾つか増やしています．

```
    int begin(HardwareSerial &uart, uint32_t baud = 115200);
    bool kick(void);
    bool restart(void);
    String getVersion(void);
    bool setOprToStation(void);
    bool setOprToSoftAP(void);
    bool setOprToStationSoftAP(void);
    String getAPList(void);
    bool joinAP(String ssid, String pwd);
    bool leaveAP(void);
    bool setSoftAPParam(String ssid, String pwd, uint8_t chl = 7, uint8_t ecn = 4);
    String getJoinedDeviceIP(void);
    String getIPStatus(void);
    String getLocalIP(void);
    bool enableMUX(void);
    bool disableMUX(void);
    bool createTCP(String addr, uint32_t port);
    bool releaseTCP(void);
    bool registerUDP(String addr, uint32_t port);
    bool unregisterUDP(void);
    bool createTCP(uint8_t mux_id, String addr, uint32_t port);
    bool releaseTCP(uint8_t mux_id);
    bool registerUDP(uint8_t mux_id, String addr, uint32_t port);
    bool unregisterUDP(uint8_t mux_id);
    bool setTCPServerTimeout(uint32_t timeout = 180);
    bool startTCPServer(uint32_t port = 333);
    bool stopTCPServer(void);
    bool startServer(uint32_t port = 333);
    bool stopServer(void);
    bool send(const uint8_t *buffer, uint32_t len);
    bool send(uint8_t mux_id, const uint8_t *buffer, uint32_t len);
    bool send(String &str);
    bool send(uint8_t mux_id, String &str);
    uint32_t recv(uint8_t *buffer, uint32_t buffer_size, uint32_t timeout = 1000);
    uint32_t recv(uint8_t mux_id, uint8_t *buffer, uint32_t buffer_size, uint32_t timeout = 1000);
    uint32_t recv(uint8_t *coming_mux_id, uint8_t *buffer, uint32_t buffer_size, uint32_t timeout = 1000);
    bool isDataAvailable(uint8_t mux_id);
    bool isDataAvailable(void);
    bool isConnected(void);
    bool isConnected(uint8_t mux_id);
    bool getMuxCStatus(uint8_t *mux_id_ptn);
    
```

# おわりに
ESP8266を使うと安価に簡単にネットに接続できます．TOPPERS/R2CAを使うと，マルチタスクが使えるので，サーバーで接続待ちになりながら，センサー等の処理を同時に実行出来ます．