ラズパイIoTスタータキットanyPiとAzure Power BI のArcGISを使って、センサー情報を地図上で可視化

アルプスIoT Smart Moduleの気圧センサ、湿度センサ、温度センサの環境データをanyPiで受け取り、anyPiを構成する3GPIを使用して、受け取った環境データをMicrosoft Azure IoT Hub経由でPower BIに渡してリアルタイムにグラフィック表示を行います。同時に、anyPiにGPSを接続して、取得した位置情報を使って、Power BIのArcGISにより地図表示を行います。環境情報のグラフィック表示とGPSの位置情報による地図表示は、Power BIのダッシュボードに統合して表示します。anyPiに、アルプスIoT Smart Module、GPSを組み合わせた画像を次に示します。

<div align="center" "><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/9693c74b-4bc8-85ad-2da1-3895873b6c87.jpeg"></div>


anyPiはラズパイIoTスタータキット「[anyPi（エニーパイ）](http://mechatrax.com/products/anypi/)」で、[メカトラックス株式会社様](http://mechatrax.com/)から提供していただきました。「[3GPIを 用いたAzure IoT Hub への環境データの送信](http://qiita.com/TomoSoft/items/29b3cf8e5bb873623a9b)」では3GPIを使用しましたが、今回はanyPiを構成する3GPIを使用してネットワークにPPP接続を行い、Microsoft Azure IoT Hubに接続します。
[アルプスIoT Smart Module](http://www.alps.com/j/iotsmart-network/index.html)は、「[アルプスのセンサネットワークモジュールをラズパイ3と3GPIで使ってみる（其の2 C言語によるBLEソフト）](http://qiita.com/TomoSoft/items/8e211f29002c38fd2488)」では気圧、温度・湿度の環境データを取得してIFTTTにアップロードし、GoogleドライブのGoogleスプレッドシートに情報を保存しましたが、今回は取得した気圧、温度・湿度などの環境データをMicrosoft Azure IoT Hubにアップロードします。
Power BIは、Microsoft Azure IoT Hubで受け取った環境データや位置情報を、Azure Stream Analyticsを経由してMicrosoft Power BIダッシュボードへ、リアルタイム ストリーミングすることにより、Microsoft Power BIダッシュボードで環境データや位置情報をリアルタイムに表示させます。


#_Azure IoT SDKs for Cのコンパイル_
Azure IoT SDKs for CをanyPiにダウンロードし、ソースファイルを編集して、ビルドを行い、Azure IoT SDKのHTTPプロトコルのサンプルプログラムの実行ファイルを作成します。なお、HTTPプロトコルにより、Azure IoT Hubに接続するために、必要となるHTTPプロトコル用のサンプルソースの編集と、アルプスIoT Smart Module用のセンサ情報収集プログラムとGPSプログラムについては、項目「Azure IoT Hubへの接続のためのソースコードの修正」に詳細を示します。
>Azure IoT SDKは、言語ごとにSDKが分割されており、「[3GPIを 用いたAzure IoT Hub への環境データの送信](http://qiita.com/TomoSoft/items/29b3cf8e5bb873623a9b)」で記述した内容と異なる場所および名称で現在保存されています。

最初に、次のコマンドで、anyPiにAzure IoT SDKs for Cをダウンロードします。

```
$ git clone --recursive https://github.com/Azure/azure-iot-sdk-c.git
```

フォルダ「./azure-iot-sdks-c」にSDKがコピーされたことを確認し、次のコマンドでディレクトリ「azure-iot-sdks-c」に移動します。

```
$ cd azure-iot-sdk-c
```

次のコマンドを実行して環境を構築します。サンプルを実行するために必要な追加コンポーネントのインストールの問い合わせに対して、全て「y」と答えます。

```
$ sudo build_all/linux/setup.sh
```

項目「Azure IoT Hubへの接続のためのソースコードの修正」に従って、IoT Hubに接続するためのHTTPプロトコル用のサンプルソースコードの編集と、アルプスIoT Smart Module用のセンサ情報収集プログラムとGPSプログラムを修正し、次のコマンドでサンプルアプリケーションをビルドします。

```
$ ./build_all/linux/build.sh
```

Azure IoT SDKのHTTPプロトコルのサンプルプログラムの実行ファイル「iothub_client_sample_http」が、次のディレクトリに作成されていれば、ビルドは成功です。

```
$ ls cmake/iotsdk_linux/iothub_client/samples/iothub_client_sample_http
```

#_Azure IoT Hubへの接続のためのソースコードの修正_

コンパイルする前に、プライマリキー接続文字列の埋め込みと、アルプスIoT Smart Moduleのセンサ情報とGPSの位置情報をメッセージに編集するために、ダウンロードしたソースコードと既存のプログラムを変更して、センサ情報と位置情報をメッセージに編集するロジックを追加します。

##_Azure IoT SDKのHTTPプロトコルのサンプルプログラムへの接続文字列の埋め込み_
ファイル「./iothub_client/samples/iothub_client_sample_http/iothub_client_sample_http.c」をエディタで開き、データ定義部分の次に示すコード「[device connection string]」を、IoTHubのトップ画面から、左側のサイドメニュー「共有アクセスポリシー」をクリックし、表示された「iothubowner」をクリックし、表示された「接続文字列-プライマリキー」の内容で置き換えます。

```c
                              *
                              *              
/*String containing Hostname, Device Id & Device Key in the format:                         */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessKey=<device_key>"                */
/*  "HostName=<host_name>;DeviceId=<device_id>;SharedAccessSignature=<device_sas_token>"    */
static const char* connectionString = "[device connection string]";
                              *              
```

##_アルプスIoT Smart Moduleからのセンサ情報のメッセージ編集_
「[アルプスのセンサネットワークモジュールをラズパイ3と3GPIで使ってみる（其の2 C言語によるBLEソフト）](http://qiita.com/TomoSoft/items/8e211f29002c38fd2488)」で作成したプログラムを、Azure IoT SDKから呼び出せるように、次のように変更します。関数名を「ble_main」とし、この関数をAzure IoT SDKのHTTPプロトコルのサンプルプログラムから一度だけ呼び出します。以降は、アルプスIoT Smart Moduleから環境データが入力されるごとに、events_handler_main関数で、次のようにそれぞれ外部変数「temperature」「humidity」「pressure」に環境データを設定して、Azure IoT SDKのHTTPプロトコルのサンプルプログラムのSendEvent関数を呼び出します。なお、events_handler_main関数にすでに実装されている「IFTTTにアップロードし、GoogleドライブのGoogleスプレッドシートに情報を保存する」ロジックは削除します。

```c
static void events_handler_main(const uint8_t *pdu, uint16_t len, gpointer user_data, uint16_t *pHandle)
{
                              *
                              *              
	if(pdu[3] == 0xf3) {
		Pressure = ((float)pdu[5] + (float)pdu[6]*256)*860 / 65535 +250;
		Humidity = (((float)pdu[7] + (float)pdu[8]*256) - 896) / 64;
		Temperature = ((float)pdu[9] + ((float)pdu[10]*256) - 2096) /50;
		printf("Pressure:%7.2f Humidity:%5.2f Temperature:%5.2f\n ",Pressure,Humidity,Temperature);
		
		sprintf(ascPressure,"%7.2f",Pressure);
		sprintf(ascHumidity,"%5.2f",Humidity);
		sprintf(ascTemperature,"%5.2f",Temperature);

		// tomosoft
		temperature = Temperature;
		humidity =  Humidity;
		pressure =  Pressure;

		SendEvent();
	}
                              *
                              *              
```

##_GPSからの位置情報のメッセージ編集_
anyPiに接続されたGPSから位置情報を読み取る方法は、「[anyPiでGPSデータの取得](http://tomosoft.jp/design/?p=9357)」に示します。このプログラムを、Azure IoT SDKから呼び出せるように、次のように変更します。関数名を「gps_main」とし、GPSから位置情報が入るまで繰り返し入力要求を行い、入力した位置情報は、それぞれ外部変数「latitude」と「longitude」に設定します。この変数の内容は、アルプスIoT Smart Moduleからのセンサ情報をアップロードするときに、メッセージに組み込みます。

```c
#include <gps.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

// tomosoft
extern  double latitude ;
extern  double longitude ;
	

int gps_main() {
	int rc;
	struct timeval tv;
	struct gps_data_t gps_data;
	
	printf("gps start0\n");
	if ((rc = gps_open("localhost", "2947", &gps_data)) == -1) {
	    printf("code: %d, reason: %s\n", rc, gps_errstr(rc));
	    return EXIT_FAILURE;
	}
	printf("gps start1\n");
	gps_stream(&gps_data, WATCH_ENABLE | WATCH_JSON, NULL);
	printf("gps start2\n");

	while (1) {
	    /* wait for 2 seconds to receive data */
	    if (gps_waiting (&gps_data, 2000000)) {
	    	printf("gps start3\n");
	        /* read data */
	        if ((rc = gps_read(&gps_data)) == -1) {
	            printf("error occured reading gps data. code: %d, reason: %s\n", rc, gps_errstr(rc));
	        } else {
	            /* Display data from the GPS receiver. */
	            if ((gps_data.status == STATUS_FIX) && 
	                (gps_data.fix.mode == MODE_2D || gps_data.fix.mode == MODE_3D) &&
	                !isnan(gps_data.fix.latitude) && 
	                !isnan(gps_data.fix.longitude)) {
	                //gettimeofday(&tv, NULL); EDIT: tv.tv_sec isn't actually the timestamp!
	                latitude = gps_data.fix.latitude;
	                longitude = gps_data.fix.longitude;
	                printf("latitude: %f, longitude: %f, speed: %f, timestamp: %ld\n", latitude, longitude, gps_data.fix.speed, gps_data.fix.time); //EDIT: Replaced tv.tv_sec with gps_data.fix.time
	                break;
	                	
	            } else {
	                printf("no GPS data available\n");
	                // break;
	            }
	        }
	    }
		else{
	    	printf("gps start4\n");
		}

	    sleep(3);
	}

	/* When you are done... */
	gps_stream(&gps_data, WATCH_DISABLE, NULL);
	gps_close (&gps_data);
	
	return EXIT_SUCCESS;
}
```

##_Azure IoT Hub に送信するメッセージ編集_
取得した環境データや位置情報をAzure IoT Hub に送信するメッセージは、ファイル「./iothub_client/samples/iothub_client_sample_http/iothub_client_sample_http.c」を変更して作成します。「./iothub_client/samples/iothub_client_sample_http/iothub_client_sample_http.c」のmain関数を次のように編集して、GPSプログラムのgps_main関数を呼び出します。

```c
int main(void)
{
   (void)printf("main\r\n");
   gps_main();
   iothub_client_sample_http_run();
   return 0;
}
```

iothub_client_sample_http_run関数を次のように編集して、アルプスIoT Smart Module用のセンサ情報収集プログラムのble_main関数を呼び出します。

```c
                              *
                              *              
if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle, ReceiveMessageCallback, &receiveContext) != IOTHUB_CLIENT_OK)
{
    (void)printf("ERROR: IoTHubClient_LL_SetMessageCallback..........FAILED!\r\n");
}
else
{
    (void)printf("IoTHubClient_LL_SetMessageCallback...successful.\r\n");
	
    /* Now that we are ready to receive commands, let's send some messages */
	getflg = false;
	ble_main();   // tomosoft
    (void)printf("tomosoft1\r\n");
	
    do
    {
    	while(true){
    		if (getflg) break;
        	ThreadAPI_Sleep(1000);
    	}
                              *
                              *              
```
アルプスIoT Smart Module用のセンサ情報収集プログラムからは気圧、温度・湿度の環境データ、GPSプログラムからは緯度/経度データを、それぞれぞ次の変数に設定します。

```c
double latitude = 0.0;
double longitude = 0.0;
double temperature = 10.52;
double humidity = 75.23;
double pressure = 1008.36;
```
アルプスIoT Smart Module用のセンサ情報収集プログラムが環境データを取得すると、次に示すSendEvent関数を呼び出します。SendEvent関数は現状変数に設定されている気圧、温度・湿度の環境データ、緯度/経度データをメッセージに編集し、anyPiを構成する3GPIを使用して、Microsoft Azure IoT Hubにアップロードします。

```c
void SendEvent(void)
{
	size_t iterator = 0;

	sprintf_s(msgText, sizeof(msgText), "{\"deviceId\":\"myFirstDevice\",\"windSpeed\":%.2f,\"latitude\":%.6f,\"longitude\":%.6f,\"temperature\":%.2f,\"humidity\":%.2f,\"pressure\":%.2f}", 
		avgWindSpeed + (rand() % 4 + 2), latitude, longitude,temperature,humidity,pressure);
	if ((messages[iterator].messageHandle = IoTHubMessage_CreateFromByteArray((const unsigned char*)msgText, strlen(msgText))) == NULL)
	{
	    (void)printf("ERROR: iotHubMessageHandle is NULL!\r\n");
	}
	else
	{
	    MAP_HANDLE propMap;

	    messages[iterator].messageTrackingId = iterator;

	    propMap = IoTHubMessage_Properties(messages[iterator].messageHandle);
	    (void)sprintf_s(propText, sizeof(propText), temperature > 28 ? "true" : "false");
	    if (Map_AddOrUpdate(propMap, "temperatureAlert", propText) != MAP_OK)
	    {
	        (void)printf("ERROR: Map_AddOrUpdate Failed!\r\n");
	    }

	    if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle, messages[iterator].messageHandle, SendConfirmationCallback, &messages[iterator]) != IOTHUB_CLIENT_OK)
	    {
	        (void)printf("ERROR: IoTHubClient_LL_SendEventAsync..........FAILED!\r\n");
	    }
	    else
	    {
	        (void)printf("IoTHubClient_LL_SendEventAsync accepted message [%zu] for transmission to IoT Hub.\r\n", iterator);
	    
	    }

	}
    IoTHubClient_LL_DoWork(iotHubClientHandle);
	getflg = true;
}

```


##_ビルド自動化のコマンド「CMake」の編集_
コンパイラに依存しないビルド自動化のコマンド「CMake」により、アルプスIoT Smart Module用のセンサ情報収集プログラムとGPSプログラムもコンパイルしてリンクできるように、「./iothub_client/samples/iothub_client_sample_http/CMakeLists.txt」を次のように編集します。

- Bluetooth Low Energy（BLE）用のライブラリ「BlueZ」のパスの設定
- アルプスIoT Smart Module用のセンサ情報収集プログラムとGPSプログラムのソースコードとヘッダファイルの指定
- コンパイルに必要なインクルードファイルと、実行ファイルを作成するためのライブラリの指定

```
                       *
                       *
set(BLUEZ_PATH 
/home/pi/work/bluetooth/bluez-5.40/
)

if(NOT ${use_http})
	message(FATAL_ERROR "iothub_client_sample_http being generated without HTTP support")
endif()

set(iothub_client_sample_http_c_files
iothub_client_sample_http.c
)

set(iothub_client_sample_http_h_files
iothub_client_sample_http.h
)

set(iothub_client_sample_http_c_files
${iothub_client_sample_http_c_files}
anyPi/gps.c
)

set(iothub_client_sample_http_c_files
${iothub_client_sample_http_c_files}
anyPi/att.c
#anyPi/gatt-service.c
anyPi/gatt.c
anyPi/gattrib.c
anyPi/gatttool.c
anyPi/log.c
${BLUEZ_PATH}/client/display.c
${BLUEZ_PATH}/btio/btio.c
)

set(iothub_client_sample_http_h_files
${iothub_client_sample_http_h_files}
anyPi/att-database.h
anyPi/att.h
anyPi/gatt-service.h
anyPi/gatt.h
anyPi/gattrib.h
anyPi/gatttool.h
anyPi/log.h
)

IF(WIN32)
	#windows needs this define
	add_definitions(-D_CRT_SECURE_NO_WARNINGS)
	add_definitions(-DGB_MEASURE_MEMORY_FOR_THIS -DGB_DEBUG_ALLOC)

if (WINCE) # Use C++ as mitigation for C99
  SET_SOURCE_FILES_PROPERTIES(iothub_client_sample_http.c PROPERTIES LANGUAGE CXX)
ENDIF()

ENDIF(WIN32)

include_directories(. )
include_directories(${BLUEZ_PATH})
include_directories(${BLUEZ_PATH}/btio)
include_directories(/usr/include/glib-2.0)
include_directories(/usr/lib/arm-linux-gnueabihf/glib-2.0/include)
include_directories(/usr/include/dbus-1.0)
include_directories(/usr/lib/arm-linux-gnueabihf/dbus-1.0/include)

add_executable(iothub_client_sample_http ${iothub_client_sample_http_c_files} ${iothub_client_sample_http_h_files})

target_link_libraries(iothub_client_sample_http 
#iothubclient is here only because locking... in gballoc no less.
	iothub_client
	iothub_client_http_transport
	gps
	${BLUEZ_PATH}/lib/.libs/libbluetooth-internal.a
	${BLUEZ_PATH}/src/.libs/libshared-glib.a
	readline
	glib-2.0

)
                       *
                       *
```

#_Microsoft Power BIダッシュボードへのリアルタイム ストリーミング_
anyPiからのアルプスIoT Smart Moduleのセンサ情報は、anyPiを構成する3GPIを使用して、Microsoft Azure IoT Hubにアップロードします。Microsoft Power BIダッシュボードでリアルタイムに表示させるために、Microsoft Azure IoT HubからAzure Stream Analyticsを経由させます。「[Stream Analyticsから Power BIダッシュボードへリアルタイム ストリーミング](http://tomosoft.jp/design/?p=9525)」に従って、Microsoft Power BIでアカウント作成し、Azure Stream Analyticsを設定します。Azure Stream Analyticsで作成したテーブルに、受け取ったアルプスIoT Smart Moduleのセンサ情報が設定され、この情報をMicrosoft Power BIで参照します。テーブルに設定されたセンサ情報は、Microsoft Power BIでをグラフ化して位置を地図に表示し、Microsoft Power BIダッシュボックスに統合して表示します。設定を行ったMicrosoft Power BIを次に示します。

<div align="center" "><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/a990b5e3-6836-7e76-3573-e9428b9e4ec8.jpeg"></div>

#_アルプスIoT Smart Moduleのセンサ情報をリアルタイムでMicrosoft Power BIに表示_
anyPiはアルプスIoT Smart Moduleのセンサ情報を受け取り、anyPiを構成する3GPIを使用してMicrosoft Azure に送信して、リアルタイムでMicrosoft Power BIに表示させます。

最初にanyPiにら接続されたGPSを、次のコマンドでonにします。

```
$ sudo 3gpictl --gpson
```

次にanyPiから、作成したAzure IoT SDKのHTTPプロトコルのサンプルプログラムを、次のコマンドで実行します。

```
$ cmake/iotsdk_linux/iothub_client/samples/iothub_client_sample_http/iothub_client_sample_http

```

リアルタイムにMicrosoft Power BIダッシュボードに表示されます。


<div align="center" "><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/0f4bca31-4f62-3397-7c20-00ed289182fb.jpeg"></div>

anyPiでAzure IoT SDKのHTTPプロトコルのサンプルプログラムを実行したコンソール端末には、次のようなメッセージが表示されます。

```
$ make/iotsdk_linux/iothub_client/samples/iothub_client_sample_http/iothub_client_sample_http
main
gps start0
gps start1
gps start2
gps start3
no GPS data available
gps start3
no GPS data available
gps start3
no GPS data available
gps start3
no GPS data available
gps start3
latitude: 35.432493, longitude: 139.646223, speed: 0.000000, timestamp: -964270490
Starting the IoTHub client sample HTTP...
Info: IoT Hub SDK for C, version 1.1.19
IoTHubClient_LL_SetMessageCallback...successful.
Connection successful
charWriteReq[0][]=char-write-req 0x0018 200300
char_write_req_cb
Characteristic value was written successfully
st_state = 5
charWriteReq[1][]=char-write-req 0x0018 230301
char_write_req_cb
Characteristic value was written successfully
charWriteReq[2][]=char-write-req 0x0013 0100
                       *
                       *
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 33 05 09 
Notification handle = 0x0012 value: f3 14 d0 e2 87 15 e7 0c 00 80 00 80 00 00 00 00 01 0a 0f 09 
Pressure:1011.96 Humidity:72.11 Temperature:24.14
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[49] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 34 05 0a 
Notification handle = 0x0012 value: f3 14 d4 e2 b9 15 f3 0c 00 80 00 80 00 00 00 00 01 0a 0f 0a 
Pressure:1012.01 Humidity:72.89 Temperature:24.38
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[50] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 35 05 0b 
Notification handle = 0x0012 value: f3 14 d0 e2 e2 15 ff 0c 00 80 00 80 00 00 00 00 01 0a 0f 0b 
Pressure:1011.96 Humidity:73.53 Temperature:24.62
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[51] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 36 05 0c 
Notification handle = 0x0012 value: f3 14 ce e2 05 16 0a 0d 00 80 00 80 00 00 00 00 01 0a 0f 0c 
Pressure:1011.93 Humidity:74.08 Temperature:24.84
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[52] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 37 05 0d 
Notification handle = 0x0012 value: f3 14 d0 e2 1a 16 12 0d 00 80 00 80 00 00 00 00 01 0a 0f 0d 
Pressure:1011.96 Humidity:74.41 Temperature:25.00
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[53] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
Notification handle = 0x0012 value: f2 14 00 80 00 80 00 80 00 80 00 80 00 80 00 00 0d 38 05 0e 
Notification handle = 0x0012 value: f3 14 ce e2 33 16 19 0d 00 80 00 80 00 00 00 00 01 0a 0f 0e 
Pressure:1011.93 Humidity:74.80 Temperature:25.14
 IoTHubClient_LL_SendEventAsync accepted message [0] for transmission to IoT Hub.
Confirmation[54] received for message tracking id = 0 with result = IOTHUB_CLIENT_CONFIRMATION_OK
                       *
                       *
```

Azure IoT SDKのHTTPプロトコルのサンプルプログラムを実行したときのAzure Stream Analyticsのトップ画面を次に示します。

<div align="center" "><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/2dd796f0-0101-b60e-853e-764ea33b218d.jpeg"></div>