PlatformIOでXIAO ESP32C3を動かす

久しぶりに秋葉原に出てみたところ、秋月さんで[XIAO ESP32C3](https://akizukidenshi.com/catalog/g/gM-17454/)というボートを見つけました。
https://files.seeedstudio.com/wiki/XIAO_WiFi/board-pic.png
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/adc89d79-fa8d-6b31-e7bc-b8a8cde48dc3.png)

このシリーズ、サイズ感とお値段がいい感じなので好きなのです。もちろん購入。
まずはPlatformIOで動かせるのかを確認してみることにしました。

XIAO ESP32C3をUSBでWindowsに接続したところ自動的にデバイスを認識し、準備ができましたとのメッセージが表示されました。
![2022-10-09_184351.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/9e1b9242-7ed6-13a2-5571-dde64e4a99c3.png)

そのままPlatformIOを立ち上げて、BoardのところにXIAO ESP32C3と入力してみたところ、あっさり見つかりました。
![2022-10-09_184818.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/aa1fba23-c25c-14c3-f639-8b2b107da7ca.png)

プロジェクトを作成し、まずはそのままLチカを試してみたところ、ビルドは通るのですがLチカしません。Serialからも音沙汰無しです。
コンソールのメッセージを見ると wait usb download 的なメッセージが出ていたので、XIAO ESP32C3を抜き差ししてみたところLチカが始まりました！
どうやら初回セットアップ後、いちどデバイスを抜き差しする必要があったようです。

とりあえずLチカの動作を確認できたので、次にWiFiの動作を試してみました。

```
//https://lab.seeed.co.jp/entry/2020/05/21/120000
#include <Arduino.h>
#include <WiFi.h>
#ifdef ARDUINO_ARCH_ESP32
 #include <WebServer.h> //if you get an error here please update to ESP32 arduino core 1.0.0
#else
 #include <ESP8266WebServer.h>
#endif

#ifdef ARDUINO_ARCH_ESP32
WebServer* pServer = NULL;
#else
ESP8266WebServer* pServer=NULL;
#endif

static bool gIsLedOn;

void setServerCallback(){
  static const String htmlStr0 ="<!DOCTYPE html><html lang='ja'><head><meta charset='UTF-8'>";
  static const String htmlStr1 ="<style>input {margin:2px;width:190px;height:90px;}</style>";
  static const String htmlStr2 ="<title>test</title></head><body>";
  static const String htmlBtn1Str ="<div><form method='get'><input type='submit' name='nLed' value='LED'></form></div>";
  static const String htmlStr4 ="</body></html>";
  static const String main_htmlStr = htmlStr0+htmlStr1+htmlStr2+htmlBtn1Str+htmlStr4;

  pServer->on("/", [](){
    int paramNum = pServer->args();
    String str = main_htmlStr;

    if(paramNum>0){
        String nameArr[paramNum];
        String valArr[paramNum];
        for(int i=0;i<paramNum;++i){
            nameArr[i] = pServer->argName(i);
            valArr[i] = pServer->arg(i);
            Serial.print(nameArr[i]+":"+valArr[i]+", ");
        }
        Serial.println("//end");
        if(paramNum>0 && nameArr[0]=="nLed"){
          gIsLedOn = !gIsLedOn;
          digitalWrite(D10,gIsLedOn?HIGH:LOW);
        }
    }else{
        Serial.println("no params input:");
    }

    pServer->send(
      200, 
      "text/html", 
      str.c_str());
  });
}

void initServer(){
#ifdef ARDUINO_ARCH_ESP32
  pServer = new WebServer(80);
#else
  pServer = new ESP8266WebServer(80);
#endif
  //-----------------------------------------------------
  delay(100);
  const char *setup_ssid = "XIAOESP32AP";
  const char *setup_password = "98765";
  const IPAddress setup_ip = IPAddress( 192, 168, 10, 10 );
  const IPAddress setup_subnet = IPAddress( 255, 255, 255, 0 );
  WiFi.softAP(setup_ssid, setup_password);
  delay(100);
  WiFi.softAPConfig(setup_ip, setup_ip, setup_subnet);
  IPAddress myIP = WiFi.softAPIP();
  pServer->begin();
  delay(100);
  Serial.print("softAPIP: ");
  Serial.println("SSID: "+WiFi.softAPSSID());
  Serial.println("AP IP address: "+myIP.toString());
  Serial.println("Access to "+String(setup_ssid)+" 192.168.10.10/");

  setServerCallback();
  //-----------------------------------------------------
}

void setup() {
  // put your setup code here, to run once:
  gIsLedOn=true;
  Serial.begin(9600);
  Serial.println("start");
  pinMode(D10,OUTPUT);
  digitalWrite(D10,gIsLedOn?HIGH:LOW);
  initServer();
}

void loop() {
  // put your main code here, to run repeatedly:
  pServer->handleClient();
}
```
特にライブラリ等を入れることなくビルドすることができました。
付属のアンテナを接続し、上記を実行すると、WiFiの選択肢にESP32が追加されるのですが、おや・・・？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/a515ed5e-988f-4201-8b7b-6ac109604ffd.png)
XIAOESP32APと指定したはずのSSIDが、、、それにパスワードも設定されていないようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/8c5f9c59-ca00-10cf-d829-ffb687eef502.png)
ひとまずそこは後で調べるとして、Windowsを上記SSIDのWiFiに接続します。
接続したら ブラウザのアドレスバーに ```http://192.168.10.10``` と入力すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/e4cd3e96-107f-bad9-5356-17d37c96e977.png)

