前回はJNIの基本部分を実装し、JavaからC言語のネイティブコードを実行するところまで確認できました。今回は実際にセンサを制御する処理をJavaから呼び出すようにしていきます。

サンプルソースを使った下準備

まず、 hiddata.c、hiddata.h の二つのファイルを 、前回作成したlib_usb_Hiddata.c と同じディレクトリにコピーしてください。本記事の場合は、コピー元の二つのファイルは/home/root/iws600cm-0.1.0/に存在し、コピー先のディレクトリは/home/root/になります。

$ cp iws600cm-0.1.0/hiddata.c ./
$ cp iws600cm-0.1.0/hiddata.h ./

続いて、 lib_usb_Hiddata.cを編集していきます。まずソース冒頭の#includeに “hiddata.h”を追加し、更にhiddata.cで参照している各種ヘッダファイルも追加します。必要なヘッダファイルは下記の通りです。

#include "lib_usb_Hiddata.h"
#include <stdio.h>
#include "hiddata.h"
#include <string.h>
#include <usb.h>
…

続いて、lib_usb_Hiddata.cの末尾に、hiddata.cの#include以降の内容(12行目以降)を貼り付けます。

…
JNIEXPORT jint JNICALL Java_lib_usb_Hiddata_usbhidGetReport
(JNIEnv *env, jobject thisj, jint device, jint reportID, jbyteArray buffer, jint len)
{
	printf("Called usbhidGetReport\n");
	return 0;
}

//ここまでが現在のlib_usb_Hiddata.cの内容。
//以降が、hiddata.cの12行目以降からコピーした内容

#define usbDevice   usb_dev_handle  /* use libusb's device structure */

/* ------------------------------------------------------------------------- */

#define USBRQ_HID_GET_REPORT    0x01
#define USBRQ_HID_SET_REPORT    0x09

#define USB_HID_REPORT_TYPE_FEATURE 3


static int  usesReportIDs;

/* ------------------------------------------------------------------------- */

static int usbhidGetStringAscii(usb_dev_handle *dev, int index, char *buf, int buflen)
{
	char    buffer[256];
	int     rval, i;

	if ((rval = usb_get_string_simple(dev, index, buf, buflen)) >= 0) /* use libusb version if it works */
		return rval;
	if ((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, 0x0409, buffer, sizeof(buffer), 5000)) < 0)
		return rval;
…

後は、JNI用に作成した4つの関数について、hiddata.cの関数とつなげる処理を書いていきます。JNIの引数による配列の受け渡しについては、下記のweb記事を参考にさせていただきました。
http://tsukaayapontan.web.fc2.com/doc/jni/jni_04.html

各関数の実装

Java_lib_usb_Hiddata_usbhidOpenDevice関数

Java_lib_usb_Hiddata_usbhidOpenDevice関数は、hiddata.c のusbhidOpenDevice 関数とのつなぎ込みを行います。元の関数からint vendor(ベンダID)、int product(プロダクトID)のみ引数に残したので、後の部分はNULL及び0を代入します。また、取得するハンドルの格納ポインタは、関数内のローカル変数でusbDevice_t *devを定義してそこに対して取得します。

usbhidOpenDevice関数は戻り値が0なら成功・0以外なら失敗なので、そこで処理を分岐し、成功した場合はポインタのアドレスをintにキャストしたものを戻り値にします。

JNIEXPORT jint JNICALL Java_lib_usb_Hiddata_usbhidOpenDevice
(JNIEnv *env, jobject thisj, jint vendor, jint product)
{

	usbDevice_t     *dev = NULL;

	if (usbhidOpenDevice(&dev, vendor, NULL, product, NULL, NULL, 0) != 0) {
		return 0;
	}

	return (int)dev;
}

Java_lib_usb_Hiddata_usbhidCloseDevice関数

Java_lib_usb_Hiddata_usbhidCloseDevice関数は、Hiddata.c のusbhidCloseDevice関数とのつなぎ込みを行います。特に難しいところは無く、引数にint型で与えられたハンドルのアドレスをポインタにキャストして渡します。

JNIEXPORT void JNICALL Java_lib_usb_Hiddata_usbhidCloseDevice
(JNIEnv *env, jobject thisj, jint device)
{
	usbhidCloseDevice((usbDevice_t*) device);
}

Java_lib_usb_Hiddata_usbhidSetReport関数

Java_lib_usb_Hiddata_usbhidSetReport関数は、Hiddata.cのusbhidSetReport関数とのつなぎ込みを行います。JNIでは、引数に与えられたbyte配列を読み書きするためにGetArrayLengthやGetByteArrayElementsなどの関数を使います。前者で引数のバッファサイズを取得し、後者でバッファの実体に対するポインタを取得します。そしてchar*で確保した同等サイズのバッファに引数の内容をコピーしてusbhidSetReport関数を実行します。
usbhidSetReportの関数の戻り値をそのままこの関数の戻り値としますが、先に確保したメモリを忘れずに解放してからreturnします。

