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;
