こんにちは、ENGかぴです。
ESP32-WROOM-32EはWiFiを使用するとTCP通信を行うことができます。アクセスポイントを使って、外部機器に接続要求を行うTCPクライアントを実装し双方向通信を行う方法をまとめました。
ESP32-WROOM-32EをTCPクライアントとして使用します。外部機器は下記記事のArduino UNO(以下Arduinoとする)を使用しています。ArduinoはTCPクライアントに応答するサーバーとして使用します。
ESP32-WROOM-32E開発ボード(秋月電子)を使用しArduino IDEで開発を行います。アクセスポイントはWZR-HP-G300NH(バッファロー:生産中止)を使用しています。
ESP32-WROOM-32E(以下ではESP32とする)を使って動作確認したことをまとめています。
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
TCPクライアントを実装する
ESP32とArduinoはアクセスポイントを介して接続します。ESP32とアクセスポイントはWiFiで接続し、アクセスポイントとArduinoはLANケーブルで接続します。
ESP32はArduinoに対して接続要求を出すTCPクライアントとして動作します。Arduinoが接続要求を受け付けると双方向通信が可能となるため、ESP32からArduinoに文字列を送信してArduinoからの応答を確認します。
ESP32はIPアドレスを切り替えることで最大2台をArduinoに接続できるようにしています。本記事ではクライアント1のIPアドレスを使用します。
PR:スキマ時間を有効に!スマホで学べる人気のオンライン資格講座【スタディング】まずは気になる講座を無料で体験しよう!
ライブラリの準備
#include <WiFi.h>
WiFiClient client;//クライアント情報を管理する
WiFiライブラリをインクルードします。WiFiClientクラスの変数としてclientをインスタンス化(clientオブジェクト)します。clientオブジェクトは接続要求やデータの受信などTCP通信の管理に使用します。
アクセスポイントに接続する
const char *ssid = "xxxxxxxxxxxx"; //使用するアクセスポイントのSSID
const char *pass = "aaaabbbbcccc"; //使用アクセスポイントのpassword
const IPAddress ip(192,168,11,10); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
void setup() {
WiFi.begin(ssid,pass);
WiFi.config(ip,ip,subnet); //サーバーのIPを固定する場合
while( WiFi.status() != WL_CONNECTED ){
delay(1000);
Serial.print(".");
if( ++waitcnt >= 60 ){
esp_restart(); //モジュールのリスタート
}
Serial.println(WiFi.localIP()); //接続後のIPアドレスを表示
}
}
WiFi.begin()でSSIDとパスワードを指定してWiFi通信を開始します。SSIDとパスワードは定数で宣言しているものを指定しています。
アクセスポイントに接続要求を出し接続が確立するまで1秒おきに”.”をモニター表示して待ちます。60秒経過しても接続されない場合はesp_restart()によってESP32-WROOM-32Eをリスタートするようにしています。
アクセスポイントに接続するとローカルIP(自局のIP)を表示しています。IPアドレスを固定していない場合はアクセスポイントが割り振ったIPアドレスになります。
サーバーに接続要求する
void mainApp(void){
if( client.connected() == false){
if( timconnect == TIME_UP){
timconnect = TIME_CONNCT_MAX;
if(!client.connect(servip,servport)){
if( ++waitcnt >= 60 ){
waitcnt =0;
}
Serial.print(waitcnt);
Serial.print(".");
}
else{
Serial.println("Connect Ok");
}
}
}
}
clientオブジェクトのconnected()関数でクライアントが接続していることを確認します。接続していない場合は接続処理を行います。
サーバーへの接続要求はconnect()関数を使用して行います。第1引数に接続先のIPアドレスを指定します。第2引数に接続先のポートを指定します。
connect()関数は接続が確立するとtrueが戻るため、「Connect OK」を表示して接続したことを通知します。サーバーからの応答を待っている間、接続要求を頻繁に処理しないようにtimconnectによるタイマで1秒毎に処理するように遅延を設けています。
データの送信と受信
client.println("\n"); //crlfを送信
if (client.available()) {//受信データが有るか
char c = client.read();//データの読み込み
Serial.write(c);
}
clientオブジェクトのprintln()関数で文字列を送信することができます。print()関数やwrite()関数を使用してもデータを送信することができます。例では\n(LF)を改行コード付きのprintln()関数で送信しています。
クライアントが接続している(サーバーと接続している)場合に受信したデータが存在するかをavailable()関数で確認します。受信データが存在する場合は1以上の値が戻るためRead()関数でデータを読み込みます。例では読み込んだデータをシリアルモニターに表示しています。
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
WiFiの再接続の検討
void mainWifi() {
auto status = WiFi.status();
if( status != WL_CONNECTED ){
if( timreconnect == TIME_OFF ){
timreconnect = TIME_RECONNCT_MAX;
Serial.println("Re-Connecting-start");
}
}
else{
mainApp();
}
if( timreconnect == TIME_UP ){
timreconnect = TIME_OFF;
}
}
mainWifi()はアクセスポイントとの接続状態を確認します。アクセスポイントとの接続が切れると自動で再接続処理を行うためWiFi.status()で接続状態を確認してWL_CONNECTED(接続中)でなければ再接続状態であることをシリアルモニターに表示して通知します。
再接続には少し時間がかかるためtimreconnectでタイマ管理を行い10秒経過毎に接続状態を繰り返し表示します。
アクセスポイントに接続している場合はmainApp()でArduinoに接続要求を出してTCP通信ができる状態にします。
PR:次の一手があなたの未来を決める! アビリティクラウドーフリーランスを目指すあなたに向けたエンジニアのマッチングサービス。大手のエンドクライアントメインの企業が多く、業務内容も規模が大きいのが特徴
動作確認
ESP32のクライアント1の電源を入れるとアクセスポイントへの接続を開始します。ESP32のIPアドレスはピン21で切り替えることができます。
Arduinoは電源を入れるとDHT20の測定をしながらクライアントからの接続要求を待機します。
ESP32とアクセスポイントの接続が完了すると、ESP32はArduinoに対して接続要求を行います。Arduinoが接続要求を受け付けると双方向通信ができるようになります。
ESP32の5ピンをGNDに接続するとArduinoにCRLFを送信します。ArduinoはDHT20から取得した温湿度データをHTMLの文字列で応答します。ESP32の17ピンをGNDにするとArduinoとの接続を切断します。
Arduinoは文字列を受けて後、一定の期間データを受信しない場合は接続中のクライアントを廃棄してTCP接続を切断を行うようにしています。TCP接続が切断した場合はDHT20の測定を再開してクライアントからの接続を待ちます。
ESP32がArduinoから受信したデータをシリアルモニターで確認します。
ESP32の5ピンをGNDに接続すると、シリアルモニターにArduinoから文字列の応答が表示できていることが確認できました。文字列にDHT20から取得した温湿度データが表示できています。
シリアルモニターの結果の最下の「Connect OK」はArduinoがデータを受信しないことによってTCPクライアントを破棄したことによるESP32の再接続による履歴の表示です。
ESP32の17ピンをGNDに接続するとESP32からArduinoに対してクライアントの切断の要求を出します。切断するとESP32は次の接続要求を行います。接続要求が受け付けられると「Connect OK」が表示されます。
ソースコード全体
以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。
#include <WiFi.h>
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_RECONNCT_MAX 1000
#define TIME_CONNCT_MAX 100
#define TIME_FILTER_MAX 1
#define PIN_DI1 21
#define PIN_DI2 5
#define PIN_DI3 17
#define DI_FILT_MAX 4
enum DI_NO{
DI1 = 0,
DI2,
DI_MAX
};
struct DIFILT_TYP{
uint8_t wp;
uint8_t buf[DI_MAX][DI_FILT_MAX];
uint8_t di[DI_MAX];
};
const char *ssid = "001D73912B41"; //SSID
const char *pass = "y013xxrctu6f7"; //password
const IPAddress ip(192,168,11,10); //IPアドレス
const IPAddress ip2(192,168,11,11); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
const IPAddress servip(192,168,11,2); //IPアドレス
const uint16_t servport = 80;
/* 変数宣言 */
WiFiClient client;
uint32_t beforetimCnt = millis();
int8_t timdifilt;
int16_t timreconnect = TIME_OFF;
int16_t timconnect;
int16_t waitcnt;
DIFILT_TYP difilt;
bool conflg = false;
bool diconflg = false;
/* プロトタイプ宣言 */
void mainApp(void);
void mainWifi(void);
void mainTimer(void);
void DiFilter(void);
void setup() {
Serial.begin(115200);
pinMode(PIN_DI1,INPUT_PULLUP);
pinMode(PIN_DI2,INPUT_PULLUP);
pinMode(PIN_DI3,INPUT_PULLUP);
for( uint8_t i=0; i < 10; i++ ){
mainTimer();
DiFilter();
delay(10);
}
WiFi.begin(ssid,pass);
if( digitalRead(PIN_DI1)){
WiFi.config(ip,ip,subnet);
}
else{
WiFi.config(ip2,ip2,subnet);
}
while( WiFi.status() != WL_CONNECTED ){
delay(1000);
Serial.print(".");
if( ++waitcnt >= 60 ){
esp_restart();
}
Serial.print(waitcnt);
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
waitcnt = 0;
}
void loop() {
mainWifi();
mainTimer();
DiFilter();
}
/* WiFi管理*/
void mainWifi(void){
auto status = WiFi.status();
if( status != WL_CONNECTED ){
if( timreconnect == TIME_OFF ){
timreconnect = TIME_RECONNCT_MAX;
Serial.println("Re-Connecting-start");
}
}
else{
mainApp();
}
if( timreconnect == TIME_UP ){
timreconnect = TIME_OFF;
}
}
/* メイン処理関数 */
void mainApp(void){
if( client.connected() == false){
if( timconnect == TIME_UP){
timconnect = TIME_CONNCT_MAX;
if(!client.connect(servip,servport)){
if( ++waitcnt >= 60 ){
waitcnt =0;
}
Serial.print(waitcnt);
Serial.print(".");
}
else{
Serial.println("Connect Ok");
}
}
}
else{
timconnect = TIME_CONNCT_MAX;
waitcnt = 0;
if( difilt.di[DI1] == LOW ){
if( conflg == false){
conflg = true;
client.println("\n");//crlfを送信
}
}
else{
conflg = false;
}
if (client.available()) {//受信データが有るか
char c = client.read();//データの読み込み
Serial.write(c);
}
}
if( difilt.di[DI2] == LOW ){
if( diconflg == false){
diconflg = true;
client.stop();
}
}
else{
diconflg = false;
}
}
/* Timer Management function add */
void mainTimer(void){
if ( millis() - beforetimCnt >= BASE_CNT ){
beforetimCnt = millis();
//10msごとにここに遷移する
if( timdifilt > TIME_UP ){
timdifilt--;
}
if( timreconnect > TIME_UP ){
--timreconnect;
}
if( timconnect > TIME_UP ){
--timconnect;
}
}
}
/* DiFilter function add */
void DiFilter(void){
if( timdifilt == TIME_UP ){
timdifilt = TIME_FILTER_MAX;
difilt.buf[0][difilt.wp] = digitalRead(PIN_DI2);
difilt.buf[1][difilt.wp] = digitalRead(PIN_DI3);
for(uint8_t i=0; i<DI_MAX; i++){
if( difilt.buf[i][0] == difilt.buf[i][1] &&
difilt.buf[i][1] == difilt.buf[i][2] &&
difilt.buf[i][2] == difilt.buf[i][3] ){ //4回一致を確認
difilt.di[i] = difilt.buf[i][0];
}
}
if( ++difilt.wp >= DI_FILT_MAX ){
difilt.wp = 0;
}
}
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。