フューチャーホームコントローラーを支えなかった技術 windows/linuxクロス利用ができるUSB操作ルーチン


フューチャーホームコントローラーの技術解説を書いてます。
フューチャーホームコントローラーは、声でしゃべるだけで様々な家電を操作できる未来的ガジェットです。(写真の手前にあるのは名刺のです。)
http://rti-giken.jp/


今回は、フューチャーホームコントローラー用に作ったのに、ハードウェアの制約で使えなかったボツルーチンについて解説して行きたいと思います。

USBコントロール

フューチャーホームコントローラーは当初の構想ではUSBハブを積んで、そのUSBハブの先にある機材もjavascriptで操作できるようにしようと考えていました。
ですが、USBハブにつなげる機材によっては、電力不足に成ることがわかり、この構想は断念しました。

ただ、断念するまでは作る気マンマンだったわけで、
USB機器をwindows/linux両方で操作できるようにしたラッパークラスを作りました。
ちゃんと動作します。

今回は、陽の目をひることがなかった、
windowslinux両方で使える USBラッパークラスを紹介したいと思います。


windowsの場合。とくに特になにもしないでも使えます。やるじゃんゲイツ
linuxの場合、 libusbが必要です。なんだよリヌース。
(windowsのlibusbもありますが、動作がいまいち謎で安定しなかったため、スクラッチしてまつ)


解説はソースコードのあとに。

ソースコード

//ヘッダの方
//MITライセンスでライセンスされています
#if _MSC_VER
	#include "XLLoadLibraryHelper.h"
#else
struct usb_bus;
struct usb_device;
struct usb_dev_handle;
#endif

class XLUSBHIDDevice
{
public:
	XLUSBHIDDevice();
	virtual ~XLUSBHIDDevice() ;

	//iVendorID iProductID で利用するUSBデバイスを特定し、ハンドルを開きます。
	void Open(int iVendorID, int iProductID) ;

	void SetFeature(unsigned char* data,int len) ;
	void GetFeature(unsigned char* data,int len) ;

	int Write(unsigned char* data,int len,unsigned int timeoutMS) ;
	int Read(unsigned char* data,int len,unsigned int timeoutMS) ;

private:
#if _MSC_VER
		HANDLE Handle;
		XLLoadLibraryHelper HibLib;

		typedef struct _HIDD_ATTRIBUTES {
			ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)

			//
			// Vendor ids of this hid device
			//
			USHORT  VendorID;
			USHORT  ProductID;
			USHORT  VersionNumber;

			//
			// Additional fields will be added to the end of this structure.
			//
		} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;

		typedef void (__stdcall *_HidD_GetHidGuidDef) (OUT LPGUID   HidGuid);
		typedef BOOLEAN (__stdcall *_HidD_GetAttributesDef) (IN  HANDLE              HidDeviceObject,OUT PHIDD_ATTRIBUTES    Attributes);
		typedef BOOLEAN (__stdcall *_HidD_GetFeatureDef) (IN    HANDLE   HidDeviceObject, OUT   PVOID    ReportBuffer, IN    ULONG    ReportBufferLength);
		typedef BOOLEAN (__stdcall *_HidD_SetFeatureDef) (IN    HANDLE   HidDeviceObject, IN    PVOID    ReportBuffer, IN    ULONG    ReportBufferLength);

		_HidD_GetHidGuidDef _HidD_GetHidGuid;
		_HidD_GetAttributesDef _HidD_GetAttributes;
		_HidD_GetFeatureDef _HidD_GetFeature;
		_HidD_SetFeatureDef _HidD_SetFeature;
	
		
		

#else   
		struct usb_bus *Bus;
		struct usb_device *Dev;
		struct usb_dev_handle *Handle;

		unsigned int ReadEndPoint;
		unsigned int WriteEndPoint;
		unsigned int ReadEndPointMaxWriteSize;
		unsigned int WriteEndPointMaxWriteSize;
#endif

};


//cppの方

//MITライセンスでライセンスされています
#include "common.h"
#include "XLUSBHIDDevice.h"

#if _MSC_VER
	#include <setupapi.h>
	#pragma comment(lib, "setupapi.lib")

#else
	//参考
	//http://kentai-shiroma.blogspot.jp/2012/06/usbptu2f3linux.html?showComment=1351979073666#c8439821432021535316
	#include <usb.h>
#endif

