見出し画像

【電子工作】M5ATOMとBLE その3

前回

やっとこさ、コントローラーのように、M5ATOMから、micro:bitへイベントを送信することができました

すぐにATOMでコントローラーを作りたいところですが、
micro:bit が提供するイベントのサービスと、その各キャラクタリスティックが気になって仕方がないのです

イベントのサービス

知っておいて損はないはず
もう少し調べてみます

キャラクタリスティック

EVENT SERVICEの公式ドキュメントはこちら ↓

各キャラクタリスティックは、()内に使えるプロパティも加えると、こちら ↓

  • MicroBit Requirements(read / notify)

  • MicroBit Event (read / notify)

  • Client Requirements (write)

  • Client Event (write / write wit response)

ドキュメントを読んでみると、予想した通り、
MicroBit~で始まるイベントは、micro:bitから発信するイベント
Client~で始まるイベントは、Bluetooth接続した端末から発信するイベント
のようです
各キャラクタリスティックに付与されている read / notify / write も、納得できます(with responseの使い所がわかりませんが、やれることはwriteとおなじになるはず、と見ています)

ただ、いまいち「Requirements」の有無が理解できていません
もう少し挙動を追いたいので、実装してみます

任意のイベントIDはどうやってブロックに定義できるの?

MakeCode上で、イベント受信ブロックをいくつか用意してみましたが、使えるIDが決まっているようです
定義済みのIDなら受信できることは容易そうですが、
プルダウン内には存在しないID、任意のID
を送るには一体、どうしたらいいのでしょうね・・・?

無理やり変数を作って、初期化して、ブロックに設定してみるとエラーになります・・・

エラーになる理由は、変数宣言の順番が違うようです
かなり疑わしい実装ですが、コード側を見てみるとたしかにそのようでした

なので・・・
強引にmain.py上で変数宣言位置を変更してやってみます・・・

プログラムは通ったけど、ブロックの方に画面を移動すると、
コードの編集分が無かったことにされてしまいますね・・・
しかもちゃんと修正済みの.hexもダウンロードできている・・・

カスタムブロック

でも、このやり方・・・正解じゃないよね・・・
もしブロックに任意のIDや定数を組み込むなら、拡張機能作るのかな・・・?

と思ってみると、エクスプローラー上に「+」ボタンを発見・・・
「カスタムブロック」!?
これか~・・・
しかもcustom.ts の雛形も親切~

・・・かとおもいきや、
なにこれ? 全く慣れない・・・
コメントにコマンドいれて、ブロックを定義する感じか・・・
覚えることが多い!馴染めない!

結局、コピペが近道なので、
control.onEventのソースコードを見よう見まねで定義してみました

enum MyEnum {
    //% block="MyEventMES_4649"
    MyEventMES_4649 = 4649,
    //% block="MyEventMES_5963"
    MyEventMES_5963 = 5963,
}

/**
 * Custom blocks
 */
//% weight=100 color=#0fbc11 icon=""
namespace custom {

    /**
     * Registers an event handler.
     */
    //% weight=20 blockGap=8 blockId="custom_on_event" block="on event|from %src=MyEnum|with value %value=MyEnum"
    //% help=control/on-event
    //% blockExternalInputs=1
    export function onEvent(id: MyEnum, event: MyEnum, handler: Action): void {
        control.onEvent(id, event, 
        function() { 
            serial.writeLine("aaaaaaaaaaaaaaaa");
            handler();
        });
    }
}
僕定義のカスタムブロック

これが正解っぽいですね
blockId をしかるべき名前で定義することが重要っぽい感じです
しかし、ちょっと慣れないなぁ・・・

ATOM側

4つのキャラクタリスティックをすべてregisterNotifyして、
Client~ではじまるキャラクタリスティックに対してwriteValueしてみました

#include "BLEDevice.h"
#include "M5Atom.h "
static BLEUUID ubit_event_serviceUUID("E95D93AF-251D-470A-A062-FA1922DFA9A8");  //event service


static BLEUUID ubit_event_characteristic_microbit_requirementsUUID("E95DB84C-251D-470A-A062-FA1922DFA9A8");  //microbit requirements
static BLEUUID ubit_event_characteristic_microbit_eventUUID("E95D9775-251D-470A-A062-FA1922DFA9A8"); //microbit event
static BLEUUID ubit_event_characteristic_client_requirementsUUID("E95D23C4-251D-470A-A062-FA1922DFA9A8"); //Client Requirements
static BLEUUID ubit_event_characteristic_client_eventUUID("E95D5404-251D-470A-A062-FA1922DFA9A8"); //client event
static BLEUUID* characteristicUUIDs[4] = {
  &ubit_event_characteristic_microbit_requirementsUUID,
  &ubit_event_characteristic_microbit_eventUUID,
  &ubit_event_characteristic_client_requirementsUUID,
  &ubit_event_characteristic_client_eventUUID,
};

static BLEAddress *pServerAddress = new BLEAddress("xx:xx:xx:xx:xx:xx");  //自分のMicrobitのMACアドレス
static BLERemoteCharacteristic* remoteCharacteristics[4] = {
  nullptr,
  nullptr,
  nullptr,
  nullptr,
};
typedef struct event {
  uint16_t event_type;
  uint16_t event_value;
} event;

