PTU2F3 をバグったSDKなんて使わないで直接操作しよう


PTU2F3というUSBから操作できるタップがあります。
開発用SDKとして dll が付属してくるため、プログラムでON/OFFが簡単にできます。

しかし、こいつのSDKがバグっていて、メモリ保護にひっかかりよくクラッシュします。
とりあえず、スイッチは操作できるのですが、メモリ保護で落ちまくられるのも気持ちのいいものではありません。
このバグったSDKを使わないで、直接 PTU2F3 とおしゃべりするソフトを作ってみました。


コンパイルには、ddkmsdnからダウンロードしてくる必要があります。
http://www.usefullcode.net/2009/03/windows_sdkddkwdkmsdn20093.html

バイスオープン

USBデバイスwindowsから操作するには、 CreateFile で USBデバイスを開きます。
ただ、こいつらはふつーのファイルではないので、名前を特定して上げる必要があります。

名前の特定には、ベンダーID とプロダクトIDが必要です。
これらは、デバイスマネージャから調べることができます。

ターゲットのデバイスのプロパティを開いて、ハードウェアタブを選択します。
そこにあるプロパティを開きます。(プロパティのプロパティで話がややこしいんですけど)


詳細タブで、ここにもあるプロパティって名前のコンボボックスから、ハードウェアID を選択します。


そうすると、情報が表示されます。

HID\VID_0711&PID_0028&REV_0201
HID\VID_0711&PID_0028
HID_DEVICE_UP:FF00_U:0001
HID_DEVICE

この、 VID_0711&PID_0028 ってゆーのが重用です。
こいつらは16進数で、ベンダーID とプロダクトIDを表しています。

これにより、PTU2F3は、 ベンダーID 0x711 で、プロダクトID 0x28 ということがわかりました。
この情報を使って、デバイスを開きます。


バイスを開いて、閉じるだけのコードです。

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <setupapi.h>
extern "C"
{
	//ddkを入れてください。
	#include <ddk/hidsdi.h>
}

#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "hid.lib")

//iVendorID iProductID で利用するUSBデバイスを特定し、ハンドルを開きます。
HANDLE HIDOpen(int iVendorID, int iProductID)
{
	GUID hidGuid;
	HANDLE hDevHandle = INVALID_HANDLE_VALUE;
	HDEVINFO hDevInfo;
	HIDD_ATTRIBUTES Attributes;
	PSP_DEVICE_INTERFACE_DETAIL_DATA pspDidd;
	SP_DEVICE_INTERFACE_DATA spDid;

	HidD_GetHidGuid(&hidGuid);
	hDevInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE|DIGCF_ALLCLASSES);
	{
		USHORT i;
		for(i = 0; i < 128;i++){
			memset(&spDid, 0, sizeof(SP_DEVICE_INTERFACE_DATA)); // spDid Clear;
			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);
			pspDidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(dwRequiredLength);
			pspDidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
			if(SetupDiGetDeviceInterfaceDetail(hDevInfo, &spDid, pspDidd, dwRequiredLength, &dwRequiredLength, NULL)){
				hDevHandle = CreateFile(pspDidd->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
				if(hDevHandle != INVALID_HANDLE_VALUE){
					Attributes.Size = sizeof(Attributes);
					if(HidD_GetAttributes(hDevHandle, &Attributes)){
						if(iVendorID == Attributes.VendorID && iProductID == Attributes.ProductID){
							free(pspDidd);
							break;
						}
					}
					CloseHandle(hDevHandle);
				}
			}
			free(pspDidd);
		}
	}
	return hDevHandle;
}

int main()
{
	//デバイスを開きます。
	//ベンダーID   0x711
	//プロダクトID 0x28
	HANDLE handle = HIDOpen(0x711,0x28);
	if (handle == INVALID_HANDLE_VALUE)
	{
		CloseHandle(handle);
		printf("デバイスが見つかりませんでした");
		return -1;
	}

	CloseHandle(handle);
	printf("正常完了");
	return 0;
}