XLUSBHIDDevice::XLUSBHIDDevice()
{
#if _MSC_VER
	this->Handle = NULL;

	this->HibLib.Load("hid.dll");
	this->_HidD_GetHidGuid = (_HidD_GetHidGuidDef) this->HibLib.GetProcAddress("HidD_GetHidGuid");
	this->_HidD_GetAttributes = (_HidD_GetAttributesDef)this->HibLib.GetProcAddress("HidD_GetAttributes");
	this->_HidD_GetFeature = (_HidD_GetFeatureDef)this->HibLib.GetProcAddress("HidD_GetFeature");
	this->_HidD_SetFeature = (_HidD_SetFeatureDef)this->HibLib.GetProcAddress("HidD_SetFeature");
#else
	this->Bus = NULL;
	this->Dev = NULL;
	this->Handle = NULL;

	this->ReadEndPoint = 0;
	this->WriteEndPoint = 0;
	this->ReadEndPointMaxWriteSize = 0;
	this->WriteEndPointMaxWriteSize = 0;
#endif
}
XLUSBHIDDevice::~XLUSBHIDDevice()
{
#if _MSC_VER
	if (this->Handle)
	{
		CloseHandle(this->Handle);
	}
#else
	if (this->Handle)
	{
		usb_release_interface(this->Handle, 0);
		usb_close(this->Handle);
	}
#endif
}


//iVendorID iProductID で利用するUSBデバイスを特定し、ハンドルを開きます。
void XLUSBHIDDevice::Open(int iVendorID, int iProductID)
{
#if _MSC_VER
	GUID hidGuid;
	this->_HidD_GetHidGuid(&hidGuid);
	 
	HDEVINFO hDevInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
	if (hDevInfo == INVALID_HANDLE_VALUE )
	{
		throw XLException("SetupDiGetClassDevsに失敗");
	}

	for(unsigned short i = 0; i < 128;i++)
	{
		SP_DEVICE_INTERFACE_DATA spDid = { 0 } ;
		spDid.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

		if(!SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &hidGuid, i, &spDid)) continue;
		DWORD dwRequiredLength = 0;
		SetupDiGetDeviceInterfaceDetail(hDevInfo, &spDid, NULL, 0, &dwRequiredLength, NULL);

		std::vector<char> buffer(dwRequiredLength);
		PSP_DEVICE_INTERFACE_DETAIL_DATA pspDidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)&buffer[0];
		pspDidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
		if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spDid, pspDidd, dwRequiredLength, &dwRequiredLength, NULL))
		{
			continue;
		}
		//0x00916b74 "\\?\hid#vid_0711&pid_0028#8&24d77392&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"
		const char * vid = strstr(pspDidd->DevicePath,"vid_");
		if (vid == NULL)
		{
			continue;
		}
		vid += sizeof("vid_") - 1;  
		const char * pid = strstr(vid,"pid_");
		if (pid == NULL)
		{
			continue;
		}
		pid += sizeof("pid_") - 1;  

		const char * type = strstr(pid,"#");
		if (type == NULL)
		{
			continue;
		}
		type += sizeof("#") - 1;  

		char* e;
		int vecndorID = strtoul( vid ,&e , 16); 
		int productID = strtoul( pid ,&e , 16); 
