ESP-NOW機能を使った無線通信の基本的なやり方について、送信編と受信編の2本に分けて記事を書いた。
ここでは、ESP-NOWによるデータの送受信について、応用的なやり方を記載する。
応用1 8ビット以上の数値を送る。
送信編の記事に書いたとおり、基本的にESP-NOWによるデータ送信はuint8_t型の変数を格納したリストしか送ることができない。すなわち、符号なし8ビットで表現できる0~255までの値しか送れないということだ。
試しに「300」という数値をESP-NOWで送ってみよう。結果はこうなる。

このように、300という数値を送ったにもかかわらず、「44」という数値を受け取ってしまった。
これは、300という数値は2進数で表すと100101100なのだが、これをuint8_t型変数に代入してしまうと8ビットしか入らないために最上位(9桁目)の1が入らず、00101100のみが格納されてしまう。00101100は10進数で言うと44であるため、受信側は44を受け取る。
(※厳密に言うと、先ほど「300という数値を送ったにもかかわらず」と書いたが、送信する時点で送信用の配列要素に300を代入した時点で既に最上位の数値が落ちてしまっているため、そもそも送信機は300を送ることができておらず、44を送信してしまっている。受信機はそれを正しく受け取り、結果は44だと言っているだけである。)
255よりも大きな数値を送信するため、ここでは「数値を8ビットごとに分割して送り、受信側で結合する」という作戦を取る。
今回送る値は、61644とする。これを2進数で表すと1111000011001100という16桁(16ビット)で記述される。この16ビット整数を、上8桁、下8桁に分割したものを格納するため、uint8_t型変数を2個用意する。なお、ここでは16ビット整数を例にして説明するが、同じやり方で32ビットなどのもっと大きな整数も送信できる。
uint16_t bigNum = 61644;//送信したい大きな値(16ビット) uint8_t num_MSB;//送りたいbigNumの上位8ビットを格納するための変数 uint8_t num_LSB;//送りたいbigNumの下位8ビットを格納するための変数
まずは下位8ビットを、num_LSBに格納しよう。やり方はとっても簡単。ただ普通に代入するだけで良い。
num_LSB = bigNum;//下位8ビットは普通に代入
変数bigNumは16ビットなので16桁の2進数であるが、それを代入する受け入れ先のnum_LSBは8ビットしか格納できないため、何もしなくてもただ代入するだけで下位8ビットのみを抽出して代入するような動作となる。

上位8ビットを代入するためには、まず16ビット変数を右に8つビットシフトさせる。その後に代入してやれば、上位8ビットを抽出して代入ができる。
bigNum = bigNum >> 8;//右に8桁ビットシフト num_MSB = bigNum;//ビットシフト後の変数の下位8桁(もとの変数の上位8桁)を代入

これで大きな桁数の変数を8ビットごとに分割してuint8_t型変数に代入できたので、これを送信する。受信側では、複数のuint8_t型変数を結合して、元々送りたかった大きな桁数の数値を再現する必要がある。
ここから先は受信機側での処理である。今は16ビット変数を送ろうとしているので、受信側にもそれを格納するためのuint16_t型変数を用意しておく(ここではRecvNumという名前で変数を用意した)。
あとは先程の8ビット分割と逆のような手順によって数値を結合する。つまり、まず上位8桁を16ビット変数の下位8桁に代入したあと、左に8ビットシフトさせて上位8桁とし、空いた下位8桁に受信した下位8ビットを足し合わせれば、元々送りたかった16ビットが完成する。
uint16_t RecvNum; //16ビット整数格納用の変数 RecvNum = data[0];//16ビット変数の下位8桁に、送信したい値の上位8桁を代入 RecvNum = RecvNum << 8;//左に8桁ビットシフトして、下位8桁を上位8桁に送る RecvNum = RecvNum + data[1];//送信したい値の下位8桁を、16ビット変数の下位8桁に加算する。
ここで、受信データとして受け取った配列であるdataの0番目の要素が上位8桁(先程のnum_MSBに相当)で、1番目の要素が下位8桁(num_LSB)である。
こうして結合した結果、どのような数値になったかチェックしてみよう。

240というのは上位8桁の11110000を10進数にしたもので、204は下位8桁の11001100を10進数にしたものである。ちゃんと上位・下位8桁に分割して送受信できていることがわかる。これらを上記のように結合して16ビット変数にしたものを10進数で表すと61644となっており、今回送りたかった大きな値がちゃんと送受信できている。
応用2 文字列の送信
数値だけではなく文字列を送りたいということもあるだろう。この場合、文字列を各文字に分割してuint8_t型で表される数値に変換した後に送信し、受信側でそれを一文字ずつ復元して繋げるというやり方となる。
まずは送信側の処理について。今回送りたい文字は”abcdef”とする。char型変数を格納する配列として用意する。また、これらの文字をuint8_t型に変換して格納するための配列も用意する。
char moji[] = "abcdef"; uint8_t mojiData[sizeof(moji)];
配列mojiを用意するとき、わざわざ要素数を指定せず、カッコ[]の中は何も書かずに直接文字列代入で宣言してやると楽。勝手に必要な要素数をカウントして配列を作ってくれる。なお、今回の文字列は6文字だから要素数は6個かと思いきや実際には文字数より1多い要素数の配列になる。なぜなら、char文字列の最後には文字列終了を表す空文字が入るからである。
というわけで、格納用のuint8_t型配列も6 + 1の7個の要素数を持ったものを用意する。sizeofを使って、配列mojiのサイズ(つまり要素数)と同じ要素数を持った配列として宣言してやれば良い。
さて、このchar型配列の各要素をuint8_tに代入するわけだが、char型変数はuint8_tと同じく8ビットの変数なので、先程のように桁を分割・結合する必要はない。そのまま代入してやるとよい。
for(int i=0;i < sizeof(moji);i++) { mojiData[i] = moji[i]; }
複数の文字からなる文字列mojiを1文字ずつ抜き取って8ビット変数として配列mojiDataに入れていっている。ただそれだけ。あとはこの配列mojiDataをESP-NOWで送信する。
次に、受信機側の処理。受け取った配列の各要素を抜き取って、char型配列に一つずつ入れていく。ちょうど送信時と逆の手順のようなものだ。データ受信時に呼び出される関数をまるごと以下で紹介する。
void dataRecv(const uint8_t *addr, const uint8_t *data, int dataSize)//データ受信時に呼び出される関数 { char recvMoji[dataSize];//受け取ったデータと同じ要素数の、文字列格納用配列を作る for(int i=0;i<dataSize;i++) //1文字ずつ抜き取って配列に格納 { recvMoji[i] = data[i]; } Serial.print("Received char is, ");//受け取った文字列をシリアル出力(確認用) Serial.println(recvMoji); }
これを、データ受信時に呼び出される関数内にて実行する。

送りたかった文字列”abcdef”をちゃんと送れていることがわかる。
3,まとめ
ESP-NOWは8ビットの整数しか送ることができないのでなんの工夫もしないと0~255までの値しか送ることができないが、本記事の方法によりもっと大きな値を送ったり、文字列を送信することができるので、実用上これらができたらだいたい事足りると思う。