IoTエッジコンピューティングをSmartMeshで実践

今回の実験は、Moteに内蔵されている非力なマイコンを使って、振動計測ソリューションのFFTデータをSmartMeshで送信したIoTエッジコンピューティングを実践してみました。

振動計測ソリューションについては、こちら。

振動計測ソリューション

SmartMeshのSDKについては、こちらを参照してください。

SmartMesh SDK

▼目次

振動計測ソリューションの制御方法

振動計測ソリューションは、ADuCM4050をベースにしたしシステムで、高精度加速度センサーADXL1002による加速度データを4096ポイントのFFTを実行し、その結果をUARTにデータを出力するIoTエッジコンピューティングプラットフォームです(図1)。

図図1:振動計測ソリューション

標準で添付されるアプリは、Python3系のシステムで実行可能なスクリプトが用意されており、ADCデータおよびFFTデータの取得および、波形表示が行えます(図2)。

図図2:振動計測ソリューション

このシステムを利用するには、PySerialとnumpyを事前にインストールする必要があります。今回の説明では、割愛します。

UARTのデータフォーマット

振動計測システムは、2つのUARTのデータフォーマットがあります。一つは、システムにコマンド設定するコマンドフォーマット。もう一つは、システムから送られてくるデータフォーマットです。

FFT解析を行うには、システムにコマンド設定する必要があり、今回の設定では、以下のコマンドを指定しています。


:0 CPS 0000
:0 RMC 0000
:0 CPS 0000
:0 RRP 005s
:0 CPS 0000
:0 BSZ 0000
:0 CPS 0000
:0 DFA 0001
:0 CPS 0000
:0 AL1 0002
:0 CPS 0000
:0 AH1 0080
:0 CPS 0000
:0 AL2 0080
:0 CPS 0000
:0 AH2 0800
:0 CPS 0000
:0 AC8 0003
:0 CPS 0000
:0 AW8 0008
:0 CPS 0000
:0 VAX 0001
:0 CPS 0000
:0 CRS 0000

CPSコマンドは、コマンド設定モードです。CPSに続くコマンドを設定しますが、連続に指定することはできないので、コマンド設定するたびに、CPSコマンドを指定する必要があります。

詳しくは、振動計測ソリューションのサイトからダウンロードしてください。

振動計測ソリューション

FFTデータの受信

コマンドを指定すると、FFTのデータが送られてきますが、データフォーマットは、3種類あります。

  • XDSフォーマット
  • XFDフォーマット
  • ANIフォーマット

FFTの実行が開始されると、XDSフォーマットに続いて、XFDフォーマットがUARTから出力されます。ANIフォーマットは、事前に閾値を超えた場合に出力される仕様になっています(図3)。

図図3:振動計測ソリューションのデータ・フロー

XFDフォーマットは、ヘッダ部分に、経過時刻、CH情報、アラーム情報、バンドごとの周波数、バンドごとの周波数レベルが入っており、続けてFFTデータが4096データ分が送られてきます(図4)。

図図4:XFDデータフォーマット

SmartMeshでは、IoTエッジコンピューティングらしさを出すために、FFTのデータを丸ごと送出するのではなく、XFDのヘッダ部分を使用することにしました。

OCSDKで、Moteのアプリを構築

振動計測ソリューションから出力されるFFT解析データは、UART経由で送られてきますので、Mote側もUARTで受け取る必要があります。Mote側のUARTは、OCSDKのサンプルであるUART(RAW)をベースに構築します。

OCSDK UART(RAW)

振動計測ソリューションとMoteは、図5のようなデータ・フローを構築する必要があります。

図図5:全体のシステム・データ・フロー

OCSDKをビルドするための準備

今回の実験には、OCSDKのサンプルをベースにしますが、プロジェクトをビルドする必要があります。前回のBlinkモードでも行ったように、ソフトウェアの開発環境として、IARシステムズ社のEmbedded Workbench for Armが必要です。

IAR Systems Embedded Workbench for Arm

UART(HDLC)とUART(RAW)の違い

OCSDKのUARTは、UART(HDLC)とUART(RAW)があります。UART(HDLC)は、外部のマイコンから、SmartMeshのネットワーク参加およびデータ転送を行う仕組みを提供していますが、HDLCフォーマットを前提にしています。なので、MoteのUART API仕様に基づいたフォーマットで出力する必要があるのですが、振動計測ソリューションはHDLCのフォーマットでは出力できないので、UART(RAW)を使用します。

UART(RAW)による振動計測システムとのハンドリング

UART(RAW)の場合、HDLCのエンコードおよびデコードの必要はありません。OCSDKのサンプルは、UARTの送信タスクとUARTの受信タスクのみで構成されています。そのまま使うには不足する要素が多いので、アプリケーションタスクを追加し、コマンド処理およびデータ抽出処理を行うように構築しました(図6)。

