HID(ヒューマン インターフェイス デバイス)からデータを取得する ~Delphiでお手軽プログラミング
HID一覧取得、HIDからデータを取得するスレッドのユニット(UHIDThread)
Hid.pasはjvclを利用、SetupApi.pasはインターネットから取得して使用しました。unit UHIDThread; interface uses Winapi.Windows, System.SysUtils, System.Classes; type THIDdeviceInfo = Record SymLink : String; BufferSize : Word; Handle : THandle; VID : Word; PID : Word; VersionNumber : Word; ManufacturerString : String; ProductString : String; SerialNumberString : String; end; THIDDeviceList = Array of THIDdeviceInfo; THIDbuffer = Array[0..63] of Byte; THIDcallBack = Procedure(Data:THIDbuffer);stdcall; THIDreport = Record ReportID:Byte; Data :THIDbuffer; end; THIDthread = Class(TThread) private FActiveDevice:THIDdeviceInfo; procedure setActiveDevice(ADevice:THIDdeviceInfo); procedure HIDCloseDevice(); function HIDReadDevice(var Data:THIDbuffer): Boolean; public HIDcallback: THIDcallBack; Constructor Create; destructor Destroy; override; procedure Execute; override; procedure HIDOpenDevice(); property ActiveDevice:THIDdeviceInfo read FActiveDevice write setActiveDevice; end; //function GetHidDeviceInfo(Symlink:PChar):THIDdeviceInfo; function GetHidDeviceList():THIDDeviceList; implementation uses Hid, SetupApi; function GetHidDeviceInfo(Symlink:PChar):THIDdeviceInfo; var pStr :pWideChar; PreparsedData:PHIDPPreparsedData; HidCaps :THIDPCaps; DevHandle :THandle; HidAttrs :THIDDAttributes; ret:LongBool; const HIDUSB_COUNTOFINTERRUPTBUFFERS = 64; begin ZeroMemory(@Result,SizeOf(THIDdeviceInfo)); Result.SymLink := SymLink ; GetMem(pStr, 1024); DevHandle := CreateFileW( Symlink, GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); If (DevHandle <> INVALID_HANDLE_VALUE) then begin If HidD_GetAttributes(DevHandle, HidAttrs) then begin Result.VID :=HidAttrs.VendorID; Result.PID :=HidAttrs.ProductID; Result.VersionNumber:=HidAttrs.VersionNumber; end; If HidD_GetManufacturerString(DevHandle, pStr, 1024) then Result.ManufacturerString := String(pStr) else Result.ManufacturerString := ''; If HidD_GetProductString(DevHandle, pStr, 1024) then Result.ProductString := String(pStr) else Result.ProductString := ''; If HidD_GetSerialNumberString(DevHandle, pStr, 1024) then Result.SerialNumberString := String(pStr) else Result.SerialNumberString := ''; HidD_SetNumInputBuffers(DevHandle, HIDUSB_COUNTOFINTERRUPTBUFFERS); ret:=HidD_GetPreparsedData(DevHandle, PreparsedData); If ret and (PreparsedData<>nil) then begin HidP_GetCaps(PreparsedData, HidCaps); Result.BufferSize := HidCaps.OutputReportByteLength; HidD_FreePreparsedData(PreparsedData); end else Result.BufferSize := 0; CloseHandle(DevHandle); end; FreeMem(pStr); end; function GetHidDeviceList():THIDDeviceList; var HID_GUID :TGUID; spdid :TSPDeviceInterfaceData; pSpDidd :PSPDeviceInterfaceDetailDataW; spddd :TSPDevInfoData; HidInfo :HDEVINFO; DevCnt :Integer; dwSize :DWord; Info :THIDdeviceInfo; DeviceList :THIDDeviceList; begin HidD_GetHidGuid(HID_GUID); HIDinfo := SetupDiGetClassDevsW( @HID_GUID, nil, {self.handle} 0, DIGCF_DEVICEINTERFACE or DIGCF_PRESENT); if (THandle(HIDinfo)<>INVALID_HANDLE_VALUE) then begin DevCnt := 0; spdid.cbSize := SizeOf(spdid); while SetupDiEnumDeviceInterfaces( HidInfo, nil, HID_GUID, DevCnt, spdid) do begin setlength(DeviceList,DevCnt+1); //ClearDeviceInfo(DeviceList[DevID]); ZeroMemory(@DeviceList[DevCnt],SizeOf(THIDdeviceInfo)); dwSize := 0; SetupDiGetDeviceInterfaceDetailW( HIDinfo, @spdid, nil, 0, @dwSize, nil); If (dwSize > 0) then begin GetMem(pSpDidd, dwSize); pSpDidd.cbSize := SizeOf(TSPDeviceInterfaceDetailDataW); spddd.cbSize := SizeOf(spddd); If SetupDiGetDeviceInterfaceDetailW( HIDinfo, @spdid, pSpDidd, dwSize, nil, @spddd) then begin ZeroMemory(@Info,SizeOf(THIDdeviceInfo)); Info := GetHidDeviceInfo(@(pSpDidd^.DevicePath)); Info.Handle := INVALID_HANDLE_VALUE; DeviceList[DevCnt]:=Info; end; FreeMem(pSpDidd); end; inc(DevCnt); end; SetupDiDestroyDeviceInfoList(HidInfo); end; Result:=DeviceList; end; Constructor THIDthread.Create; begin inherited Create(False); //プライオリティは適宜調整 Priority := tpTimeCritical; FreeOnTerminate := True; FActiveDevice.Handle:=INVALID_HANDLE_VALUE; end; procedure THIDthread.HIDCloseDevice; begin if FActiveDevice.Handle<>INVALID_HANDLE_VALUE then begin CloseHandle(ActiveDevice.Handle); FActiveDevice.Handle:=INVALID_HANDLE_VALUE; end; end; procedure THIDthread.HIDOpenDevice(); begin HIDCloseDevice; FActiveDevice.Handle := CreateFileW( PChar(ActiveDevice.SymLink), GENERIC_READ or GENERIC_WRITE or winapi.windows.SYNCHRONIZE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 ); end; function THIDthread.HIDReadDevice(var Data: THIDbuffer): Boolean; var hEv :THandle; HidOverlapped :TOverlapped; bResult :DWord; BytesRead :DWord; ReadBuf :THIDreport; begin Result := False; if (FActiveDevice.Handle <> INVALID_HANDLE_VALUE) then begin hEv := CreateEvent(nil, true, false, nil); HidOverlapped.hEvent := hEv; HidOverlapped.Offset := 0; HidOverlapped.OffsetHigh:= 0; ZeroMemory(@(ReadBuf.Data[0]),length(ReadBuf.Data)); ReadFile(FActiveDevice.Handle, ReadBuf, FActiveDevice.BufferSize, BytesRead, @HidOverlapped); bResult:=WaitForSingleObject(hEv, 0); if (bResult = WAIT_TIMEOUT) or (bResult = WAIT_ABANDONED) then begin CancelIo(ActiveDevice.Handle); Result := False; end else begin GetOverlappedResult(FActiveDevice.Handle, HidOverlapped, BytesRead, False); Data := ReadBuf.Data; Result := True; end; CloseHandle(hEv); end; end; procedure THIDthread.setActiveDevice(ADevice: THIDdeviceInfo); begin HIDCloseDevice; FActiveDevice:=ADevice; end; destructor THIDthread.Destroy; begin HIDCloseDevice; inherited; end; Procedure THIDthread.Execute; var buf: THIDbuffer; begin while not Terminated do begin If (FActiveDevice.Handle <> INVALID_HANDLE_VALUE) then begin If Assigned(HIDcallback) then begin ZeroMemory(@buf[0],SizeOf(buf)); if HIDReadDevice(buf) then HIDcallback(buf); Sleep(1); end else Sleep(10); end else Sleep(10); end; HIDCloseDevice; end; initialization begin LoadHid(); LoadSetupApi(); end; finalization begin UnloadHid; UnloadSetupApi; end; end.
UHIDThreadユニットを使用してHIDからデータを取得する
サンワ サプライ 心拍センサー付きブルーLEDマウスがHIDを使用している。HID経由で心拍データを取得する。参考URL https://www.sanwa.co.jp/seihin_joho/mouse_hr/index.html
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, UHIDThread; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; ListBox1: TListBox; Memo1: TMemo; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private 宣言 } DeviceList : THIDDeviceList; HIDthread : THIDthread; public { Public 宣言 } end; var Form1: TForm1; procedure HIDDataRead(Data:THIDbuffer); stdcall; implementation {$R *.dfm} uses WinAPI.MMSystem; procedure HIDDataRead(Data:THIDbuffer); stdcall; var st:String; i:Integer; begin st:=''; for i := 0 to Length(Data)-1 do st:=st+IntToHex(Data[i],2); Form1.Memo1.Lines.Add(st); end; procedure TForm1.Button1Click(Sender: TObject); var i:integer; begin DeviceList:=GetHidDeviceList(); ListBox1.Clear; for i := 0 to Length(DeviceList)-1 do begin ListBox1.AddItem( Format('%2d',[i])+':'+ DeviceList[i].ProductString+'('+ DeviceList[i].ManufacturerString+')' , nil ); end; end; procedure TForm1.Button2Click(Sender: TObject); begin if ListBox1.ItemIndex < 0 then exit; HIDthread.ActiveDevice:=DeviceList[ListBox1.ItemIndex]; HIDthread.HIDOpenDevice; end; procedure TForm1.FormCreate(Sender: TObject); begin HIDthread:=THIDthread.Create; HIDthread.HIDcallback:=HIDDataRead; end; procedure TForm1.FormDestroy(Sender: TObject); begin HIDthread.Terminate; end; end.
実行画面
Health MouseというHIDデバイス名のデータを取得。RAWデータにハイパスフィルタとローパスフィルタを透過させる
HIDから取り出したデータはノイズが含まれているので、フィルターでノイズを除く。procedure HIDReader(Data:THIDbuffer); stdcall; var st:String; i,ct,ctr:Integer; tmp:THeart; begin if Data[$00]=$0D then begin if Data[$0C]=$80 then begin ctr:=length(htr); setlength(htr ,ctr+1); if ctr=0 then HtBaseTime:=timeGetTime(); htr[ctr].tm :=timeGetTime()-HtBaseTime; htr[ctr].ht :=Data[$0D] shl 8 + Data[$0E]; Form1.Series1.AddXY( htr[ctr].tm, htr[ctr].ht ); //ハイパスフィルター(HPF) ha:=0.3; if ctr > 0 then begin if ctr=1 then begin hat1:=trunc(htr[ctr-1].ht*(1-ha)+htr[ctr].ht*ha); hat2:=hat1; ha1:=htr[ctr].ht-hat1; end else begin hat1:=trunc(hat2*(1-ha)+htr[ctr].ht*ha); hat2:=hat1; ha2:=ha1; ha1:=htr[ctr].ht-hat1; Form1.Series2.AddXY(htr[ctr].tm, ha1); end; end; //ローパスフィルター(LPF) la:=0.3; if ctr > 1 then begin if ctr=2 then begin la1:=trunc(ha2*(1-la)+ha1*la); la2:=la1; end else begin la1:=trunc(la2*(1-la)+ha1*la); la2:=la1; Form1.Series3.AddXY(htr[ctr].tm, la1); end; end; for i := 0 to Form1.Series1.Count-100 do Form1.Series1.Delete(0); for i := 0 to Form1.Series2.Count-100 do Form1.Series2.Delete(0); for i := 0 to Form1.Series3.Count-100 do Form1.Series3.Delete(0); end; end; end;
RAWデータにハイパスフィルタとローパスフィルタを透過させる その2
上記と違って、移動平均を使って低ノイズと高ノイズを取り除く。procedure HIDReader(Data:THIDbuffer); stdcall; var i,ctr:Integer; tmp:THeart; begin if Data[$00]=$0D then begin if Data[$0C]=$80 then begin ctr:=length(htr); setlength(htr ,ctr+1); if ctr=0 then HtBaseTime:=timeGetTime(); htr[ctr].tm :=timeGetTime()-HtBaseTime; htr[ctr].ht :=Data[$0D] shl 8 + Data[$0E]; Form1.Series1.AddXY( htr[ctr].tm, htr[ctr].ht ); //ハイパスフィルタとして移動平均を計算する if ctr>1 then begin if ctr=2 then begin hat1:=(htr[ctr-2].ht+htr[ctr-1].ht+htr[ctr].ht) div 3; ha1:=htr[ctr-1].ht-hat1; hat2:=hat1; ha2:=ha1; end else if ctr=3 then begin hat1:=(hat2+htr[ctr-1].ht+htr[ctr].ht) div 3; ha1:=htr[ctr-1].ht-hat1; hat3:=hat2; ha3:=ha2; hat2:=hat1; ha2:=ha1; end else begin hat1:=(hat3+hat2+htr[ctr].ht) div 3; ha1:=htr[ctr-1].ht-hat1; Form1.Series2.AddXY(htr[ctr].tm, ha1); hat4:=hat3; ha4:=ha3; hat3:=hat2; ha3:=ha2; hat2:=hat1; ha2:=ha1; end; end; //ローパスフィルタとして移動平均を計算する if ctr>3 then begin if ctr=2 then begin la1:=(ha4+ha3+ha2) div 3; la2:=la1; end else if ctr=3 then begin la1:=(la2+ha3+ha2) div 3; la3:=la2; la2:=la1; end else begin la1:=(la3+la2+ha2) div 3; Form1.Series3.AddXY(htr[ctr].tm, la1); la3:=la2; la2:=la1; end; end; end; end; end;