//		int typeID = strtoul( type ,&e , 16); 

		if ( vecndorID != iVendorID || productID != iProductID)
		{
			continue;
		}

		HANDLE hDevHandle = CreateFile(pspDidd->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
		if(hDevHandle == INVALID_HANDLE_VALUE)
		{
			continue;
		}

		//見つかった!
		this->Handle = hDevHandle;
		break;
	}

	if (this->Handle == NULL )
	{
		throw XLException("USB デバイスがありません VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}

#else
	//準備
	usb_init();
	usb_find_busses();
	usb_find_devices();
	this->Bus = usb_get_busses();

	//USBデバイスを検索します
	for(auto bus=this->Bus; bus; bus=bus->next)
	{
		for(auto dev=bus->devices; dev; dev=dev->next) 
		{
			if( (dev->descriptor.idVendor==iVendorID) && (dev->descriptor.idProduct==iProductID) )
			{
				this->Dev = dev;
				break;
			}
		}
	}
	if (!this->Dev)
	{
		throw XLException("USB デバイスがありません VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}

	//デバイスを開きます
	this->Handle = usb_open(this->Dev);
	if(! this->Handle )
	{
		throw XLException(std::string() + "usb_openに失敗:" + usb_strerror() + " VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}

	//オマジナイの儀式
	if( usb_set_configuration(this->Handle,this->Dev->config->bConfigurationValue)<0 )
	{
		if( usb_detach_kernel_driver_np(this->Handle,this->Dev->config->interface->altsetting->bInterfaceNumber)<0 )
		{
//			fprintf(stderr,"usb_set_configuration Error.\n");
//			fprintf(stderr,"usb_detach_kernel_driver_np Error.(%s)\n",usb_strerror());
			throw XLException(std::string() + "usb_set_configuration失敗後のusb_detach_kernel_driver_npに失敗:" + usb_strerror() + " VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
		}
	}

	if( usb_claim_interface(this->Handle,this->Dev->config->interface->altsetting->bInterfaceNumber)<0 )
	{
		if( usb_detach_kernel_driver_np(this->Handle,this->Dev->config->interface->altsetting->bInterfaceNumber)<0 )
		{
//			fprintf(stderr,"usb_claim_interface Error.\n");
//			fprintf(stderr,"usb_detach_kernel_driver_np Error.(%s)\n",usb_strerror());
			throw XLException(std::string() + "usb_claim_interface失敗後のusb_detach_kernel_driver_npに失敗:" + usb_strerror() + " VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
		}
	}

	if( usb_claim_interface(this->Handle,this->Dev->config->interface->altsetting->bInterfaceNumber)<0 )
	{
//		fprintf(stderr,"usb_claim_interface Error.(%s)\n",usb_strerror());
		throw XLException(std::string() + "usb_claim_interfaceに失敗:" + usb_strerror() + " VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}
	
	//エントリーポイントを検索します 複数あったら最後にあったやつをとりあえず採用するw
	auto config = this->Dev->config;
	for (unsigned int i = 0 ; i < config->bNumInterfaces ; ++ i)
	{
		auto inter = &(config->interface[i]);
		for(unsigned int j = 0; j < inter->num_altsetting; j++) {
			auto interdesc = &inter->altsetting[j];

			for(int k = 0; k < (int) interdesc->bNumEndpoints; k++) {
				auto endpoint = &interdesc->endpoint[k];

				unsigned int wmax = endpoint->wMaxPacketSize; //le16 little endianらしい?
				unsigned int maxsize = wmax & 0x7ff;  //一回に読み書き込める最大値

				//see
				//https://github.com/gregkh/usbutils/blob/master/lsusb.c
				//http://subversion.assembla.com/svn/cJmcWSaLSr3OBaeJe5aVNr/comport_linux_dcu60.cpp
				if (endpoint->bEndpointAddress & 0x80)
				{//読み込み IN
					this->ReadEndPoint = endpoint->bEndpointAddress ;
					this->ReadEndPointMaxWriteSize = maxsize;
				}
				else
				{//書き込み OUT
					this->WriteEndPoint = endpoint->bEndpointAddress ;
					this->WriteEndPointMaxWriteSize = maxsize;
				}
			}
		}
	}
	
	printf("read %d %d\r\nwrite %d %d\r\n",this->ReadEndPoint,this->ReadEndPointMaxWriteSize,this->WriteEndPoint,this->WriteEndPointMaxWriteSize);
	if (this->ReadEndPointMaxWriteSize <= 0)
	{
		throw XLException("ReadEndPointがないです VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}
	if (this->WriteEndPointMaxWriteSize <= 0)
	{
		throw XLException("WriteEndPointがないです VendorID:" + num2str(iVendorID) + "ProductID:" + num2str(iProductID));
	}
#endif

}

// HID Class-Specific Requests values. See section 7.2 of the HID specifications 
#define HID_GET_REPORT                0x01 
#define HID_GET_IDLE                  0x02 
#define HID_GET_PROTOCOL              0x03 
#define HID_SET_REPORT                0x09 
#define HID_SET_IDLE                  0x0A 
#define HID_SET_PROTOCOL              0x0B 
#define HID_REPORT_TYPE_INPUT         0x01 
#define HID_REPORT_TYPE_OUTPUT        0x02 
#define HID_REPORT_TYPE_FEATURE       0x03 

#define CTRL_IN                (LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE)
#define CTRL_OUT		       (LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE)

void XLUSBHIDDevice::SetFeature(unsigned char* data,int len) 
{
#if _MSC_VER
	_HidD_SetFeature(this->Handle,data,len);
#else
//	int ret = usb_control_transfer(this->Handle,CTRL_OUT ,HID_GET_REPORT,(HID_REPORT_TYPE_FEATURE<<8)|0x00,0, data,len, 100000); 
//	if (ret < 0)
//	{
//		throw XLException("libusb_control_transfer書き込みエラー:"+num2str(ret));
//	}
#endif
}

void XLUSBHIDDevice::GetFeature(unsigned char* data,int len) 
{
#if _MSC_VER
	_HidD_GetFeature(this->Handle,data,len);
#else
//	int ret = usb_control_transfer(this->Handle,CTRL_IN,HID_SET_REPORT,(HID_REPORT_TYPE_FEATURE<<8)|0x00, 0,data,len, 100000);  
//	if (ret < 0)
//	{
//		throw XLException("libusb_control_transfer読み込みエラー:"+num2str(ret));
//	}
#endif
}



int XLUSBHIDDevice::Write(unsigned char* data,int len,unsigned int timeoutMS) 
{
#if _MSC_VER
	OVERLAPPED ol = {0};
	ol.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

	DWORD r ;
	if (! WriteFile(this->Handle,data,len,&r,&ol) )
	{
		DWORD lasterror = GetLastError();
		if (lasterror != ERROR_IO_PENDING)
		{
			CloseHandle(ol.hEvent);
			throw XLException("WriteFileに失敗 "+ XLException::StringWindows(lasterror)  );
		}
		WaitForSingleObject(ol.hEvent,timeoutMS);
		if (!GetOverlappedResult( this->Handle, &ol, &r, FALSE ))
		{//timeout
			CancelIoEx(this->Handle,&ol);
			CloseHandle(ol.hEvent);
			throw XLException("WriteFileに失敗 タイムアウト");
		}
	}

	CloseHandle(ol.hEvent);
	return r;
#else
	int ret=usb_bulk_write(this->Handle, this->WriteEndPoint, (char*)data, len, timeoutMS);
	if (ret < 0)
	{
		throw XLException("usb_bulk_writeに失敗:"+num2str(ret) + " :"+ usb_strerror() );
	}
	return ret;
#endif
}

int XLUSBHIDDevice::Read(unsigned char* data,int len,unsigned int timeoutMS) 
{
#if _MSC_VER
	OVERLAPPED ol = {0};
	ol.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

	DWORD r ;
	if (! ReadFile(this->Handle,data,len,&r,&ol) )
	{
		DWORD lasterror = GetLastError();
		if (lasterror != ERROR_IO_PENDING)
		{
			CloseHandle(ol.hEvent);
			throw XLException("ReadFileに失敗" + XLException::StringWindows(lasterror) );
		}
		WaitForSingleObject(ol.hEvent,timeoutMS);
		if (!GetOverlappedResult( this->Handle, &ol, &r, FALSE ))
		{//timeout
			CancelIoEx(this->Handle,&ol);
			CloseHandle(ol.hEvent);
			throw XLException("ReadFileに失敗 timeout" );
		}
	}

	CloseHandle(ol.hEvent);
	return r;
#else
	int ret=usb_bulk_read(this->Handle, this->ReadEndPoint,(char*) data, len, timeoutMS);
	if (ret < 0)
	{
		throw XLException("usb_bulk_readに失敗:"+num2str(ret) + " :" + usb_strerror() );
	}
	return ret;
#endif
}

使い方

使い方・・・と、いっても、公開されているメソッドが7つしかないですね。
うち2つは、コンストラクタとデストラクタなので実質5個です。

XLUSBHIDDevice();
virtual ~XLUSBHIDDevice() ;

//iVendorID iProductID で利用するUSBデバイスを特定し、ハンドルを開きます。
void Open(int iVendorID, int iProductID) ;

void SetFeature(unsigned char* data,int len) ;
void GetFeature(unsigned char* data,int len) ;

int Write(unsigned char* data,int len,unsigned int timeoutMS) ;
int Read(unsigned char* data,int len,unsigned int timeoutMS) ;

Openでデバイスを開いて、
Write / Read で通信します。
Feature(Message)を送りたい時には、 SetFeature / GetFeature を使います。

Openでデバイスどーやって開くのさ?って人は、
私が昔書いた記事を参考にしてください。
PTU2F3 をバグったSDKなんて使わないで直接操作しよう


これだけで、windowsでも、linuxでも USB機器を操作できるようになります。

もし、これをFHCに搭載できていれば、
USBミサイルランチャーとかを操作して、「コンピュータ、ファイエル!!」とかいってスポンジロケット飛ばせたのに、残念なところです。

フューチャーホームコントローラーは、とてつもない試行錯誤の上にできているということがわかっていただけると嬉しいです。