(Arduino&Xcode)iPhoneとESP-WROOM-32(ESP32)を使用したWiFiデータ通信

久しぶりの投稿です。
今回は「Xcode(ver15.4)」と「Arduino IDE(2.3.2)」開発環境を利用して「iPhone」と「ESP32」でWiFi経由によるデータのやり取りについて書いていきます。

# プログラム動作内容
ESP32にてローカルサーバーを立て、iPhoneから任意のデータをESP32に送信し、ESP32モジュールに接続されたLCDへ受信データを表示するプログラムを作成する。

# 開発環境の準備
○Arduino IDE *2024/8/30時点ではver2.3.2
Arduinoについては以前紹介した記事を確認下さい。(https://qiita.com/pontaman007/items/296f6dc37442722a8244)
ただし、久々に実施しようとするとVCPドライバが何故か記事通りにできなくなったので、もしかするとドライバがなくてもできるようになったのかもしれません。(推測)
当方は「Tools」バーにて以下の設定(赤字下線)のようにすると今まで通り書き込みができるようになったので写真を載せておきます。
 ![スクリーンショット 2024-08-30 14.02.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/d2536ee1-36a7-2bf7-681d-07322cffa7aa.png)

○Xcode *2024/8/30時点ではVer15.4
Xcodeはいくつか方法はあると思いますが、Appstore経由でインストールするのが簡単かと思います。
本記事でプロジェクト作成する場合は、以下の設定で作成下さい。
 ![スクリーンショット 2024-08-30 14.18.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/4cfe5f8a-537c-f2b6-7209-e4a8c3ba75f2.png)
![スクリーンショット 2024-08-30 14.16.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/6ee51f19-5c1a-e31c-4693-4a149df57298.png)

# Arduino(ESP32側)プログラム
過去記事を参考に今回のプログラムを作成しておりますので、詳細知りたい方は過去記事参照下さい。
　・回路図：https://qiita.com/pontaman007/items/f47ead88beacf50e9912
　・LCDとWiFi使用：https://qiita.com/pontaman007/items/0540409fada8c3762ca7
```C:01_WiFi_TEST1(Arduino)
/*  作成日；2024/8/30
    ファイル名；01_WiFi_TEST1
    プログラム内容；スマホから受け取った文字列データをLCDに表示
    使用部品；ESP32、LCD、WiFiルータ
    ESP32へのピン接続；(21:LCD_SDA)、(22:LCD_SCL)
 */

#include <Wire.h>  //I2C通信ライブラリ
#include <WiFi.h> //WiFiモジュールライブラリ

#define LCD_ADRS 0x3E //I2Cアドレス
String overflow = "Over flow of characters";  //文字数が16byte以上の場合に表示させる文字

WiFiServer server(80);  //PCからの応答を読み取るためのサーバ定義(ポート80使用)
const char* ssid     = "自宅ルータのSSID";  //SSID
const char* password = "自宅ルータのPASS";  //PASS
IPAddress ip; //IPアドレス格納変数
String ip_string = "";  //IPアドレスをString型に変換した値を格納する
String get_data = "None"; //スマホからのデータ受信を表示する変数

bool getdata_flag = true; //受信データフラグ


/*  初期設定  */
void setup() {

  Wire.begin(); //I2C通信開始(ESP32をマスター)
  init_LCD(); //LCD初期化
  lcdwriteData("Booting now..."); //LCDへ起動中の表示
  
  /*  ここに起動時に確認する項目を記載  */
  delay(5000);
  /*  ここに起動時に確認する項目を記載  */
  
  wifiConnect();  //WiFi接続処理
  getdataDisplay(); //初期データをLCDに表示
  
}
/*  初期設定  */


/*  メイン本文  */
void loop() {

  WiFiClient client = server.available(); //クライアントからの要求を待つ
  
  if (client) { //クライアントから情報が来れば

    String currentLine = "";  //データ受信用文字列をクリア
    getdata_flag = true;

    while (client.connected()) {  //クライアントと接続中に繰り返す処理

      if (client.available()) { //クライアントからデータ受信すれば

        char c = client.read(); //1文字(1バイト)読み込み

        if (c == '\n') {  //改行情報が来れば

          if (currentLine.length() == 0) {  //クライアントからのデータ読み込み完了すれば(「\n」+「\n」の情報が来れば)
            
            //HTMLヘッダ送信
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();

            client.println(); //クライアントに対してデータ送信完了の合図

            break;

          } else { 

            currentLine = ""; //受信した文字列をリセット

          }

        } else if (c != '\r') {  //「\r」以外の文字が来れば

          currentLine += c; //文字列の最後尾に追加

        }

        if ( currentLine.indexOf("GET") != -1 && currentLine.indexOf(" ",5) != -1 && getdata_flag == true ) { //クライアントから「GET+入力データ」を受信すれば

          get_data = currentLine.substring(5,currentLine.indexOf(" ",5)); //ローカルIPアドレス以降の受信データを保管
          getdata_flag = false; //受信データフラグをリセット(これをしないと「受信データ+HTTP/1.1」の文字列データが保存されてしまうため)

        }

      }

    }
    
    getdataDisplay();  //スマホから取得したデータをLCDに表示
    delay(1000); 
  
  }
  
}
/*  メイン本文  */


/*  スマホからの受信データ表示処理  */
void getdataDisplay() {

  lcdwriteCommand(0x01);  //LCD液晶文字クリア([Clear Display])
  lcdwriteData("Get Data");
  lcdwriteCommand(0x40+0x80); //2行目へカーソル移動
  lcdwriteData(get_data);  //スマホから受け取ったデータ表示
                
}
/*  スマホからの受信データ表示処理  */


/*  WiFi接続処理  */
void wifiConnect() {
  
  delay(500); //チャタリング防止
  lcdwriteCommand(0x01);  //LCD液晶文字クリア([Clear Display])
    
  WiFi.begin(ssid, password); //Wi-Fiへの接続処理
    
  while (WiFi.status() != WL_CONNECTED) { //WiFiに接続されるまで待機
       delay(500);
  }
    
  ip = WiFi.localIP();  //ESP32のIPアドレスを取得
  ip_string = "IP:" + String(ip[0]) + "." + String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]); //ESP32のIPアドレスをString型へ変換
    
  lcdwriteData("WiFi Connect OK"); //起動OKの表示
  lcdwriteCommand(0x40+0x80); //2行目へカーソル移動(0x40 = 2行目先頭文字アドレス、0x80 = DDRAMアドレスへの移動)
  lcdwriteData(ip_string); //IPアドレスの表示
  delay(1000);
    
  server.begin(); //サーバー起動

}
/*  WiFi接続処理  */


/*  データ書き込み(複数byte) */
void lcdwriteData(String t_data){
  
  int m_count = t_data.length();  //送信データの文字数をカウント
  
  if(m_count>16){ //データ数が16バイトを超えたら
    lcdwriteCommand(0x01);  //LCD液晶文字クリア([Clear Display])
    //全文字を表示
    for(int i=0;i<23;i++){
      if(i==16){  //17文字目に到達したら
        lcdwriteCommand(0x40+0x80); //2行目へカーソル移動
      }
      Wire.beginTransmission(LCD_ADRS); //LCDをI2Cスレーブとして通信開始([slave address])
      Wire.write(0x40); //最終データ、LCDへの表示データ([control byte] = 8bit目 0:最終,1:連続、7bit目 0:命令,1:表示データ)
      Wire.write(overflow[i]);  //1文字をキューに格納
      Wire.endTransmission(); //キューに格納されたデータを送信してI2C通信停止

    }
  }else{
  //全文字を表示
    for(int i=0;i<m_count;i++){
      if(i==16){
        lcdwriteCommand(0x40+0x80); //2行目へカーソル移動
      }
      Wire.beginTransmission(LCD_ADRS); //LCDをI2Cスレーブとして通信開始([slave address])
      Wire.write(0x40); //最終データ、LCDへの表示データ([control byte] = 8bit目 0:最終,1:連続、7bit目 0:命令,1:表示データ)
      Wire.write(t_data[i]);  //1文字をキューに格納
      Wire.endTransmission(); //キューに格納されたデータを送信してI2C通信停止

    }
  }
}
/*  データ書き込み(複数byte) */


/*  コマンド書き込み(1byte)  */
void lcdwriteCommand(byte t_command){ //(書き込み順 = [slave address] + [control byte] + [data byte] )
  
  Wire.beginTransmission(LCD_ADRS); //LCDをI2Cスレーブとして通信開始([slave address])
  Wire.write(0x00); //最終データ、LCDへの命令([control byte] = 8bit目 0:最終,1:連続、7bit目 0:命令,1:表示データ)
  Wire.write(t_command);  //命令文を送信([data byte])
  Wire.endTransmission(); //I2C通信停止
  delay(10);  //10ms待つ
  
}
/*  コマンド書き込み(1byteずつ)   */


/*  LCD初期化  */
void init_LCD(){
  
  delay(100); //電源ON後40ms以上待つ
  //[Function set] = 5bit目 0:4ビット,1:8ビット、4bit目 0:1行,1:2行、3bit目 フォントサイズ(0でOK)、1bit目 0:通常状態,1:以降拡張コマンド入力
  lcdwriteCommand(0x38); //8ビット、2行表示、5×8ドット、通常状態([Function set])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  lcdwriteCommand(0x39); //8ビット、2行表示、5×8ドット、以降拡張コマンド入力([Function set])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  //[Internal OSC frequency] = 4bit目 0:1/5バイアス電圧,1:1/4バイアス電圧、3〜1bit クロック周波数[kbits/s]
  lcdwriteCommand(0x14);  //1/5バイアス電圧、クロック周波数100kbit/s(標準)([Internal OSC frequency])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  //[Contrast set] = 4〜1bit コントラスト64段階調整([Power/ICONcontrol/Contrast set]と併せて設定)
  lcdwriteCommand(0x73);  //コントラスト設定([Contrast set])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  //[Power/ICONcontrol/Contrast set] = 4bit目 0:アイコン無,1:アイコン有(このLCDにはアイコンがないため0で設定)、3bit目 0:ブースターON(3.3V時),1:ブースターOFF(5V時)、
  //                                   2〜1bit コントラスト64段階調整([Contrast set]と併せて設定))
  lcdwriteCommand(0x56);  //アイコン無、ブースターON(3.3Vのため)、コントラスト設定(35)([Power/ICONcontrol/Contrast set]) 
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  //[Follower control] = 4bit目 0:フォロア回路OFF,1:フォロア回路ON、3〜1bit 増幅率の設定(0〜7)
  lcdwriteCommand(0x6C);  //フォロア回路ON、増幅度6([Follower control])
  delay(500); //200ms以上待つ
  lcdwriteCommand(0x38);  //8ビット、2行表示、5×8ドット、通常状態(拡張コマンド入力終了)([Function set])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  lcdwriteCommand(0x01);  //LCD液晶文字クリア([Clear Display])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  //[Display ON/OFF] = 3bit目 0:文字表示OFF(RAM内のデータはそのまま),1:文字表示ON、2bit目 0:カーソル表示OFF,1:カーソル表示ON、
  //                   1bit目 0:ブランク表示OFF,1:ブランク表示ON
  lcdwriteCommand(0x0F);  //文字表示ON、カーソル表示ON、ブランク表示ON([Display ON/OFF])
  delay(20); //26.3μs以上待つ(「lcdwriteCommand」で10ms待つためなくてもOK)
  
}
/*  LCD初期化  */
```

# Xcode(iPhone側)プログラム
今回作成するのが初めてなのでどこを編集するのか簡単に解説します。
プロジェクト構成の中で2箇所のみ(「ViewController」と「Main」)初期状態から追記しています。
 ![スクリーンショット 2024-08-30 14.35.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/d02c1795-576a-902e-e1f5-cbf2385cc3ea.png)

```C:01_WiFi_TEST.ViewController(Xcode)
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var SendButton: UIButton! //サーバーへデータを送信するボタン
    @IBOutlet weak var textField: UITextField!  //サーバー送信するデータを入力するテキストボックス

    override func viewDidLoad() {
        
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
    }
    
    
    @IBAction func SendButton(_ sender : Any) { //送信ボタンが押されれば
        
        var urlstring = "http://192.168.11.11/" + textField.text!   //ローカルIP+送信するデータを結合
        let url = URL(string: urlstring)!   //URLを生成
        var request = URLRequest(url: url)  //Requestを生成
        
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in  //非同期で通信処理内容
            guard let data = data else { return }
            do {    //データ(json)が返ってきたときの処理
                let object = try JSONSerialization.jsonObject(with: data, options: [])  // DataをJsonに変換
                print(object)
            } catch let error {  //エラー時の処理
                print(error)
            }
        }
        
        task.resume()   //タスク処理実行
        textField.text = "" //テキストボックスをクリア
        
    }

}
```

Mainについてはシミュレーター画面を用いて設定できる。
①画面に上から「1:Label」「2:Button」「3:TextField」を配置し、1と3については直接テキストを打ち込む
![スクリーンショット 2024-08-30 18.21.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/4b229833-fb59-84c3-da72-ffccc0973709.png)
②「Main」を「ViewController」と紐づけるために、「Send Button」と「Text Field」を右クリックし対象箇所を「ViewController」までドラッグ&ドロップし、「ViewController」で記入した「SendButton」と「textField」と紐づける。
![スクリーンショット 2024-08-30 18.27.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/58c8eeec-b6af-d8eb-4325-4c87423cc077.png)

③完了すればシミュレーターで自分が動かしたい端末を選択(本解説ではiPhone15Pro)し、左上の「▶️」を押せばシミュレーター端末にてプログラムが書き込まれる。
![スクリーンショット 2024-08-30 18.36.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/916f6b04-a2f4-7e8e-32e9-44bb8cb8d2a3.png)

# 動作確認
①ESP32側を先に起動し、LCDが以下の画面になるまで待機
![スクリーンショット 2024-08-30 18.47.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/19bb5d5c-0bdc-a487-9f8c-fb261f8ceac6.png)
②シミュレーターのiPhone画面で、テキストボックスに送付するデータを入力(今回は「1234」)し、「SendButton」を押す
![スクリーンショット 2024-08-30 18.42.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/447aa5f1-f098-a868-b5b8-a01173a085d1.png)
③ESP32側のLCD画面に受信したデータを表示
![スクリーンショット 2024-08-30 18.47.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/24f14f8b-2caa-e2c4-cdfe-b04df2280d9e.png)

これで任意のデータをiPhoneからESP32側に送信することができました。
ESP32側のプログラムで送信データによって動作する内容を変えれば様々な対応をすることが出来ますので応用してみて下さい。

# 次回作成記事予定
スマホから家の赤外線家電を遠隔操作(WiFi接続環境)