紫外線センサ計測値をTWELITEで飛ばしてGoogle Spreadsheetへアップロード【前編】

# はじめに
「身の回りの環境を見える化したい」ということで、工作してみました。

センサは特に紫外線センサではなくても問題ありません。

全体のイメージ図は下記の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/5d8f72e7-a133-b0de-9e85-904c40d96e99.png)

###おおまかな処理の流れ
 1. 子機でセンサ値をADC（アナログデジタル変換）
 1. 子機からデータ送信
 1. 親機でデータ受信
 1. Raspberry Piへデータをシリアル送信
 1. 適当にデータを処理してGoogle APIでアップロード

# 使用するプログラムについて
TWELITEは購入した状態で、簡単に通信ができるアプリケーションがプリインストールされていますが、今回は自作しました。
（自作する場合には、[TWELITEライター](https://mono-wireless.com/jp/products/TWE-LITE-R/index.html)が必要になるので注意）
といっても、公式のSDKを使った開発は敷居が高かったので、[TWELITE ZONE](https://mae8bit-twe.hateblo.jp/index)さんのbasicio.hライブラリを使わせてもらいました。[チュートリアル](https://mae8bit-twe.hateblo.jp/tutorial)を参考にしつつ、下記を行ってください。
編集とビルドは、Visual Studio Codeを使います。

1. 
[旧ソフトウエア開発環境 TWELITE SDK](https://mono-wireless.com/jp/products/TWE-NET/index.html)
をダウンロードし、適当な作業フォルダに解凍
2. GitHubから[リポジトリ](https://github.com/akitaro7/HomeIoT_Twelite)をクローンし、1.で設置した開発環境の「...\TWELITE_SDK\MWSDK_201805\MWSDK」の下に配置する。（Wks_TWELITEディレクトリと同列になる）


# 子機の作成
TWELITEは乾電池2本から動作し、長期間運用できるのが長所ですが、今回はADCを使用するので9V→レギュレータを使用しました。


使用する回路はこんな感じです。
![回路図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/dcd5c575-d27b-97ec-5513-eea499099da3.png)
※素人回路図なので参考程度で・・・

基盤はこんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/29403651-3402-649d-44ca-3c04cb4fc473.png)
3Dプリンターで適当に殻を作って完成！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/43b755cb-88e5-c3f1-340a-7d3f07578b2f.png)


###子機のプログラム
VScodeで開く子機のプロジェクトディレクトリはここです。

```c:子機のプロジェクトディレクトリ
..\TWELITE_SDK\MWSDK_201805\MWSDK\HomeIoT_Twelite\twelite_basicio_UltravioletRays_Tx
```

####初期化
10秒ごとに本モジュールが呼ばれ、動作確認用のLEDとADCを設定します。

```c:main.c
// 起動時や起床時に呼ばれる関数
void setup(bool_t warmWake, uint32_t bitmapWakeStatus) {

    // 動作確認用のLED
    dio_pinMode(LED, OUTPUT);
    dio_write(LED, HIGH);

    //無線通信を初期化する
    radio_setupInit(
        RADIO_MODE_TXONLY,  //送信のみ
        APP_ID,             //アプリケーションID (受信側も同じ値)
        CHANNEL,            //チャンネル（受信側も同じ値）
        3);                 //無線出力(最大値)

    //ADCを設定
    adc_enable(ADC_SAMPLE_6,  //サンプル数 6
        ADC_CLOCK_500KHZ,     //ADCクロック 500KHZ
        FALSE);               //内部の基準電圧を使用

    //ADC開始
    adc_attachCallbackWithTimer(
        0,                  //タイマー番号。0~４
        0,                  //プリスケーラー。 0~16
        0,                  //呼び出し周期を決定する16ビットカウンタの目標値。
        FALSE,              //0V～基準電圧×1　(内部基準電圧使用時、0V～1.235V)
        ADC_TGT,            //測定対象を指定します。
        u16AdcData,         //結果を格納するバッファ。uint_16t型配列。
        ADC_CNT,            //バッファの要素数。2～2047
        FALSE,              //FALSE バッファがいっぱいになったら終了
        ADC_INT_FULL ,      //バッファいっぱいまで変換結果が書き込まれたとき
        adcDone);           //コールバック関数
}
```

####ADCコールバック関数
ADCが完了すると、adcDoneがコールされるので、データをパックして無線送信します。

```c:main.c
//ADC変換が完了した
void adcDone() {

    //バッファに格納されたAD変換結果は10ビットの生値であるため、adc_convertResults() によって単位変換する
    bool_t ret;
    ret = adc_convertResults(
        ADC_TGT,    //測定対象を指定します。
        u16AdcData,
        ADC_CNT);

    //ADCは必要なくなったので電源OFF
    adc_disable();

    if (ret == FALSE)
        return;
    
    //送信データクリア
    memset(&txData, 0, sizeof(txData));

    //************************************************************
    //送信データの設定は【ここから】行う
    //************************************************************
    //送信の共通情報を設定
    setTxHeadData();

    //センサデータを設定
    //紫外線モジュールのPDH、PDLの差分を計算する
    pushSensorData("DIFF", (int32_t)(u16AdcData[0]) - (int32_t)(u16AdcData[1]));
    pushSensorData("PDH", (int32_t)(u16AdcData[0]));
    pushSensorData("PDL", (int32_t)(u16AdcData[1]));

    //************************************************************
    //送信データの設定は【ここまで】で行う
    //************************************************************

    //無線データ送信バッファをすべて送信する
    sendDataBuff();

    bDone = TRUE;
}
```

後は、送信後に10秒寝ます。以下ループ。

```c:main.c
// setup()後、３種類のイベントで呼ばれるループ関数
void loop(EVENTS event) {
    if (event == EVENT_START_UP) {
        // 最初に呼ばれる
        //自身のモジュールアドレスを表示
        // radio_printf(
        //     RADIO_ADDR_BROADCAST,   //不特定多数に送信
        //     0,                      //データタイプ 0～7。自由に設定可
        //     "Send module address = %X\r\n", getModuleAddress());         //送信内容
    } else if (event == EVENT_TICK_SECOND) {
        // 1秒毎に呼ばれる
    } else if (event == EVENT_TICK_TIMER) {
        // 4ミリ秒毎(デフォルト)に呼ばれる

        if (bDone && radio_txCount() == 0) {
            // 動作確認用のLED消灯
            dio_write(LED, LOW);

            //AD変換が完了し、無線送信中のデータもなくなった
            //10秒後起床でスリープする
            sleepTimer(10000, FALSE);
        }
    }
}
```

####無線通信の内容（親機と共用）
データの中身は、データ名4byte + int32型のセットが9個までです。

```c:mySensorIF.h
#define NAME_TX_BYTE_LEN    7
#define NAME_DATA_BYTE_LEN  4
#define DATA_TX_MAX         9

typedef struct 
{
    /* Header data */
    char                nameTx[NAME_TX_BYTE_LEN];       //送信名前              7byte
    char                numData;                        //センサーデータ        1byte
    uint16_t            onBoardTmp;                     //温度                 2byte
    uint16_t            batteryMV;                      //バッテリー電圧[mV]    2byte
    uint32_t            timesTx;                        //送信回数              4byte
} mySensorHeader_t_typ;

typedef struct 
{
    /* data 8byte */
    char                nameData[NAME_DATA_BYTE_LEN];   //データ名前            4byte
    int32_t             val;                            //データ                4byte
} mySensorData_t_typ;

// MAX 92 byte
typedef struct 
{
    /* sensorf I/F data */
    mySensorHeader_t_typ    head;                       //共通情報              16byte
    mySensorData_t_typ      data[DATA_TX_MAX];          //センサデータ          8*9=72byte
} mySensorIF_t_typ;
```

# 親機の作成
親機には、[MONOSTICK](https://mono-wireless.com/jp/products/MoNoStick/index.html)を使用しました。

###親機のプログラム
VScodeで開く親機のプロジェクトディレクトリはここです。

```c:親機のプロジェクトディレクトリ
..\TWELITE_SDK\MWSDK_201805\MWSDK\HomeIoT_Twelite\twelite_basicio_Sensors_Rx
```

####初期化
シリアル通信と、無線の設定を行います。

```c:main.c
// 起動時や起床時に呼ばれる関数
void setup(bool_t warmWake, uint32_t bitmapWakeStatus) {
    //シリアル0の初期化
    serial_init(SERIAL_BAUD_115200);
    //無線通信を初期化する
    radio_setupInit(
        RADIO_MODE_TXRX,    //送受信可能
        APP_ID,             //アプリケーションID (受信側も同じ値)
        CHANNEL,            //チャンネル（受信側も同じ値）
        3);                 //無線出力(最大値)
    //無線受信コールバックを登録
    radio_attachCallback(NULL, radioRxFunc);
}
```

####無線の受信
無線を受信したら、データをアンパックしてシリアル通信でRaspberry Piへ送信します。

```c:main.c
//無線受信コールバック
void radioRxFunc(uint32_t u32SrcAddr, bool_t bBroadcast, uint8_t u8CbId,
    uint8_t u8DataType, uint8_t *pu8Data, uint8_t u8Length, uint8_t u8Lqi) {

    //受信情報を表示
    serial_printf("from=%X, ID=%d, LQI=%d, DataType=%d, DataLen=%d, ",
        u32SrcAddr, u8CbId, u8Lqi, u8DataType, u8Length);

    //受信データのアンパック
    mySensorIF_t_typ rxData;
    memcpy(&rxData, pu8Data, u8Length);

    //データを表示
    char tmpNameTx[NAME_TX_BYTE_LEN+1] = {};
    memcpy(tmpNameTx, rxData.head.nameTx, NAME_TX_BYTE_LEN);
    serial_printf("txName=%s, tmp=%d, bat=%d, times=%d, numData=%d, ",
        tmpNameTx, rxData.head.onBoardTmp, rxData.head.batteryMV, rxData.head.timesTx, rxData.head.numData);

    int i;
    char tmpDataName[NAME_DATA_BYTE_LEN+1] = {};
    for (i = 0; i < rxData.head.numData; i++)
    {
        memcpy(tmpDataName, rxData.data[i].nameData, NAME_DATA_BYTE_LEN);
        serial_printf("d%dName=%s, d%dVal=%d, ",
            i+1, tmpDataName, i+1, rxData.data[i].val);
    }
    serial_puts("\r\n");
}
```

シリアル通信で送信するデータが長いため、basicio.hライブラリのバッファを増やす必要がありました。
もし、今回使っている3個より多いデータ数を送信してシリアル通信が表示されなくなった場合は、下記のバッファを増加してください。

```c:basicio.h
//シリアル0送信FIFOバッファ(16～2047)
#ifndef SERIAL_TX_BUFFER_SIZE
//#define SERIAL_TX_BUFFER_SIZE 96
#define SERIAL_TX_BUFFER_SIZE 256
#endif
```

#動作確認手順
無線で使用するアプリケーションIDと親機のアドレスを書き換えてください。

+ アプリケーションID：
他の開発者やプロジェクトと重複しない番号を設定する必要があります。販売元は、どれか一つのTWELITEのシリアル番号に0x80000000を加えて使うことを推奨しているようですが、 basicio.hライブラリでは、[リファレンス](https://mae8bit-twe.hateblo.jp/entry/2019/09/10/113149#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3ID)に「大きさは32ビットの整数です。16進表記で0xHHHHLLLLとした場合、HHHH,LLLL共に0x0001～0x7FFFの範囲に収める値としてください。」とあります。事実、0x80000000を加えて作成したアプリケーションIDの通信は失敗しました。
+ 親機のアドレス：送信先のTWELITEのアドレスです。（親機のプログラムを書き込んだ時に、表示されるMy module address = xxxxxxxx←この8桁16進数）

```c:HomeIoT_Twelite/twelite_basicio_Sensors_Rx/Main/Source/myRadioKey.h
#define APP_ID   0x12345            // TODO:自分のアプリケーションIDを設定
#define HOST_ADD 0x81234567         // TODO:母機のアドレスを入力
#define CHANNEL 18
```

ということで、親機のアドレスを知るために、親機のプロジェクトディレクトリを開いてビルド＆書き込みを行います。
（このとき、親機のAPP_IDも書き換えておくと手間が省けます。親機側のHOST_ADD は何でもよいので）

ビルドの手順は[チュートリアル](https://mae8bit-twe.hateblo.jp/tutorial)を確認ください。
成功すると、下記のようにバイナリファイルが出力されるはずです。

```c:親機のアプリBIN
..\twelite_basicio_Sensors_Rx\Main\Build\twelite_basicio_Sensors_Rx_Main_BLUE_L1200_V0-1-1.bin
```

ビルドは旧環境で行っていますが、書き込みとデバックは[TWELITE STAGE](https://mono-wireless.com/jp/products/stage/index.html)が便利なのでこちらを使っています。TWELITE_Stage.exeと同列のディレクトリにBINディレクトリがあるので、親機のアプリBINを保存してください。



下記の操作で親機のアドレスを確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/a98c169a-1293-b1ec-4bf1-69dc96f45f2b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/040929f9-35ca-3948-17e0-9078c260a62e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/f88cef04-a432-3df6-1250-213a83a1a411.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/0c413bb9-2672-349f-89fa-b769ca3afaa8.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/611d41fd-22f6-8774-b134-d4dd4927f4f9.png)

表示された親機のIDを使って、子機のプログラムを修正→ビルド→書き込みを行います。

うまくいけば、子機から10秒ごとにデータが送信されてTWELITE STAGEの画面内にシリアル通信内容が表示されるはずです。


前編はここまで。
次回は、Raspberry Pi編をやります。