Arduino
WiFi
bluetooth
RN4020
Wemos

WeMosとRN4020でIoT的なことをする

More than 1 year has passed since last update.

この記事は、RN4020使用Bluetooth Low EnergyモジュールとWeMos(Arduino互換)を使っていろいろ試した結果です。RN4020の設定とWeMosで結構ハマったのでまとめてみました。

材料

今回使ったのは以下のような感じのものです。

  • RN4020使用Bluetooth Low Energyモジュール(秋月電子で購入 こちら):2個
  • WeMos D1 ESP8266モジュールArduino互換ボード、WiFi内蔵(Amazonで購入でこちら):1個

RN4020は秋月電子で¥1,300です。RN4020は動作時の消費電流が16mAらしいです。これなら小さい太陽電池でいけそう。。。ということで衝動買して試したらいけました。
このRN4020、自分でスクリプトを書き込むと、動作をある程度好きに変えられるようです。RN4020を使って、太陽電池の発電量を監視しつつ、スイッチ2つの状態を読み出すBluetoothモジュールを作ってみます。
WeMos D1はESP8266モジュールを使っています。Arduinoと互換があるので、Arduino IDEで開発ができるのでとても便利です。ただし、WiFiが日本の技適を通っていないので、使用にはご注意下さい。心配な方は、WeMosのESP8266を外してESP-WROOM-02(こちら)を取り付ければOKです。スイッチサイエンスからは似たようなものでESPr Oneというものも発売されています(こちら)。まあ、最初からESP-WROOM-02を使う手もあります(単に今回はWeMosを使ってみたかっただけとも言う)。
Arduino IDEによる開発方法はスイッチサイエンスのページが詳しいです。
image.jpg
RN4020写真(チップの落書きは、親機と子機の区別のためにマジックで記入したもの)

全体構成

今回のシステムの構成は、以下のような感じです。
RN4020でSW2つの状態と電圧を計測します。ペアリングしたもう1つのRN4020はWeMosに接続します。WeMosは周期的にRN4020を経由してSW2つの状態と電圧を読み取り、その結果をインターネット上のDBに格納します。

システム構成.png

RN4020の設定

BLE(Bluetooth Low Energy)とは?

RN4020はBLE用のモジュールです。BLEは、ざっくりいうとBluetoothのバージョン4.0の規格で省電力、省コストを意図した規格です。IoTのセンサーとかを電池駆動でばらまいたりするのにちょうどよい規格になってます。
ネットワークの構成としては不特定多数に対してデータを送信する「ブロードキャスト」と特定のデバイス間で使用する「コネクション」の2種類があります。
今回は特定デバイス間で通信させたいので「コネクション」で試します。
コネクション方式の場合、コネクションの開始側、通信のトリガーとなる側をセントラルといいます。セントラルからの要求に従って応答を返す側をペリフェラルといいます。
ペリフェラルから送信される情報は、キャラクタリスティックといいます。それらを目的別にまとめたものをサービスといいます。キャラクタリスティックとサービスにはそれぞれUUIDがあり、セントラルがUUIDで情報の指定をすると、ペリフェラルがその設定値を返却する仕組みです。
RN4020は、スクリプトを設定することで、ユーザー独自のプライベート・キャラクタリスティックを設定できます。
しかも、アナログとデジタルのポートを持っており、各ポートの読み出し値をそのままキャラクタリスティックで返却可能です。
今回のシステム構成の場合SWや太陽電池を接続したRN4020はペリフェラル、WeMosに接続したRN4020はセントラルとして設定します。
本記事では便宜上ペリフェラルに設定するRN4020は子機、セントラルに設定するRN4020は親機と記します。

設定

RN4020の情報を確認し、各種設定をします。
パソコンとRN4020を以下のように接続します。
中華のFTDI232互換モジュール(アマゾンで245円だった)でパソコンにつなげます。
PCとの接続.png

お家のパソコンはMacなのでコンソールで「sudo cu -l /dev/tty.usbserial-xxx -s 115200」といった感じで接続します。
Windowsな方はTera Termあたりで接続しましょう。

子機設定

ペリフェラルとして設定します。
2つのキャラクタリスティックにアナログポート1つとデジタルポート2つを割り当てることで、親機からセンサーの値を読み取れるようになります。
アナログポートは入力1.65Vとなっており、今回使用する太陽電池は2Vなので抵抗で1V以下に分圧して読み取らせることにします。
子機として使うRN4020をパソコンに繋げ、シリアル通信で操作します。
以下のように設定します。「//」の部分は説明文(コメント)なので実際には入力しません。