JNIEXPORT jint JNICALL Java_lib_usb_Hiddata_usbhidSetReport
(JNIEnv *env, jobject thisj, jint device, jbyteArray buffer, jint len)
{
	int returnValue;
	int i;
	jsize size = (*env)->GetArrayLength( env, buffer);
	jbyte *buf = (*env)->GetByteArrayElements(env, buffer, 0);
	char *sendbuf = malloc(size);

	for (i = 0; i < size; i++) sendbuf[i]= buf[i];

	returnValue = usbhidSetReport((usbDevice_t*)device, sendbuf,len);

	free(sendbuf);

	return returnValue;
}

Java_lib_usb_Hiddata_usbhidGetReport関数

Java_lib_usb_Hiddata_usbhidGetReport関数は、Hiddata.cのusbhidGetReport関数とのつなぎ込みを行います。先ほどと同様に、あらかじめ必要サイズ分の受信バッファを確保してusbhidGetReport関数を実行し、関数に成功したら、引数に与えられたbufferに受信内容をコピーします。引数に与えられた配列の内容を変更した場合、ReleaseByteArrayElements関数を実行して変更を適用する必要があります。
最後に、先に確保したメモリを忘れずに解放してからreturnします。

JNIEXPORT jint JNICALL Java_lib_usb_Hiddata_usbhidGetReport
(JNIEnv *env, jobject thisj, jint device, jint reportID, jbyteArray buffer, jint len)
{
	int returnValue;
	jbyte *buf = (*env)->GetByteArrayElements(env, buffer, 0);
	int _len = len;
	char *recvbuf = malloc(_len);
	int i;


	returnValue = usbhidGetReport((usbDevice_t*)device, reportID, recvbuf, &_len);


	if (returnValue==0) {
		for (i = 0; i < _len; i++) buf[i] = recvbuf[i];
	}

	(*env)->ReleaseByteArrayElements(env, buffer,buf, 0);
	free(recvbuf);

	return returnValue;
}

以上の内容を作成したら、C言語のソースをビルドします。ビルド方法は前回とほぼ同様ですが、-lusbを追加してlibusbの参照設定を行います。ビルドエラーが発生する場合は、誤字・脱字や、必要なファイルが正しい箇所に存在するかなどを確認してください。

$ gcc -shared -fPIC -I /home/vstone/java/jdk1.8.0_40/include/ -I /home/vstone/java/jdk1.8.0_40/include/linux/  lib_usb_Hiddata.c -lusb -o libHiddata.so

テストプログラムの修正

共用ライブラリがビルドできたら、テストプログラムから実際にセンサを扱ってみます。前回作成したHiddata.javaのpublic static void main(String[] args)メソッドを、 「センサを開く」「値を取得する」「センサを解放する」という流れに直してみます。

デバイスハンドル(int)や受信バッファ(byte[])などを準備し、引数も正しいものに修正します。また、戻り値が設定されたメソッドは、全て成功=0、失敗=0以外なので、これを元にエラーチェックも行います。

元のサンプルコードでは、センサ値の取得の際は要素数17のbyte配列をセンサから読み込み、前から2番目の数値をセンサ値として表示しているので、Javaのソースでも受信バッファにbyte[17]を確保して、取得したバッファの2番目の値を表示させています。

ここまでの内容を具体的に記述すると下記の通りです。

	public static void main(String[] args) {
		System.loadLibrary("Hiddata");

		try{
			Hiddata hiddata = new Hiddata();
			int dev = hiddata.usbhidOpenDevice(0x16c0,0x05df);
			if(dev==0){
				System.out.println("Sensor not found.");
			}
			else{
				System.out.println("Sensor found.");
				for(int i=0;i<30;i++){
					byte[] buffer = new byte[17];
					if(hiddata.usbhidGetReport(dev,0,buffer ,buffer.length)==0){
						 System.out.println("value:"+buffer[1]);
					}
					else System.out.println("Sensor read error.");

					Thread.sleep(500);
				}
				hiddata.usbhidCloseDevice(dev);
			}

		} catch(InterruptedException e){
			e.printStackTrace();
		} 
	}

Javaのソースを作成したら、前回の説明と同じ手順で、javacでコンパイルしjavaで実行します。実行時にはSotaにセンサを忘れずにつないでください。

$ javac lib/usb/Hiddata.java
$ java -Djava.library.path=./ lib.usb.Hiddata
修正したテストプログラムを実行すると、前々回のC言語のサンプルと同様に画面にセンサの情報が逐次表示される

プログラムを正しく実行できると、画面に人感センサのセンサ値が表示されます。表示される数値は、センサが反応すると2、反応しないと0です。

ここまで進められれば、あと一歩でVstoneMagicからもセンサを制御できるようになります。と言うわけで、次回はVstoneMagicと連携して使ってみたいと思います。

今回のサンプルコードは以下のリポジトリに公開しています。ライセンスがGPL2なので、ご利用の際にはご注意ください。 https://github.com/vstoneofficial/HumanSensor_Sota/releases/tag/HumanSensor_3rd

このリポジトリは、本連載全体のソースコードです。今回のソースを取得する場合は、上記URLより「Source code (zip)」をクリックすれば、記事に合ったzip形式のソースをダウンロードできます。