フューチャーホームコントローラーを支えなかった技術 windows/linuxクロス利用ができるUSB操作ルーチン
フューチャーホームコントローラーの技術解説を書いてます。
フューチャーホームコントローラーは、声でしゃべるだけで様々な家電を操作できる未来的ガジェットです。(写真の手前にあるのは名刺のです。)
http://rti-giken.jp/
今回は、フューチャーホームコントローラー用に作ったのに、ハードウェアの制約で使えなかったボツルーチンについて解説して行きたいと思います。
USBコントロール
フューチャーホームコントローラーは当初の構想ではUSBハブを積んで、そのUSBハブの先にある機材もjavascriptで操作できるようにしようと考えていました。
ですが、USBハブにつなげる機材によっては、電力不足に成ることがわかり、この構想は断念しました。
ただ、断念するまでは作る気マンマンだったわけで、
USB機器をwindows/linux両方で操作できるようにしたラッパークラスを作りました。
ちゃんと動作します。
今回は、陽の目をひることがなかった、
windowsとlinux両方で使える 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ミサイルランチャーとかを操作して、「コンピュータ、ファイエル!!」とかいってスポンジロケット飛ばせたのに、残念なところです。
フューチャーホームコントローラーは、とてつもない試行錯誤の上にできているということがわかっていただけると嬉しいです。