図図6:タスク構成図

タスク間は、セマフォで同期を取る方法を実装しています。SmartMeshのMoteは、それ以外にも内部でいくつものタスクが実行されています。SMartMeshのラジオ制御であったり、CLIの制御であったり、10タスクほど動いています。そのため、必要とするタスクのスタックメモリもやたら大きく取ればいいわけではないことも容易に想像できます。

今回、使用したスタックサイズは、

  • アプリケーションタスク 2048 Bytes
  • UART送信タスク 512 Bytes
  • UART受信タスク 512 Bytes

としています。

UARTの最大受信可能サイズ

SmartMeshのMote側は、1回の転送可能なサイズに制限があります。もちろん、どのマイコンも転送できるサイズは制限されているのですが、SmartMeshのMoteは、120Bytesに制限されています。UARTのOCSDKサンプルも、120Bytesに制限されています。120Bytesを超えるデータを送受信するには、アプリ側で120Bytes毎に分割してデータ転送すればいいでしょう。

こういった技術的背景があるので、振動計測ソリューションのデータも120Bytes毎に分割したデータ転送版も用意してあります。

FFTヘッダデータの抽出

FFTのデータ抽出は、UARTから送られてくるペイロードのヘッダをキーにして、目的のデータのみを受信するようにしています。XFDのデータは、先頭に:0 XFD が入っているので、この文字列をキーにします。受信タスクからデータを受信した際に、アプリケーションタスクのループ内でキー判定をしています。

