Mam's WebSite
Delphiでお手軽プログラミング

トップページ⇒DelphiでHIDからデータを取得する

HID(ヒューマン インターフェイス デバイス)からデータを取得する


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;

Mam's WebSite