メッセージのやり取り

あまり詳しくはないのですが、USBにはいくつかのメッセージやり取り方法があるらしく、それを行わないといけません。
このへんは詳しい人の解説を読んでくださいw

Feature
HidD_SetFeature
HidD_GetFeature

すぐに応答を返す短いやり取りをします。
PTU2F3 だと、3バイトのメッセージをやり取りしています。

	BYTE featureBuffer[3] = { 0x00, 0xff , 0xff };
	HidD_SetFeature( handle , featureBuffer , 3 );
バルク転送
ReadFile
ReadFileEx
WriteFile
WriteFileEx

大きなデータの転送を行います。
転送できないとブロックして待ちぼうけになるときがあります。
ちゃんと作るなら、タイムアウトなどの処理が必須らしいです。

PTU2F3 だと、9バイトのデータをやり取りしています。

	DWORD updatesize=0;
	BYTE readstatus[9] = {0,1,0,0,0,0,0,0,0};	//ステータスを取得するコード
	BOOL ret = WriteFile( handle , readstatus , 9 , &updatesize , NULL ); 

PTU2F3のプロトコル

ステータス

WriteFile 0,1,0,0,0,0,0,0,0 すると、ステータスを取得できます。
結果は、 ReadFile で読み出せます。

結果の2バイト目に、現在のタップの状態が帰ってきます。
面白いことに、ONになっているスイッチが 0 で、OFF になっているスイッチが 1 で帰ってきます。

0,0,0,0,0,0,0,0,0 すべてON
0,1,0,0,0,0,0,0,0 5だけON
0,4,0,0,0,0,0,0,0 4だけON
0,5,0,0,0,0,0,0,0 すべてOFF
セット

スイッチをONにするには、 0,2,0,0,0,0,0,0,0 という信号を送ります。
3バイト目に、 OFF にしたいスイッチのビットを立てます。
これは、ステータスの信号と対応しています。

0,2,0,0,0,0,0,0,0 すべてONにする
0,2,1,0,0,0,0,0,0 5だけONにする
0,2,4,0,0,0,0,0,0 4だけONにする
0,2,5,0,0,0,0,0,0 すべてOFFにする


タップ全体を 一気に ON/OFF しにいくので、現在のステータスとユーザが望むステータスを確認して、状態を遷移させる必要があります。
まずは、ステータスを取得して、それを見て、ユーザが支持したスイッチのON/OFFをしたら、どういう風になるのかを計算して、スイッチの状態を再度設定にいきます。

ソースコード

//PTU2F3 スイッチを直接制御するコードです


#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <setupapi.h>
extern "C"
{
	//ddkを入れてください。
	#include <ddk/hidsdi.h>
}

#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "hid.lib")

//iVendorID iProductID で利用するUSBデバイスを特定し、ハンドルを開きます。
HANDLE HIDOpen(int iVendorID, int iProductID)
{
	GUID hidGuid;
	HANDLE hDevHandle = INVALID_HANDLE_VALUE;
	HDEVINFO hDevInfo;
	HIDD_ATTRIBUTES Attributes;
	PSP_DEVICE_INTERFACE_DETAIL_DATA pspDidd;
	SP_DEVICE_INTERFACE_DATA spDid;

	HidD_GetHidGuid(&hidGuid);
	hDevInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, DIGCF_PRESENT|DIGCF_DEVICEINTERFACE|DIGCF_ALLCLASSES);
	{
		USHORT i;
		for(i = 0; i < 128;i++){
			memset(&spDid, 0, sizeof(SP_DEVICE_INTERFACE_DATA)); // spDid Clear;
			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);
			pspDidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(dwRequiredLength);
			pspDidd->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
			if(SetupDiGetDeviceInterfaceDetail(hDevInfo, &spDid, pspDidd, dwRequiredLength, &dwRequiredLength, NULL)){
				hDevHandle = CreateFile(pspDidd->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
				if(hDevHandle != INVALID_HANDLE_VALUE){
					Attributes.Size = sizeof(Attributes);
					if(HidD_GetAttributes(hDevHandle, &Attributes)){
						if(iVendorID == Attributes.VendorID && iProductID == Attributes.ProductID){
							free(pspDidd);
							break;
						}
					}
					CloseHandle(hDevHandle);
				}
			}
			free(pspDidd);
		}
	}
	return hDevHandle;
}