while(f_Data)
{
	OSSemPend(uart_app_v.uartAppSem, 0, &osErr);

	if(strncmp((char *)&uart_app_v.uartRxBuf[0],  
						Vib_xfd, sizeof(Vib_xfd))==0) {
		dnm_ucli_printf("XFD Frame Detect\r\n" );
		:
		:						
					

OSSemPend()は、セマフォを待っています。UART受信タスクが受信を完了すると、このセマフォを解除します。

次のstrncmp()で先頭6バイト分のヘッダを識別しています。

これで、振動計測ソリューションから送られてくるFFTのヘッダ情報の抽出ができるようになります。

SmartMeshへデータ送信

次に、SmartMesh側の実装です。OCSDKには、SmartMeshに参加するためのいくつかのサンプルがあるので、それに習った実装をしています。

ヘッダ指定

SmartMesh側にデータを送信する場合に、ヘッダ情報を指定する必要があります。ここでは、他のサンプルでも指定している方法を用いています。

// prepare packet to send
pkToSend = (loc_sendtoNW_t*)uart_app_v.pkBuf;
pkToSend->locSendTo.socketId = loc_getSocketId();
pkToSend->locSendTo.destAddr = DN_MGR_IPV6_MULTICAST_ADDR;
pkToSend->locSendTo.destPort = WKP_USER_1;
pkToSend->locSendTo.serviceType = DN_API_SERVICE_TYPE_BW;
pkToSend->locSendTo.priority = DN_API_PRIORITY_MED;
pkToSend->locSendTo.packetId = 0xFFFF;
					

ヘッダ指定

FFTのヘッダ情報をSmartMeshのペイロードデータに指定します。

for (i=0;i<= XFD_HEADER_SIZE;i++) {
	pkToSend->locSendTo.payload[i] =
				uart_app_v.uartRxBuf[i+12];
}
					

XFD_HEADER_SIZEは36です。UARTから受信したデータポジションは、12からなので、このような記述にしています。

パケット送信と送信確認

SmartMesh側にデータ転送する時には、下記のように実装します。

dnErr = dnm_loc_sendtoCmd(pkToSend, 36, &rc);
	ASSERT(dnErr==DN_ERR_NONE);

// print
if (rc==DN_API_RC_OK) {
	dnm_ucli_printf("packet sent\r\n");
} else {
	dnm_ucli_printf("rc = 0x%02x\r\n",rc);
}
					

rcには、成功したかどうかの情報が入ります。DN_API_RC_OKであれば、送信できたことになります。

ここまでが、振動計測ソリューションとOCSDKを利用したMote側の説明になります。

ManagerのPythonアプリ

これまでV-Managerをベースに実験してきましたが、E-Managerも多く利用されている現状を踏まえ、E-Managerをベースに振動計測ソリューションのFFT抽出データを表示するアプリを構築してみました。

SmartMesh SDKは、Python2.7系

SmartMesh SDKはPython2.7ベースに作られています。GUIベースのものから、コマンドラインベースのものまで、様々なバリエーションが用意されています。

CLIで振動計測データを表示する

SmartMesh SDKのサンプルにある「Simple」フォルダにある、「SimpleIPUpstreamMgr.py」をベースに、Moteから送られてくるデータを表示するアプリを構築しました。

ペイロードデータ

Managerが受信したデータは、def handle_data(notifName, notifParams):関数で確認することができます。この関数で渡されるnotifParamsに、SmartMeshのヘッダ情報および、ペイロードデータが保持されています。そこから必要なデータを抽出します。

Mote情報

Moteの情報は、受信したデータヘッダから抽出します。

print ('Received from mote {0}: '.format('-'.join(['%02x'%b for b in notifParams.macAddress])))
					

受信したMoteのアドレスが表示されます。

経過時間情報

経過時間情報は、ペイロードデータの先頭4バイト分に入っています。

Timestamp_raw = ((notifParams.data[3] * 16777216) + 
	(notifParams.data[2] * 65536) + 
	(notifParams.data[1] * 256) + notifParams.data[0])
TS_ms = Timestamp_raw % 1000
TS_ss = Timestamp_raw / 1000
TS_s = TS_ss % 60
TS_m = TS_ss / 60
TS_h = TS_ss / 3600
					

print ( 'Timestamp : {0:d}H : {1:d}m : {2:d}s : {3:d}ms '.format(TS_h, TS_m, TS_s, TS_ms))で表示しています。

周波数Indexの抽出

周波数Indexは、12ビット表記で送られてきます。そのため、Indexを復号するために、ビット操作による復号をする必要があります。

Index1とIndex2の場合の実装例です。

f_ch0 = ((notifParams.data[9] & 0x0F) << 8) + notifParams.data[8]
f_ch1 = ((notifParams.data[10] << 4) + ((notifParams.data[9] & 0xF0) >> 4))
					

Index1およびIndex2に該当する周波数情報が復元できます。この値から、サンプリング周波数をかけて、FFTのポイント数で割った値が、目的の周波数になります。

print ( 'Peak Index1 Frequency : {0:.3f}KHz'.format((f_ch0 * 102.4)/4096))
print ( 'Peak Index2 Frequency : {0:.3f}KHz'.format((f_ch1 * 102.4)/4096))
					

これで、FFT解析結果の周波数が表示されます。

PeakLevelの抽出

周波数Indexに該当するPeakLevelの復号方法です。PeakLevelは、下記の式にあるIEEE754 半精度浮動小数点を用いてFFTフォーマットのデコードを実装しています。

v_ch0 = (1.0 - 2 * ((notifParams.data[21] & 0x80) >> 7)) * (2 ** ((notifParams.data[21] & 0x7C) >> 2)) / (2**15) * (1024 + (notifParams.data[21] & 0x03) * 256 + notifParams.data[20]) / 1024.0
v_ch1 = (1.0 - 2 * ((notifParams.data[23] & 0x80) >> 7)) * (2 ** ((notifParams.data[23] & 0x7C) >> 2)) / (2**15) * (1024 + (notifParams.data[23] & 0x03) * 256 + notifParams.data[22]) / 1024.0    
					

この結果から、スケールレベルに調整するために、対数を取り、最大スーケルファクタ(ここでは34)を引きます。

print ( 'Peak Level Index1: {0:.4f}dB'.format(20 * np.log10(v_ch0) - 34.0))
print ( 'Peak Level Index2: {0:.4f}dB'.format(20 * np.log10(v_ch1) - 34.0))
					

これで、Indexに相当する周波数のPeakLevelが表示されるようになります。

Python2.7系の注意点

最近のPythonは3系が主流になっています。2.7系との違いも多くあります。特に数値演算では、小数点を入れていなければ整数扱いされてしまいます。

FFTのデータを受信して、表示

図7は、Moteから受信したデータをコマンドラインに表示している様子です。この例では信号発生機から、650Hzと2.6KHzの振動を与え、バンド1で650Hz、バンド2で2.6KHzを検出している様子がわかります。

図図7:振動計測ソリューションからの情報を表示している様子

ダウンロードファイルについて

今回使用したソースコードおよび、ファームウェア、Manager側のPythonスクリプトは、下のリンクからダウンロード可能です。振動計測ソリューションと使用する際、Mote側にESPツールでアプリケーション部分を書き換えて、使用してください。

まとめ

今回は、SmartMeshで実現するIoTエッジコンピューティングを実践してみました。振動計測ソリューションもSmartMeshも省電力で実現できるシステムです。センサーネットワークといえば、小さいデータがメインになりますが、今回のようなエッジサイドでFFTまで計算することで、ホスト側の負荷低減もさることながら、メッシュネットワークのメリットである多ノード対応も容易に実現することができます。今回の実験は、振動計測以外にも応用ができますので、皆さんもチャレンジしてみてください。