上記のような画面が表示されます。
その状態で
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/e0800640-3c19-0706-f152-1596e09e7279.png)

D10とGNDの間にLEDを接続し、ブラウザ上の[LED]ボタンを押たびにLEDが点灯/消灯します。

[動画](https://www.youtube.com/watch?v=yjXnlJK0rmg)

SSIDが違う件は、引き続き調査したいと思います（以前にも同じことがあったような・・・それも単純なミスだったような・・・）

続いてBLEを試してみます。

```
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLECharacteristic *pCharacteristic;
bool deviceConnected = false;

// https://www.uuidgenerator.net/
# define    SERVICE_UUID    "199a9fa8-94f8-46bc-8228-ce67c9e807e6" 
# define    CHARACTERISTIC_UUID "208c149e-8266-4686-8918-981e90546c2a"

const int _LED_PIN = D10;
const String BLEName = "XIAO_ESP32BLE";

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
        uint8_t data_buff[2]; // test用2byte'AB'
        data_buff[0] = 'A';
        data_buff[1] = 'B'; 
        Serial.printf("*** NOTIFY: %d, %d ***\n", data_buff[0], data_buff[1]);
        pCharacteristic->setValue(data_buff, 2);
    };

    void onDisconnect(BLEServer* pServer) {
      Serial.printf("*** disconnected. ***\n");
      deviceConnected = false;
    }
};



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

  // Create the BLE Device
  BLEDevice::init(BLEName.c_str());

  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");

  pinMode(_LED_PIN, OUTPUT);
}

void loop() {
  if(deviceConnected){
        Serial.print(".");
        uint8_t* pData = pCharacteristic->getData();
        digitalWrite(_LED_PIN, (pData[0]!='A')?LOW:HIGH); // 1byte目が'A'でなくなったら消灯
        pCharacteristic->notify();
  }
  delay(1000);
}

```

これも特にライブラリの追加なしにビルドすることができました。
[LightBlue等のアプリを使って](https://qiita.com/asuka4624254/items/0e934ed77b4aa698a90c)、valueに65('A'のascii code)を入れると点灯、それ以外を入れると消灯します（D10-GND間にLEDをつないでいれば）。

または[Web Bluetooth API](https://web.dev/i18n/ja/bluetooth/)を[使用して](https://neos21.net/blog/2020/07/04-01.html)、

```
<!DOCTYPE html>
<html><head><title>BLE test</title></head>
<script>
const SERVICE_NAME = "XIAO_ESP32BLE";
const SERVICE_UUID = "199a9fa8-94f8-46bc-8228-ce67c9e807e6";
const CHARACTERISTIC_UUID = "208c149e-8266-4686-8918-981e90546c2a";

var g_device = null;
var g_characteristic = null;
var g_value = 65;
    
_onload = function(){
    var info=document.getElementById('iInfo');
    ckFnc=async function(){
        info.innerHTML+="start<br>";
        if((g_device!=null)&&(g_device.gatt.connected)){
            info.innerHTML+="disconnect<br>";
            console.log('Disconnecting from Bluetooth Device...');
            g_device.gatt.disconnect();
            document.getElementById('iCheckBtn').value="check";
            document.getElementById('iSendBtn').value="---";
            g_device=null;
            g_characteristic=null;
            return;
        }
        navigator.bluetooth.requestDevice({
            filters: [{ name: SERVICE_NAME }],
            optionalServices: [SERVICE_UUID]
            //acceptAllDevices: false,
        })
        .then(device => {
            info.innerHTML+="navigator.bluetooth.requestDevice.then<br>";
            g_device = device;
            device.gatt.connect()
            .then(server => {
                info.innerHTML+="device.gatt.connect.then<br>";
                console.log(server);
                server.getPrimaryService(SERVICE_UUID)
                .then(service => {
                    info.innerHTML+="server.getPrimaryService(SERVICE_UUID).then<br>";
                    console.log(service);
                    document.getElementById('iCheckBtn').value="connected";
                    document.getElementById('iSendBtn').value="send";
                    service.getCharacteristic(CHARACTERISTIC_UUID)
                    .then(characteristic => {
                        info.innerHTML+="service.getCharacteristic(CHARACTERISTIC_UUID).then<br>";
                        console.log(characteristic);
                        g_characteristic = characteristic;
                        onRead();
                    }).catch(error => { console.error(error); })
                }).catch(error => { console.error(error); })
            }).catch(error => { console.error(error); });
        }).catch(error => { console.error(error); })
    }
}

onSend=function(){
    var info=document.getElementById('iInfo');
    info.innerHTML+="onSend<br>";
    if(g_characteristic!=null){
        info.innerHTML+="g_characteristic!=null<br>";
        console.log('send');
        const asciiA = 'A'.charCodeAt(0);
        g_value = (g_value==asciiA?0:asciiA); // 'A'なら点灯
        g_characteristic.writeValue(Uint8Array.of(g_value))
        .then(_ => {
            info.innerHTML+="g_characteristic.writeValue(Uint8Array.of(g_value)<br>";
            console.log(`${g_value} send ok.`);
        }).catch(error => { console.error(error); });
    }
}
onRead=function(){
    var info=document.getElementById('iInfo');
    info.innerHTML+="onRead<br>";
    if(g_characteristic!=null){
        g_characteristic.readValue()
        .then(value => {
            info.innerHTML+="g_characteristic.readValue().then<br>";
            console.log(`value= ${value.getUint8(0)}`);
        }).catch(error => { console.error(error); })
    }
}
onClear=function(){
    var info=document.getElementById('iInfo');
    info.innerHTML="---";
}
</script>
<body onload="_onload()">
<input type="button" id="iCheckBtn" value="check" onclick="ckFnc()"></input>
<input type="button" id="iSendBtn" value="---" onclick="onSend()"></input>
<div id='iInfo'>---</div>
<input type="button" id="iClearInfo" value="ClearInfo" onclick="onClear()"></input>
</body></html>
```

上記ページをどこかのサーバーに置き、ブラウザから立ち上げると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/13de5e77-0068-0c23-aa0f-a6fad37e5267.png)
こんな画面が表示されます。
XIAO ESP32C3を起動した状態で[check]ボタンを押すと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/089b226d-a045-bc80-a8cc-433d034b623c.png)
こんなウインドウが立ち上がるので、XIAO_ESP32BLEを選択しペアリングします。
(スマホからだとポップアップがブロックされるのでうまくいかないようです)

すると（D10-GND間にLEDをつないでいれば）LEDが点灯し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/9b5db2cd-44a3-fb27-a8b1-25a10b1553ff.png)
のように画面が変化します。
後はsendボタンを押すごとにLEDが点灯/消灯します。

このプログラムを使って[ダイソーのシャボン玉発生器を遠隔操作してみました](https://protopedia.net/prototype/3441)。