CayenneLPP( Cayenne Low Power Payload) について調べてみた 

#調べた背景

LoraのGatewayやEndDevice（ノード）をRaspberryPiとDragino GPA-HATで作っています．
GPSデータをTTN(TheThingsNetwork）に送信してマップ表示させてみたりしたいと思っているのですがその中でGPSデータを送信するのに色々試してみたののひとつがCayenneLPPです．ちなにみCayenneはカイエンと読むみたいですがどこかの高級外車の名前のようです...  
アプリケーションのデータ形式（ペイロードフォーマット）でTTNのコンソールにCayenneLPPを選ぶところがあり調べて作ってみました．  

#CayenneLPP（カイエンLPP）のメリット

1. データサイズが小さい．GPSの緯度、経度、高度のデータを11バイトで送れます．  
2. 一通りのアプリケーションのフォーマットが規定されているのでデバイスとサーバ間で互換性をとりやすい．TTNが対応しているというのもあります．

3. 楽チン．自分で一から考えなくてよいし、先人のライブラリが利用できる．  
あと、特徴として複数のセンサデータをまとめて送れるとかノードのいろいろな設定ができたりします．（ノードが対応している必要がありますが）

#技術的内容

基本の３つの構成要素があります．チャンネル（Channel)とデータタイプ（Data Type)とデータそのもの（Data)です．  
LoraWANの場合は基本のデータ形式ではフレームポート（FPort）を1で使いますが2以上の値を使って高度な使い方もできます．
（参考までにLoraWANではFPort=0のペイロードはMacコマンドになります．
アプリケーションは1から223(0xdf)までを使います．）
ここでは便宜上FPort=1のデータ形式を基本データ形式、FPort>=2のデータ形式を拡張データ形式を呼ぶことにします．
(LoraWAN以外の通信規格でもFPortがあるかも知れませんが分かっていません．）
また、サーバからEndDeviceに対してアプリ的な指令（LoraWANのMacコマンド以外という意味）を送ることができます．

##Channel

1バイトでセンサーやアクチュエータの個別の名前に対応させて0から64（63？）までの値を割り当てます．
複数のセンサー、アクチュエータでは独立した値にして区別します．

##DataTypeとData

Data Typeは1バイトでセンサーやアクチュエータの種別を表します．
DataはData Type毎にフォーマットが規定されています．

##基本データ形式

| 種別 | Data Type 値 | データサイズ（バイト）| 精度 | 
|---|---|---|---|
| デジタル入力 | 0x0 | 1 | 1 |
| デジタル出力 | 0x1 | 1 | 1 |
| アナログ入力 | 0x2 | 1 | 0.01 |
| アナログ出力 | 0x3 | 1 | 0.01 |
| 照度計 | 0x65 | 2 | 1 lux |
| presence? | 0x66 | 1 | 1 |  
| 温度計 | 0x67 | 2 | 0.1 °C |
| 湿度計 | 0x68 | 1 | 0.5 % |
| 加速度計 | 0x71 | 6 | 0.001 G |
| 気圧計 | 0x73 | 6 | 0.1 hPa |
| ジャイロ計 | 0x86 | 6 | 各軸 0.01 °/s |
| GPS | 0x88 | 9 | 緯度、経度 0.0001 °、高度 0.01m | 
他にも押しボタン(0x93)やブザー(0x8a)、キャラクタディスプレイ(0x8d)などいろいろあるようです．
presenceの意味が良く分からないのですが存在センサー？でしょうか？

GPSデータの例：  

| 1 byte |  1 byte | 3 bytes | 3 bytes | 3 bytes |
|---|---|---|---|---|
| 0x1 | 0x88 | 0x5,0x5f,0xa4 | 0x00,0xfe | 0x5,0xc1,0x70 |
| Channel 1 | GPS | 緯度=35.2164 | 経度=138.4364 | 高度=3772.00m | 
ここでたとえば0x5c170=377200で0,01mが単位なので3772.00mと変換されます． 
なお、GPSとカイエンの両方のライブラリを使えばこのあたりのことは知らなくてもデータを渡すだけでやってくれます．

複数のセンサーデータを組み合わせて一度に送ったり、フレームごとに送るセンサ（チャンネル）を変えることもできます．
複数センサーの例：

| 1 byte | 1 byte | 2 bytes | 1 byte | 1 byte | 2 bytes | ... |
|---|---|---|---|---|---|---|
| 0x01 | 0x67 | 0x01,0x10 | 0x02 | 0x67 | 0xff,0xd7 | ... |
| センサ１ | 温度 | 0110=272=27.2°C | センサ２ | 温度 | ffd7=-41=-4.1°C | ... |

フレーム毎に変える例：
Frame N

| 1 byte | 1 byte | 2 bytes | 
|---|---|---|
| 0x01 | 0x67 | 0x01,0x10 |
| センサ１ | 温度 | 0110=272=27.2°C | 
Frame N+1

| 1 byte | 1 byte | 1 bytes | 
|---|---|---|
| 0x02 | 0x68 | 0x82 |
| センサ２ | 湿度 | 0x82=130=75% | 


##拡張データ形式

###パックドデータ

固定のチャンネル値を省略し、基本データ形式のセンサデータを連結した形式です．
通信するごとにチャンネル値は0から64まで増加していきます．
この形式ではセンサーデータの順は不変でないと通信できません．

| 1byte | N Bytes | 1byte | N byte | ... | 
|---|---|---|---|---|
| Data1 Type | Data1 | Data Type2 | Data2 | ... |
例： 

| 1 byte |  N bytes | 1 byte | N bytes |
|---|---|---|---|
| 0x67 | 0x01,0x10 | 0x67 | 0x00,0xfe |
| Temp | 27.2°C | Temp | 25.4°C |
| |0x110=272| |0xfe=254|
Channelはデータを送るたびに1->2->3と増えていきます．

（あくまで個人の感想です）
個人的にはあまり使わないほうがよさげな気がします．
解釈で複数センサーデータとの区別が面倒くさそうな気がするのと
一度にたくさん送るのは送信データサイズの制限に引っかかりやすいです．
LoraチップSX1276のFIFOサイズは送受共用256バイトで単純に使えば半々の128バイトずつです．試したところSF7で送受できるペイロードの最大サイズは115バイトでした．
ペイロードサイズは送信側、受信側、両方の制限を受けるので小さめにしておくのがよさげかと思います．

###フルスケールGPSデータ

この形式では基本GPSデータと異なる形式で高精度の高度データを送れるらしいが詳細は不明．．．

###時系列データ

あるチャンネルのデータを時系列にまとめて送る形式．
チャンネルはFPort値で通知します．
$$ Frame Port = Data Channel + 100 $$
時刻差Deltaはフレーム送信時txtimeからの差を秒単位で2バイトで表します．

| 1 byte | 2 bytes | N byte(s) | 2 bytes | N byte(s) | ... |
|---|---|---|---|---|---|
| Data Type | Delta1 | Data(txtime-Delta1) | Delta2 | Data(txtime-Delta2) | ... |

###アクチュエータコマンド

デジタルのON/OFF、HIGH,LOWやアナログの値．デジタルとアナログで同じ形式でDataTypeがありません．
デジタルのHIGHはアナログの1.00と同じ値になります．
100=0x0064

| 1 byte | 2 bytes | 1 byte |
|---|---|---|
| Channel | Value | 0xFF |

###デバイス設定

デバイスの次の設定をします．
1.UNIX時刻秒
2.送信の時間間隔秒
3.センサーデータ取得の間隔秒．

| Config Mask | Setting | Size | Definition |
|---|---|---|---|
| 0b00000001 | UTC Time | 4 | Unix timestamp 秒|
| 0b00000010 | TX Period | 4 | センサーデータの送信フレームの時間間隔秒 |
| 0b00000100 | Reading Period | 2 |  |

例えばセンサーは６０秒毎に読み取り、データの送信は５分毎にといった設定ができます．
複数の設定を同時に組み合わせて行うことができます.
この場合、下位ビットのUTC Timeから順に解釈していきます．
例：

| 1byte | 4 bytes | 4 bytes | 2 bytes |
|---|---|---|---|
| 0x07 | 0x5d,0x7e,0xe4,0xf8,0x59 | 0x00,0x00,0x01,0x2c | 0x00,0x3c |
| 0b00000111 | 0x5d7ee4f8 =1568597240 |  0x0000012c=300 | 0x003c=60 |
| ３つある | UTC時刻 2019/9/16 10:27:20 | 送信間隔 300秒 | センサー読取間隔 60秒 |

###センサー読み取り間隔設定

センサーごとで（チャンネルごとで）読み取り間隔を個別に設定できます．

| 1 byte | 2 bytes |
|---|---|
| チャンネル | 時間間隔秒 |

###センサー許可/禁止設定

データをあげるセンサ（チャンネル）を個々に許可、禁止できます．
8バイトで個々のビットにセンサ（チャンネル）を割り当てて設定します．
最下位ビットがチャンネル１に対応します．
最大６４個のセンサーに対応ということになります．
ちなみに許可=1、禁止=0

| 8 bytes(64 bit channel mask) |  | 
|---|---|
| 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff | 全チャネル許可 |
| 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 | チャンネル１は許可、他は禁止 |

（あくまで個人の感想です）
他の８バイトのデータフォーマットと重複しないか心配です．

#C言語での例

ArduinoやmbedのC++での例はいくつかあるようですがC言語のはあまりないようです．GithubにCayenne-LPPというC言語のプログラムがありましたのでそれを使ってみました．

```
    /// GPS version ////
    loc_t gps_data;
    gps_location(&gps_data);
    //// Cayenne_lpp format ////
    cayenne_lpp_t lpp;
    uint8_t channel=1;      // set application channel
    cayenne_lpp_reset( &lpp);
    cayenne_lpp_add_gps( &lpp, channel, gps_data.latitude, gps_data.longitude, gps_data.altitude);
    memcpy(telegram, lpp.buffer, lpp.cursor);
```
こんな感じで数行で使えます．最後の行のtelegram は送信されるペイロードです．Cayenne-LPPにサンプルコードがあります．
RaspberryPiで簡単にGPSデータをTTNに送れました！

#最後に

CayenneLPPはIPSO Alliance Smart Objects Guidelinesから引っ張ってきて
さらにこれはLightweight M2Mから引っ張ってきているそうです．
まだオリジナルに当たれていないので理解不足とかあるかもしれないです．

#参考

https://community.mydevices.com/t/cayenne-lpp-2-0/7510
https://developers.mydevices.com/cayenne/docs/lora/#lora-cayenne-low-power-payload-examples-device-with-gps
https://github.com/aabadie/cayenne-lpp