//ステータスを読む
bool ReadStatus(HANDLE handle ,BYTE status[9] )
{
	//メッセージを送る(なぜかなくても動くw )
	BYTE featureBuffer[3] = { 0x00, 0xff , 0xff };
	HidD_SetFeature( handle , featureBuffer , 3 );

	//バルク転送で読むらしい

	//ステータス頂戴!!!
	DWORD updatesize=0;
	BYTE readstatus[9] = {0,1,0,0,0,0,0,0,0};	//ステータスを取得するコード
	BOOL ret = WriteFile( handle , readstatus , 9 , &updatesize , NULL ); 
	if (!ret)
	{
		printf("can not write!");
		return false;
	}

	memset(status,0,sizeof(BYTE) * 9 );
	ret = ReadFile( handle , status , 9 , &updatesize , NULL ); 
	if (!ret)
	{
		printf("can not read!");
		return false;
	}
	return true;
}

//ステータスを読む
bool UpdateSwitch(HANDLE handle ,int switchid,bool onoff )
{
	//ステータスを読む
	BYTE status[9];
	if ( ! ReadStatus(handle,status) )
	{
		printf("ステータスの取得に失敗しました");
		return false;
	}
	BYTE code;
	if (status[1] == 5) //alloff
	{
		code = 3;
	}
	else if (status[1] == 4) //4 only
	{
		code = 2;
	}
	else if (status[1] == 1) //5 only
	{
		code = 1;
	}
	else if (status[1] == 0) //allon
	{
		code = 0;
	}
	else
	{
		printf("未知のステータス %x を受診しました" , status[1]);
		return false;
	}

	if (switchid == 4)
	{
		if (onoff) 
		{
			code = code & ~0x01;
		}
		else
		{
			code = code | 0x01;
		}
	}
	else if (switchid == 5)
	{
		if (onoff) 
		{
			code = code & ~0x02;
		}
		else
		{
			code = code | 0x02;
		}
	}
	else
	{
		printf("未知のボタン %d が指定されました" , switchid);
		return false;
	}


	//メッセージを送る(なぜかなくても動くw )
	BYTE featureBuffer[3] = { 0x00, 0xff , 0xff };
	HidD_SetFeature( handle , featureBuffer , 3 );


	//ステータスを入れ込みます。
	//3バイト目に消すスイッチを1にしてフラグをたてます
	DWORD updatesize=0;
	BYTE readstatus[9] = {0,2,0,0,0,0,0,0,0};	
//	BYTE setallon[9]   = {0,2,0,0,0,0,0,0,0};
//	BYTE setalloff[9]  = {0,2,3,0,0,0,0,0,0};
	readstatus[2] = code;

	BOOL ret = WriteFile( handle , readstatus , 9 , &updatesize , NULL ); 
	if (!ret)
	{
		printf("can not write!");
		return false;
	}

	return true;
}


int main()
{
	HANDLE handle = HIDOpen(0x711,0x28);
	if (handle == INVALID_HANDLE_VALUE)
	{
		CloseHandle(handle);
		printf("デバイスが見つかりませんでした");
		return -1;
	}
	//todo ココを変えてね
	if ( ! UpdateSwitch(handle, 4 , true) )
	{
		CloseHandle(handle);
		printf("ステータス更新に失敗しました");
		return -2;
	}

	CloseHandle(handle);
	printf("正常完了");
	return 0;
}