HID USB Relay

Developed or developing a new product in PureBasic? Tell the world about it.
hippy
User
User
Posts: 28
Joined: Tue Mar 05, 2013 3:11 pm

HID USB Relay

Post by hippy »

Bought a cheapo $5 USB Relay device from Ebay. It's a HID device, pretty straight forward.
"1 Channel 5V USB Relay Module USB Computer Control USB Relay Switch Board"

Description:
- The module uses HID technology, foreign trade quality, without any driver, download the data with its own application, plug and play.
- Support WIN7, XP 32-bit, 64-bit system, easy to use.
- On-board square USB interface, stable connection.
- Using special relay driver chip ULN2803, relay work more stable.
- Military-level PCB production. (HAHA!)
Wrote some quick and dirty Sunday afternoon code for it...

Code: Select all

EnableExplicit

; Generic USB HID Relay for Win32 - See Ebay
; reports as "dcttech.com"
; only tested on 1 channel unit.
; TODO: multi-unit / multi-relay support
; TODO: serial number setting / getting
; TODO: Modularize with a better API

; see inspiration on https://github.com/darrylb123/usbrelay
;                    http://vusb.wikidot.com/project:driver-less-usb-relays-hid-interface

XIncludeFile "hid_module.pbi"  ; found in google with no credits sorry :( ... posted below, 

#RELAY_ON=$ff
#RELAY_OFF=$fd
#RELAY_CMD_SET_SERIAL=$fa

Global *relaydevice

Procedure Operate_Relay(*hid_device, relay.a, state.a);{
  Protected res.l                                      ;  
  Protected *b = AllocateMemory(9)
  PokeB(*b, $00)
  PokeB(*b+1, state)
  PokeB(*b+2, relay)  
  res = hid::SetFeature(*hid_device, *b, 9)
  If (res < 0)
    Debug ("Unable to WriteDevice() Error: " + Str(res));
  EndIf
  ProcedureReturn res;
EndProcedure


Procedure Init_Relays(vendor_id.l = $16c0, product_id.l = $05df)
  Protected relay_device_available 
  Protected Test=HID::TestDevice(product_id, vendor_id)
  If Test 
    HID::CloseDevice(*relaydevice)
    *relaydevice=HID::OpenDevice(product_id, vendor_id, -1, 0)
    Debug "Relay: Device Opened"
    Debug "Manufacturer: "+hid::GetManufacturerString(*relaydevice)
    Debug "Product: "+hid::GetProductString(*relaydevice)
    Debug "Serial: "+hid::GetSerialNumberString(*relaydevice)
    relay_device_available = 1
  Else
    Debug "Relay: No Device"     
    relay_device_available = 0
  EndIf
  ProcedureReturn relay_device_available
EndProcedure


Procedure Done_Relay(*relaydevice)
  hid::CloseDevice(*relaydevice)  
EndProcedure


HID::HID_Init()
If Init_Relays()

; simple test toggles relay 1 at 1hz.
Repeat
  Sleep_(1000)
  Operate_Relay(*relaydevice,1,#RELAY_ON)  ; not zero indexed, wasted some time trying relay 0 :)
  Sleep_(1000)
  Operate_Relay(*relaydevice,1,#RELAY_OFF)
ForEver

Else 
  Debug "No device found."
EndIf


And the required HID Module...
Tried to translate some of the strings from Russian, with strange results.

Code: Select all


DeclareModule HID

;-DeclareModule

Structure HID_CAPS
  Usage.w
  UsagePage.w
  InputReportByteLength.w
  OutputReportByteLength.w
  FeatureReportByteLength.w
  Reserved.w[17]
  NumberLinkCollectionNodes.w
  NumberInputButtonCaps.w
  NumberInputValueCaps.w
  NumberInputDataIndices.w
  NumberOutputButtonCaps.w
  NumberOutputValueCaps.w
  NumberOutputDataIndices.w
  NumberFeatureButtonCaps.w
  NumberFeatureValueCaps.w
  NumberFeatureDataIndices.w
EndStructure

Structure HID_Sub_DeviceInfo
  VendorID.u
  ProductID.u
  VersionNumber.u
  NumInputBuffers.u
  InputReportByteLength.u
  OutputReportByteLength.u
  FeatureReportByteLength.u
  Manufacturer.s
  Product.s
  SerialNumber.s
EndStructure

Structure HID_DeviceInfo
  CountDevice.w              ; The number of the HID devices
  DeviceInfo.HID_Sub_DeviceInfo[258]
EndStructure


Structure HID_Attributes
  VID.u
  PID.u
  VersionNumber.u
EndStructure


Declare HID_Init()
Declare HID_End()
Declare OpenDevice(PID.u, VID.u, VersionNumber.w=-1, Index.u=0)
Declare CloseDevice(hDevice)
Declare TestDevice(PID.u, VID.u, VersionNumber.w=-1, Index.u=0)
Declare DeviceInfo(*Info.HID_DeviceInfo)
Declare ReadDevice(hDevice, *Buffer, Len)
Declare WriteDevice(hDevice, *Buffer, Len)
Declare GetFeature(hDevice, *Buffer, Len)
Declare SetFeature(hDevice, *Buffer, Len)
Declare GetInputReport(hDevice, *Buffer, Len)
Declare SetOutputReport(hDevice, *Buffer, Len)
Declare GetCaps(hDevice, *Capabilities.HID_CAPS)
 ; Assignment for the lack of facilities, the PID, the VID and the number of the universe.
Declare GetAttributes(hDevice, *DeviceInfo.HID_Attributes)
Declare GetNumInputBuffers(hDevice)
Declare.s GetManufacturerString(hDevice)
Declare.s GetProductString(hDevice)
Declare.s GetSerialNumberString(hDevice)
Declare.s GetIndexedString(hDevice, Index)

EndDeclareModule




Module HID


Structure HIDD_ATTRIBUTES
  Size.l
  VendorID.u
  ProductID.u
  VersionNumber.w
EndStructure

Structure PSP_DEVICE_INTERFACE_DETAIL_DATA
  cbSize.l
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    DevicePath.l
  CompilerElse 
    DevicePath.c
  CompilerEndIf
EndStructure

CompilerIf Defined(SP_DEVICE_INTERFACE_DATA, #PB_Structure)=0
  Structure SP_DEVICE_INTERFACE_DATA
    cbSize.l
    InterfaceClassGuid.GUID
    Flags.l
    Reserved.l
  EndStructure
CompilerEndIf

EnableExplicit
Define.i

Prototype pGetHidGuid(*HidGuid.GUID)
Prototype pGetAttributes(*HidDeviceObject, *Attributes.HIDD_ATTRIBUTES)
Prototype pGetPreparsedData(*HidDeviceObject, *PreparsedData)
Prototype pGetCaps(*PreparsedData, *Capabilities.HID_CAPS)
Prototype pFreePreparsedData(PreparsedData)
Prototype pGetNumInputBuffers(HidHandle, *NumInputBuffers)
Prototype pGetManufacturerString(HidHandle, *Buffer, Len)
Prototype pGetProductString(HidHandle, *Buffer, Len)
Prototype pGetSerialNumberString(HidHandle, *Buffer, Len)
Prototype pGetIndexedString(HidHandle, Index, *Buffer, Len)
Prototype pGetFeature(HidHandle, *Buffer, Len)
Prototype pSetFeature(HidHandle, *Buffer, Len)
Prototype pGetInputReport(HidHandle, *Buffer, Len)
Prototype pSetOutputReport(HidHandle, *Buffer, Len)
Prototype pSetupDiEnumDeviceInterfaces(*DeviceInfoSet, DeviceInfoData, *InterfaceClassGuid.GUID,
                                       MemberIndex, *DeviceInterfaceData.SP_DEVICE_INTERFACE_DATA)
Prototype pSetupDiGetDeviceInterfaceDetail(*DeviceInfoSet, *DeviceInterfaceData.SP_DEVICE_INTERFACE_DATA,
                                           DeviceInterfaceDetailData, DeviceInterfaceDetailDataSize, 
                                           *RequiredSize, *DeviceInfoData)

Define hid_Lib, Setupapi_Lib, OS_Version

Procedure HID_Init()
  Shared hid_Lib, Setupapi_Lib, OS_Version
  Static __HID_INIT = 0
  
  If __HID_INIT
    ProcedureReturn ; run once only
  Else    
    __HID_INIT = 1
  EndIf
  
  OS_Version = OSVersion()
  
  hid_Lib=LoadLibrary_("hid.dll")
  Setupapi_Lib=LoadLibrary_("setupapi.dll")
  
  Global fGetHidGuid.pGetHidGuid=GetProcAddress_(hid_Lib, ?GetHidGuid)
  Global fGetAttributes.pGetAttributes=GetProcAddress_(hid_Lib, ?GetAttributes)
  Global fGetPreparsedData.pGetPreparsedData=GetProcAddress_(hid_Lib, ?GetPreparsedData)
  Global fFreePreparsedData.pFreePreparsedData=GetProcAddress_(hid_Lib, ?FreePreparsedData)
  Global fGetCaps.pGetCaps=GetProcAddress_(hid_Lib, ?GetCaps)
  Global fGetInputReport.pGetInputReport=GetProcAddress_(hid_Lib, ?GetInputReport)
  Global fSetOutputReport.pSetOutputReport=GetProcAddress_(hid_Lib, ?SetOutputReport)
  Global fGetFeature.pGetFeature=GetProcAddress_(hid_Lib, ?GetFeature)
  Global fSetFeature.pSetFeature=GetProcAddress_(hid_Lib, ?SetFeature)
  Global fGetNumInputBuffers.pGetNumInputBuffers=GetProcAddress_(hid_Lib, ?GetNumInputBuffers)
  Global fGetManufacturerString.pGetManufacturerString=GetProcAddress_(hid_Lib, ?GetManufacturerString)
  Global fGetProductString.pGetProductString=GetProcAddress_(hid_Lib, ?GetProductString)
  Global fGetSerialNumberString.pGetSerialNumberString=GetProcAddress_(hid_Lib, ?GetSerialNumberString)
  Global fGetIndexedString.pGetIndexedString=GetProcAddress_(hid_Lib, ?GetIndexedString)
  Global fSetupDiEnumDeviceInterfaces.pSetupDiEnumDeviceInterfaces=GetProcAddress_(Setupapi_Lib,
                                                                                   ?SetupDiEnumDeviceInterfaces)
  Global fSetupDiGetDeviceInterfaceDetail.pSetupDiGetDeviceInterfaceDetail=GetProcAddress_(Setupapi_Lib,
                                                                                           ?Setup_InterfaceDetail)
  
  DataSection
    
    GetHidGuid:
    ! db "HidD_GetHidGuid", 0, 0
    
    GetAttributes:
    ! db "HidD_GetAttributes", 0, 0
    
    GetPreparsedData:
    ! db "HidD_GetPreparsedData", 0, 0
    
    FreePreparsedData:
    ! db "HidD_FreePreparsedData", 0, 0
    
    GetCaps:
    ! db "HidP_GetCaps", 0, 0
    
    GetInputReport:
    ! db "HidD_GetInputReport", 0, 0
    
    SetOutputReport:
    ! db "HidD_SetOutputReport", 0, 0
    
    GetFeature:
    ! db "HidD_GetFeature", 0, 0
    
    SetFeature:
    ! db "HidD_SetFeature", 0, 0
    
    GetNumInputBuffers:
    ! db "HidD_GetNumInputBuffers", 0, 0
    
    GetManufacturerString:
    ! db "HidD_GetManufacturerString", 0, 0
    
    GetProductString:
    ! db "HidD_GetProductString", 0, 0
    
    GetSerialNumberString:
    ! db "HidD_GetSerialNumberString", 0, 0
    
    GetIndexedString:
    ! db "HidD_GetIndexedString", 0, 0
    
    SetupDiEnumDeviceInterfaces:
    ! db "SetupDiEnumDeviceInterfaces", 0, 0
    
    Setup_InterfaceDetail:
    CompilerIf #PB_Compiler_Unicode=0
      ! db "SetupDiGetDeviceInterfaceDetailA", 0, 0
    CompilerElse
      ! db "SetupDiGetDeviceInterfaceDetailW", 0, 0
    CompilerEndIf
    
  EndDataSection
  
EndProcedure


Procedure HID_End()
  Shared hid_Lib, Setupapi_Lib
  If hid_Lib
    FreeLibrary_(hid_Lib)
  EndIf
  If Setupapi_Lib
    FreeLibrary_(Setupapi_Lib)
  EndIf
EndProcedure


Procedure FunctInfo(hDevice, *Info.HID_DeviceInfo)
  Protected Attributes.HIDD_ATTRIBUTES, i
  Protected HIDP_CAPS.HID_CAPS
    
  Attributes\Size = SizeOf(HIDD_ATTRIBUTES)
  
  If fGetAttributes(hDevice, @Attributes)
    
    i = *Info\CountDevice
    *Info\CountDevice+1
    
    *Info\DeviceInfo[i]\VendorID        = Attributes\VendorID
    *Info\DeviceInfo[i]\ProductID       = Attributes\ProductID
    *Info\DeviceInfo[i]\VersionNumber   = Attributes\VersionNumber
    
    *Info\DeviceInfo[i]\Manufacturer    = GetManufacturerString(hDevice)
    *Info\DeviceInfo[i]\Product         = GetProductString(hDevice)
    *Info\DeviceInfo[i]\SerialNumber    = GetSerialNumberString(hDevice)
    *Info\DeviceInfo[i]\NumInputBuffers = GetNumInputBuffers(hDevice)
    GetCaps(hDevice, @HIDP_CAPS)
    *Info\DeviceInfo[i]\InputReportByteLength   = HIDP_CAPS\InputReportByteLength
    *Info\DeviceInfo[i]\OutputReportByteLength  = HIDP_CAPS\OutputReportByteLength
    *Info\DeviceInfo[i]\FeatureReportByteLength = HIDP_CAPS\FeatureReportByteLength
    
  EndIf
  
EndProcedure

; Preparing the HID for the installation
Procedure Open_HID_Device(PID.u, VID.u, VersionNumber.w=-1, Index.u=0, *Funct=0, *Info.HID_DeviceInfo=0)
  Protected HidGuid.Guid
  Protected devInfoData.SP_DEVICE_INTERFACE_DATA
  Protected Attributes.HIDD_ATTRIBUTES, Security.SECURITY_ATTRIBUTES
  Protected *detailData.PSP_DEVICE_INTERFACE_DETAIL_DATA
  Protected Length.l, CurrentIndex.w, hDevInfo
  Protected i, Result, DevicePath.s
  Protected Required, hDevice
  
  If fGetHidGuid=0 Or fSetupDiEnumDeviceInterfaces=0 Or
     fSetupDiGetDeviceInterfaceDetail=0 Or fGetAttributes=0
    ProcedureReturn 0
  EndIf
  
  devInfoData\cbSize = SizeOf(SP_DEVICE_INTERFACE_DATA)
  
  Security\nLength=SizeOf(SECURITY_ATTRIBUTES)
  Security\bInheritHandle=1
  Security\lpSecurityDescriptor = 0
  
  fGetHidGuid(@HidGuid)
  
  hDevInfo=SetupDiGetClassDevs_(@HidGuid,0,0, #DIGCF_PRESENT|#DIGCF_DEVICEINTERFACE)
  If hDevInfo=0
    ProcedureReturn 0
  EndIf
  
  
  For i=0 To 255
    
    Result=fSetupDiEnumDeviceInterfaces(hDevInfo, 0, @HidGuid, i, @devInfoData)
    If Result
      Result = fSetupDiGetDeviceInterfaceDetail(hDevInfo, @devInfoData, 0, 0,@Length, 0)
      *detailData=AllocateMemory(Length)
      *detailData\cbSize=SizeOf(PSP_DEVICE_INTERFACE_DETAIL_DATA)
      Result = fSetupDiGetDeviceInterfaceDetail(hDevInfo, @devInfoData, *detailData, Length+1, @Required, 0)
      
      DevicePath.s=PeekS(@*detailData\DevicePath)
      FreeMemory(*detailData)
      
      hDevice=CreateFile_(@DevicePath, #GENERIC_READ|#GENERIC_WRITE,
                          #FILE_SHARE_READ|#FILE_SHARE_WRITE, @Security, #OPEN_EXISTING, 0, 0)
      
      If hDevice<>#INVALID_HANDLE_VALUE
        
        If *Funct And *Info
          
          CallFunctionFast(*Funct, hDevice, *Info)
          CloseHandle_(hDevice)
          
        Else
          
          Attributes\Size = SizeOf(HIDD_ATTRIBUTES)
          Result = fGetAttributes(hDevice, @Attributes)
          
          If Attributes\ProductID=PID And Attributes\VendorID=VID And 
             (Attributes\VersionNumber=VersionNumber Or VersionNumber=-1 ) 
            If CurrentIndex=Index
              SetupDiDestroyDeviceInfoList_(hDevInfo)
              ProcedureReturn hDevice
            Else
              CurrentIndex+1
              CloseHandle_(hDevice)
            EndIf 
          Else
            CloseHandle_(hDevice)
          EndIf 
          
        EndIf
        
      EndIf
    Else
      Break 
    EndIf
  Next i
  
  SetupDiDestroyDeviceInfoList_(hDevInfo)
  ProcedureReturn 0
EndProcedure

Procedure OpenDevice(PID.u, VID.u, VersionNumber.w=-1, Index.u=0)
  ProcedureReturn Open_HID_Device(PID, VID, VersionNumber, Index, 0, 0)
EndProcedure

Procedure CloseDevice(hDevice) ; Çàêðûòèå HID óñòðîéñòâà.
  If hDevice
    ProcedureReturn CloseHandle_(hDevice)
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure TestDevice(PID.u, VID.u, VersionNumber.w=-1, Index.u=0)
  Protected hHid.i, Result
  
  hHid=OpenDevice(PID, VID, VersionNumber, Index)
  If hHid
    CloseDevice(hHid)
    Result=#True
  Else
    Result=#False
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure DeviceInfo(*Info.HID_DeviceInfo)
  If *Info
    ClearStructure(*Info, HID_DeviceInfo)
    Open_HID_Device(0, 0, 0, 0, @FunctInfo(), *Info)
    ProcedureReturn Bool(*Info\CountDevice>0)
  EndIf
EndProcedure

Procedure ReadDevice(hDevice, *Buffer, Len) ; ×òåíèå äàííûõ èç HID óñòðîéñòâà
  Protected Written.l=0
  
  If hDevice=0 Or *Buffer=0 Or Len<=0
    ProcedureReturn 0
  EndIf
  
  ReadFile_(hDevice, *Buffer, Len, @Written, 0)
  ProcedureReturn Written
EndProcedure

Procedure WriteDevice(hDevice, *Buffer, Len) ; Çàïèñü äàííûõ â HID óñòðîéñòâî
  Protected Written.l=0
  
  If hDevice=0 Or *Buffer=0 Or Len<=0
    ProcedureReturn 0
  EndIf
  
  WriteFile_(hDevice, *Buffer, Len, @Written,  0)
  ProcedureReturn Written
EndProcedure


Procedure GetFeature(hDevice, *Buffer, Len)
  If hDevice And *Buffer And Len>0 And fGetFeature
    ProcedureReturn fGetFeature(hDevice, *Buffer, Len)
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure SetFeature(hDevice, *Buffer, Len)
  If hDevice And *Buffer And Len>0 And fSetFeature
    ProcedureReturn fSetFeature(hDevice, *Buffer, Len)
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

 ; Consumption of a fresh fire. THE WINDOWS PLEASE TAKE PARTNERSHIP   ?
Procedure GetInputReport(hDevice, *Buffer, Len)
  Shared OS_Version
  If hDevice And *buffer And Len>0 And
     OS_Version>=#PB_OS_Windows_XP And fGetInputReport
    ProcedureReturn fGetInputReport(hDevice, *Buffer, Len)
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

 ; Çàïèñü â óñòðîéñòâî âûõîäíîãî ðåïîðòà. ÂÍÈÌÀÍÈÅ ôóíêöèÿ ïîÿâèëàñü òîëüêî â WinXP
Procedure SetOutputReport(hDevice, *Buffer, Len)
  Shared OS_Version
  If hDevice And *buffer And Len>0 And
     OS_Version>=#PB_OS_Windows_XP And fSetOutputReport
    ProcedureReturn fSetOutputReport(hDevice, *Buffer, Len)
  Else
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure GetCaps(hDevice, *Capabilities.HID_CAPS) ; Óçíà¸ì ðàçìåðû áóôåðîâ è äð. èíôîðìàöèþ îá óñòðîéñòâå
  Protected result=0, PreparsedData
  If hDevice And fGetPreparsedData And
     fGetCaps And fFreePreparsedData
    
    If fGetPreparsedData(hDevice, @PreparsedData) 
      fGetCaps(PreparsedData, *Capabilities) 
      fFreePreparsedData(PreparsedData)
      result=1
    EndIf  
    
  EndIf
  ProcedureReturn result
EndProcedure

 ; Óçíà¸ì ïî õåíäëó óñòðîéñòâà, åãî PID, VID è íîìåð âåðñèè.
Procedure GetAttributes(hDevice, *DeviceInfo.HID_Attributes)
  Protected Info.HIDD_ATTRIBUTES, Result=#False
  
  If hDevice And *DeviceInfo And fGetAttributes
    Info\Size = SizeOf(HIDD_ATTRIBUTES)
    If fGetAttributes(hDevice, @Info)
      CopyMemory(@Info+OffsetOf(HIDD_ATTRIBUTES\VendorID), *DeviceInfo, SizeOf(HID_Attributes))
      Result=#True
    EndIf
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure GetNumInputBuffers(hDevice)
  Protected NumInputBuffers=0
  If hDevice And fGetNumInputBuffers
    fGetNumInputBuffers(hDevice, @NumInputBuffers)
  EndIf
  ProcedureReturn NumInputBuffers
EndProcedure

Procedure.s GetManufacturerString(hDevice) ; Ïîëó÷àåì èäåíòèôèêàòîð èçãîòîâèòåëÿ
  Protected Result.s="", *mem
  If hDevice And fGetManufacturerString
    *mem=AllocateMemory(256)
    If *mem
      If fGetManufacturerString(hDevice, *mem, 252)
        Result=PeekS(*mem,-1,#PB_Unicode)
      EndIf
      FreeMemory(*mem)
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure.s GetProductString(hDevice) ; Ïîëó÷àåì èäåíòèôèêàòîð ïðîäóêòà
  Protected Result.s="", *mem
  If hDevice And fGetProductString
    *mem=AllocateMemory(256)
    If *mem
      If fGetProductString(hDevice, *mem, 252)
        Result=PeekS(*mem,-1,#PB_Unicode)
      EndIf
      FreeMemory(*mem)
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure.s GetSerialNumberString(hDevice) ; The beginning of the serial number of the project
  Protected Result.s="", *mem
  If hDevice And fGetSerialNumberString
    *mem=AllocateMemory(256)
    If *mem
      If fGetSerialNumberString(hDevice, *mem, 252)
        Result=PeekS(*mem,-1,#PB_Unicode)
      EndIf
      FreeMemory(*mem)
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure.s GetIndexedString(hDevice, Index) ; The differences between the scenes and the source of the structure
  Protected Result.s="", *mem
  If hDevice And fGetIndexedString
    *mem=AllocateMemory(256)
    If *mem
      If fGetIndexedString(hDevice, Index, *mem, 252)
        Result=PeekS(*mem,-1,#PB_Unicode)
      EndIf
      FreeMemory(*mem)
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure

EndModule
vwidmer
Enthusiast
Enthusiast
Posts: 282
Joined: Mon Jan 20, 2014 6:32 pm

Re: HID USB Relay

Post by vwidmer »

Very nice thanks for posting.

Also take a look at this lib from infratec maybe help a bit it is also very nice:

viewtopic.php?f=12&t=71365
WARNING: I dont know what I am doing! I just put stuff here and there and sometimes like magic it works. So please improve on my code and post your changes so I can learn more. TIA
Post Reply