+                   //エコーのオンオフを切り替えます
Echo On
D                  //とりあえずデバイスの情報を表示してみる
BTA=XXXXXXXXXXXX   //ここで表示される子機のMACアドレス控えておきます
Name=RNXXXX
Connected=no
Bonded=no
Server Service=80000000
Features=20000000
TxPower=4
V                  //バージョン表示
MCHP BTLE v1.23.5 8/7/2015
SF,1               //工場出荷にリセット
SS,00000001        //ユーザ定義のプライベートプロファイルを使用する設定
SR,20000000        //ペリフェラルに設定
PZ                 //ユーザ定義の初期化
PS,123456789012345678901234567890FF // プライベートサービスのUUIDを設定(自分で適当に設定)
PC,12345678901234567890123456789011,02,02  //アナログポート用のキャラクタリスティック、読み出し用、2バイトを指定
PC,12345678901234567890123456789022,02,01  //デジタルポート用のキャラクタリスティック、読み出し用、1バイトを指定
R,1     //再起動
Reboot
CMD
+         //再起動したので再度エコーのオン
LS        //サービス確認
123456789012345678901234567890FF
  12345678901234567890123456789011,000B,02
  12345678901234567890123456789022,000D,02
WC        // スクリプト初期化
WW        //スクリプト入力モード、次の行からユーザ設定のスクリプト入力
@PW_ON
A               //アドバタイズ開始
%000B = @I,0    //AIO0 をハンドル0x000Bに関連付け
%000D = |I,06   //PIO2と3とをハンドル0x000Dに関連付け(注1)
@DISCON
A
//ここでESCキーを入力するとスクリプト入力のモードを抜ける
SR,01000000   //起動後にスクリプト実行をするように設定
R,1           //再起動

注1:PIOを複数読みとりたいときはOR値で指定します。
複数のPIOを複数のハンドルに割り当てることは出来ません。
PIO1とPIO2をハンドル0x000Dに関連付けするなら
%000D = |I,03
PIO1とPIO2とPIO3をハンドル0x000Dに関連付けするなら
%000D = |I,07
となります。
|Iがデジタルポートを表し @Iがアナログポートを表しています。
@PW_ONは電源投入時に実行されるスクリプトです。
@DISCONはコネクション切断時に実行されるスクリプトです。
日本語の取扱説明書ユーザマニュアルが秋月電子のHPにあります。

親機設定

セントラルとして設定します。
子機とペアリングしてセキュアな接続をするように設定します。
親機の設定をする際にはペアリングする子機が動作していないといけないので、子機は適当に5Vの電源をつなげておきます。

SR,80000000   // セントラルに設定
R,1           // 再起動

F             //周りのデバイスをスキャン
XXXXXXXXXXXX,0,RNXXXX,XXXXXXXXXXXXXXXXXXXXX,-2B  //こんな感じで子機のMACアドレスが見つかるはず
X             //スキャン停止
E,0,XXXXXXXXXXXX   // 子機のMACアドレスを指定して接続⇒ 接続するとLEDが消える
B             // セキュアな接続にする
LC            //クライアントサービスの確認、子機に設定したUUIDが一覧で表示される
123456789012345678901234567890FF
  12345678901234567890123456789011,000B,02
  12345678901234567890123456789022,000D,02
CHR,000B      //AIO0読み取り
CHR,000D      //PIO2/3読み取り OR値で返ります
K   //接続切断
E    //再接続 ⇒以降は電源切ってもこのコマンド(E)だけで接続できる。

上記の設定をすれば、以降は電源を切っても再接続できます。子機が立ち上がっている状態で親機でEコマンドを実行すれば、ペアリングした親機と子機が自動的に接続します。
これで、親機と子機の設定はOKです。

WeMosとRN4020(親機)の接続

図のように接続します。
後述しますが、RN4020をWeMosのRx(D0)/Tx(D1)に接続するとWeMosが起動しなくなります。そのため、D10/D11に接続します。
親機とWeMos.png

WeMosのプログラム

WeMosの開発はArduino IDEでできます。ただし、公式の設定では使えなかず、以下のように設定しました。
・ボードはWeMos D1 R2 & mini を選択
・CPU Frequencyは80MHz
・Flash sizeは 4M(3M SPIFFS)
・Upload Speedは921600 bpsでは何故か失敗する。
 230400bpsに落とすといけた。
・プログラムをダウンロードするときにRx(D0)/Tx(D1)をつなげてると失敗する。

プログラムのポイント

WeMosのシリアルに接続したRN4020にシリアル経由でコマンドを与えることで、子機の情報を読み取ります。
WeMosクローンだけかもしれませんが、なぜかD1(Tx)に何か接続している再起動しても立ち上がらなくなります。setup()のなかでSerial.begin()した後にSerial.swap()でD10(Tx),D11(Rx)を使うことで回避します(基盤の裏面を見るとTx0,Rx0と書かれてる)。

また、何回か試験をしてるとWiFi.begin()してもWiFi.status() が6(WL_DISCONNECTED)になり、接続できなくなりました。再起動しても電源を抜いても治らない。いろいろ試した結果、setup()内でWiFi.mode()の直後にWiFi.disconnect()してからWiFi.begin()するとそのうちつながるようになりました。
ということで、WeMosのスケッチは以下のような感じです。