static void CallbackNotify(
    BLERemoteCharacteristic *pBLERemoteCharacteristic,
    uint8_t *pData,
    size_t length,
    bool isNotify)
{

    Serial.print("Notify/Indicate Callback for characteristic: ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" data length ");
    Serial.print(length);
    Serial.print(" data: ");
    event* ev = (event*)pData;    
    Serial.printf("%0d, %0d", ev->event_type, ev->event_value);
    Serial.printf(" isNotify=%d", isNotify);
    Serial.print("\n");
}

void setup()
{
    BLEDevice::init("");
    M5.begin(true, false, true);

    Serial.begin(115200);
    Serial.print("Forming a connection to ");
    Serial.println((*pServerAddress).toString().c_str());

    BLEClient *pClient = BLEDevice::createClient();
    
    Serial.println(" - Created client");

    // Connect to the  BLE Server.
    pClient->connect(*pServerAddress, BLE_ADDR_TYPE_RANDOM); //RANDOMにしないといけない
    Serial.println(" - Connected to server");

    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService *pRemoteService = pClient->getService(ubit_event_serviceUUID);
    if (pRemoteService == nullptr)
    {
        Serial.print("Failed to find our service UUID: ");
        Serial.println(ubit_event_serviceUUID.toString().c_str());
        return;
    }
    Serial.println(" - Found our service");

    // Obtain a reference to the characteristic in the service of the remote BLE server.    
    for (int i=0; i< 4; ++i)
    {
      BLEUUID* uuid = characteristicUUIDs[i];
      Serial.printf("require [%s].\n", uuid->toString().c_str());

      BLERemoteCharacteristic* p = pRemoteService->getCharacteristic(*uuid);
      if (p == nullptr)
      {
          Serial.print("Failed to find our characteristic UUID: ");
          Serial.println(uuid->toString().c_str());
      }
      /*
      //enable Notify/Indicate 
      BLERemoteDescriptor *pRD = p->getDescriptor(BLEUUID((uint16_t)0x2902));
      if (pRD == nullptr)
      {
          Serial.print("Failed to find our descriptor UUID: ");
          Serial.println(BLEUUID((uint16_t)0x2902).toString().c_str());
      }
      else
      {
          uint8_t data[2] = {0x01, 0x00}; //Notify
          //uint8_t data[2] = {0x02, 0x00}; //Indicate 
          pRD->writeValue(data, 2, false);
      }
      */
      p->registerForNotify(CallbackNotify);

      Serial.printf("canNotify:%d\n", p->canNotify());
      Serial.printf("canRead:%d\n", p->canRead());
      Serial.printf("canWrite:%d\n", p->canWrite());
    
      remoteCharacteristics[i] = p;
    }
    Serial.println(" - Found our characteristic");

}



int eventCount = 0;

void loop()
{

  if (M5.Btn.wasReleased())
  {
    event ev;
    int e = eventCount % 6;
    switch (e)
    {
      case 0:
      {
        ev.event_type = 1104;
        ev.event_value = 0; //any
        break;
      }
      case 1:
      {
        ev.event_type = 1104;
        ev.event_value = 10; //button 1 up
        break;
      }
      case 2:
      {
        ev.event_type = 2; // button b
        ev.event_value = 1; //down
        break;
      }
      case 3:
      {
        ev.event_type = 2; //button b
        ev.event_value = 0; //any
        break;
      }
      case 4:
      {
        ev.event_type = 4649; //test 1
        ev.event_value = 4649; //any
        break;
      }
      case 5:
      {
        ev.event_type = 5963; //test 2
        ev.event_value = 5963; //any
        break;
      }
    }
    
    Serial.printf("size=%d, cnt=%d, id=%d, event=%d\n", (int)(sizeof(ev)), e, ev.event_type, ev.event_value);
    ++eventCount;
        
    remoteCharacteristics[2]->writeValue((uint8_t*)&ev, sizeof(ev), true);    
    
  }

    delay(50);
    M5.update();    
}

結果としては、

  • Client Requirements へwriteValueでイベントを送信しても、イベント受信ブロックでは受信されない

  • Client Event へwriteValueでイベントを送信すると、イベント受信ブロックできる

  • MakeCodeのイベント送信ブロックを使って、イベントを送ると、「MicroBit Event」のキャラクタリスティックから、notifyを受け取れる

    • そのnotifyで受け取ったデータは、イベント送信で送ったデータと同じものだった

結論

結論としては、「Requirements」があるキャラクタリスティックのイベントは送受信できませんでした・・・
うーん・・・名前から察するに、micro:bitが定めているイベントだと思うのだけど・・・やり方がわからずでした

まぁ、任意のIDで送受信できるのであれば、ひとまずは何でもできそうですね!ポジティブになろう




GWですね
とりわけやること無いので、ATOMでフォロを動かすまでは、
たくさん投稿しよっと


この記事が気に入ったらサポートをしてみませんか?