Test
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>

const char* ssid = "MY-SSID";
const char* password = "MY-PASSWORD";

void setup() {
  delay(5 * 1000);
  Serial1.begin(115200); // PC用デバッグシリアルを準備
  WiFi.printDiag(Serial1);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();    // おまじない、これやらないと何故か接続できなくなる
  connectWiFi();        // WiFiに接続
  Serial.begin(115200); // ハードウェアシリアルを準備
  Serial.swap();        // これによりD10(Tx),D11(Rx)が有効になります。
  Serial1.println("Start");
}

void loop(){
  bool sw1, sw2;
  int volt;
  //とりあえず接続確認
  if(!isRN4020Connected()){
    connectRN4020();
    delay(100);
    return;
  }
  //センサー類読み取り
  sw1 = true;
  sw2 = true;
  //PIO2/PIO3の読み取り
  char sw_val = readRN4020("000D",1);
  if(sw_val & 0x02){
    sw1 = false; 
  }
  if(sw_val & 0x04){
    sw2 = false; 
  }
  //AIO0の読み取り
  volt = readRN4020("000B",2);
  // この辺でWiFiClientSecure使ってWebにつなげてデータをインターネットに
  // UPする処理を入れる

  //10秒毎
  delay(10 * 1000);
}

void connectWiFi(){
  int wifi_st;

  wifi_st = WiFi.begin(ssid, password);
  int i;
  while (wifi_st != WL_CONNECTED) {
    delay(500);
    if(WiFi.status() == WL_CONNECTED){
      break;
    }
    wifi_st = WiFi.begin(ssid, password);
  }
  Serial1.println("WiFi connected");
  Serial1.println("IP address: ");
  Serial1.println(WiFi.localIP());
}



void connectRN4020(){
  Serial.println("E");
  delay(100);
  while(Serial.available()){
    Serial.read();  
  }
}



bool isRN4020Connected(){
  bool ret = false;
  String line;

  Serial.println("D");
  // とりあえず解析に必要な情報を受信するまで待つ
  for(int i = 0; i < 50; i++)
  {
    delay(10);
    if(Serial.available() <= 54){
      continue;
    }else{
      break;
    }
  }
  if(Serial.available() <= 54){
    goto EXIT_FUNC;
  }
  //解析開始
  line = readlnSerial();
  if(!line.startsWith("BTA=")){ goto EXIT_FUNC; }
  line = readlnSerial();
  if(!line.startsWith("Name=")){ goto EXIT_FUNC; }
  line = readlnSerial();
  if(line.startsWith("Connected=no")){ goto EXIT_FUNC; }
  if(!line.startsWith("Connected=")){ goto EXIT_FUNC; }
  ret = true;
  // 解析完了残りを読み捨て
EXIT_FUNC:
  while(Serial.available()){
    Serial.read();  
  }
  return ret;
}



int readRN4020(String id, int num){
  int val = -1;
  char c;
  int retry_cnt = 0;

  //ゴミを捨てる
RETRY:
  while(Serial.available()){
    Serial.read();  
  }
  // コマンド発行
  Serial.println("CHR," + id);
  delay(500);
  if(Serial.available() <= 0){
    return 0;
  }
  c = Serial.read();
  if(c != 'R') {
    if(retry_cnt >= 5){
      goto EXIT_FUNC;
    }
    retry_cnt++;
    goto RETRY;
  }

  c = Serial.read();
  if(c != ',') { goto EXIT_FUNC;}
  val = 0;
  for(int i = 0; i < (2 * num); i++) {
    c = Serial.read();
    int tmp = 0;
    if((c >= 'A') && (c <= 'F')){
      tmp = c - 'A' + 10;  
    }else if((c >= '0') && (c <= '9')){
      tmp = c - '0';  
    }
    val = ( val << 4) + tmp;
  }
EXIT_FUNC:
  while(Serial.available()){
    Serial.read();  
  }
  return val;
}



String readlnSerial(){
  String line = String("");
  char c;

  while(Serial.available()){
    c = Serial.read();
    if( c == 0x0A ){
      break;
    }
    line = line + String(c);
  }

  return line;
}

ハマリポイント

  • RN4020のペアリングをした後に再起動すると、うまく接続できなくて四苦八苦しました。結果として、子機を立ち上げといて親機の方でEコマンドを発行すれば自動的に接続できることがわかりました。
  • RN4020のデジタルポートの使い方が最初よくわからなかった。デジタルポートは結局8bitの値として読み出すだけなので、bit毎にキャラクタリスティックに割り付けられないことに気が付かなかった。
  • WeMosのD1(Tx)に何か接続しているとWeMosが立ち上がらない。D1使わないようにすることでとりあえず回避した。