Skip to content

Instantly share code, notes, and snippets.

@mqu
Last active November 16, 2015 07:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mqu/6351df5f8918ec6b0dab to your computer and use it in GitHub Desktop.
Save mqu/6351df5f8918ec6b0dab to your computer and use it in GitHub Desktop.
an HIDAPI interface, using FFI, for Ruby language, to Delcom button.

Delcom-FFI library

this is delcom-ffi.rb, a library interface to Delcom button/light using HIDAPI based on FFI.

Using Delcom library - hight level fonctions

This library is build in an all-in-one file for easy of use. You can split it by class. This library is know to work with push-button light family, especialy modele 706562

initializing button object instance

# require "Delcom.rb" # if needed

# use "lsusb| grep -i delcom" on linux to locate device ID for your hardware.
vendor_id = 0xfc5
product_id = 0xb080
serial_number = 0

button=DelcomButton.new vendor_id, product_id, serial_number

Signal.trap("INT") {
	button.shutdown
	exit
}

# some code
# ....

button.mainloop

control lights

button.led(:all, :off);     # switching all ligth off
button.led(:blue, :on);     # switching blue light on
button.led(:red,  :blink);  # make red light blink

button events

reading events from HIDAPI with FFI is not thread safe ; main ruby thread will be blocked. So create an secondary thread to get events from Delcom button. Main thread will loop in a sleeping thread.

# create a thread to avoid IO blocking (in hid::read), blocking all ruby script, so we can interrupt with CTRL-C
thread = Thread.new do
	button.input_handler=HIDInputHandler.new button   # receive events from HIDAPI / port reading.
	button.on_button_event { |args|
		puts "## :on_button_event::button event " + args.to_s
	}
	button.mainloop
end

# main thread
while true do sleep 100 ; end  # main thread doing nothing and waiting for CTRL-C

selecting input handler

you can read events from xinput or from HIDAPI. So just after having created button object, you can chose wich backend you want to use (HIDInputHandler or XinputHandler):

vendor_id = 0xfc5
product_id = 0xb080
serial_number = 0

button=DelcomButton.new vendor_id, product_id, serial_number

# chose one
button.input_handler=HIDInputHandler.new button   # receive events from HIDAPI / port reading.
# button.input_handler=XinputHandler.new

button.on_button_event { |args|
	puts "## :on_button_event::button event " + args.to_s
}
io.mainloop

note : you won't be able to use Xinput backend and to control LED (with HIDAPI). Theses 2 modes are exclusives.

auto-reconnect feature

This library support disconnexion and reconnexion of USB device. To work properly, you need to configure UDEV rules (see bellow)

Thread::abort_on_exception = true  # abort all threads on exception from IO error
button.auto_reconnect              # activate autoreconnect feature

puts "# reading HID USB port for ever ; trying to auto-reconnect usb device."

begin
	while true
		thread = Thread.new do
			while true 
				infos=button.read len=8, timeout=nil
				if infos[:error]
					sleep 1
				else
					pp infos
				end
			end
		end
		while true do sleep 100 ; end  # main thread doing nothing and waiting for CTRL-C
	end
	
rescue
	sleep 1
	retry
end

udev rules

add following rules to UDEV and restart udev service.

root@:/etc/udev/rules.d# cat /etc/udev/rules.d/85-delcom.rules 
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fc5", ATTRS{idProduct}=="b080", ACTION=="add", SYMLINK+="input/delcom", MODE="0666", GROUP="your-group"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0fc5", ATTRS{idProduct}=="b080", ACTION=="add", SYMLINK+="input/delcom%n", MODE="0666", GROUP="your-group"
  • this will create 2 devices : /dev/input/delcom and /dev/input/delcomXX where XX is a digital number.
  • these 2 devices will be removed when USB device is disconnected.

connecting to MQTT

MQTT is the defacto standard for light weight bus protocol (home automation, Internet of things, embedded systems) with small code foot print. MQTT is based on a client server schema. Clients can publish message on server (called broker) at a given address called topic. Any number of clients can subscribe to topic to get notified of message publication.

Topics are strings, usually organised like file system hierarchy, also called path. You can use willcards (+,#) to subscribe to multiple topic ; ex /org/sub/network/+/device/usb/input/delcom will subscribe to any Delcom button connected to MQTT software BUS.

Ruby-MQTT implementation is available in a GEM repository (https://github.com/njh/ruby-mqtt). You can install MQTT library with this command : sudo gem install mqtt ; you can create a package using checkinstall or gem2deb command (Debian, Ubuntu).

You can join server using mosquitto cli command : mosquitto_sub -h $hostname -t "/path/to/your/topic"

require 'rubygems'
require 'mqtt'

server=`hostname`.chomp
topic="/org/sub/network/#{server}/device/usb/button/delcom"
puts "topic=#{topic}"
mqtt=MQTT::Client.connect(server)

# create a thread to avoid IO blocking (in hid::read), blocking all ruby script, so we can interrupt with CTRL-C
thread = Thread.new do
	button.input_handler=HIDInputHandler.new button   # receive events from HIDAPI / port reading.
	button.on_button_event { |args|
		puts "## :on_button_event::button event " + args.to_s
		mqtt.publish(topic, args.to_s)
	}
	button.mainloop
end

# main thread
while true do sleep 100 ; end  # main thread doing nothing and waiting for CTRL-C
#!/usr/bin/ruby
# coding: UTF-8
# author : Marc Quinton, july 2015.
# version : 0.7
# licence : MIT.
# keywords : hid, hidapi, delcom, pushbutton, usb, ffi
# source code location and documentation : https://gist.github.com/mqu/6351df5f8918ec6b0dab
# Delcom-FFI.rb : an HIDAPI interface, using FFI, for Ruby language, to Delcom button,
# keywords : hid, hidapi, delcom, pushbutton, usb, ffi
# code tested on product : 706562 (https://www.delcomproducts.com/productdetails.asp?PartNumber=706562)
# Buttons connected to pin P0.0, P0.1 and LED on P1.4 and P1.5
# links :
# HID-API source https://github.com/signal11/hidapi
# Delcom products : https://www.delcomproducts.com/
# HIDAPI + Ruby + FFI : http://spin.atomicobject.com/2014/01/31/ruby-prototyping-ffi/ - this script is based on this one.
#
# doc about HID : http://www.usbmadesimple.co.uk/ums_5.htm
# udev install (debian, ubuntu) -> this will create a symbolic link : /dev/input/delcom
# echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0fc5", ATTRS{idProduct}=="b080", ACTION=="add", SYMLINK+="input/delcom", MODE="0666", GROUP="your-group"' >/etc/udev/rules.d/85-delcom.rules
# echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0fc5", ATTRS{idProduct}=="b080", ACTION=="remove", RUN+="/usr/local/bin/delcom-remove.sh"' >>/etc/udev/rules.d/85-delcom.rules
# /etc/init.d/udev restart
require 'ffi'
require 'pp'
class ApplicationException < StandardError
end
module HidApi
extend FFI::Library
ffi_lib 'hidapi-libusb'
# ffi_lib 'hidapi-hidraw' -> not working (ioctl (SFEATURE): Broken pipe)
typedef :pointer, :device
# hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
# int hid_init(void);
# int hid_exit(void);
# void hid_close(hid_device *device);
attach_function :hid_open, [:int, :int, :int], :device
attach_function :hid_init, [ ], :void
attach_function :hid_exit, [ ], :void
attach_function :hid_close, [:device], :void
# int hid_write(hid_device *device, const unsigned char *data, size_t length);
# int hid_read(hid_device *device, unsigned char *data, size_t length);
# int hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
attach_function :hid_write, [:device, :pointer, :size_t], :int
attach_function :hid_read, [:device, :pointer, :size_t], :int, :blocking => true
attach_function :hid_read_timeout, [:device, :pointer, :size_t, :int], :int
# int hid_get_feature_report(hid_device *device, unsigned char *data, size_t length);
attach_function :hid_get_feature_report, [:device, :pointer, :size_t], :int
# int hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length);
attach_function :hid_send_feature_report, [:device, :pointer, :int], :int
# int hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen);
# int hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen);
# int hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen);
attach_function :hid_get_product_string, [:device, :buffer_out, :size_t], :int
attach_function :hid_get_manufacturer_string, [:device, :buffer_out, :size_t], :int
attach_function :hid_get_serial_number_string, [:device, :buffer_out, :size_t], :int
REPORT_SIZE = 65 # 64 bytes + 1 byte for report type
def self.pad_to_report_size(bytes)
(bytes+[0]*(REPORT_SIZE-bytes.size))
end
end
# read write OS/Library
# HidD_GetFeature HidD_SetFeature Windows/HID
# hid_get_feature_report hid_send_feature_report Linux-HIDAPI
#
module LibC
extend FFI::Library
ffi_lib FFI::Library::LIBC
# memory allocators
attach_function :malloc, [:size_t], :pointer
attach_function :calloc, [:size_t], :pointer
attach_function :valloc, [:size_t], :pointer
attach_function :realloc, [:pointer, :size_t], :pointer
attach_function :free, [:pointer], :void
# memory movers
attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
attach_function :bcopy, [:pointer, :pointer, :size_t], :void
end # module LibC
class HidAPI
def initialize vendor_id, product_id, serial_number=0
@opts={
:vendor=>vendor_id, :product=>product_id, :serial=>serial_number,
:thread => {}
}
HidApi.hid_init
self.connect
end
def disconnected?
raise "should not be called ; override me please"
end
def connect
@device=HidApi.hid_open(@opts[:vendor], @opts[:product], @opts[:serial])
raise ApplicationException, "can't open usb input device (0x#{@opts[:vendor].to_s(16)}:0x#{@opts[:product].to_s(16)}) " if @device.null?
end
def reconnect
while disconnected?
puts "trying to reconnect to device ; waiting for device connexion"
sleep 1
end
self.connect
puts "reconnected to USB device"
end
# activate auto-reconnect feature when USB device is disconnected
def auto_reconnect bool=true
# start auto-reconnect service
if bool
# start a thread checking for device disconections.
@opts[:thread][:auto_reconnect] = Thread.new do
while true
sleep 1
# try to reconnect when needed
self.reconnect if self.disconnected?
end
end
else
# stop auto-reconnect service
@opts[:thread][:auto_reconnect].terminate
end
end
def close
HidApi.hid_close(@device)
end
def get_manufacturer_string
size=256 # we are reading wchar_t (4xuchar) ; so allocate size*4
buffer = FFI::MemoryPointer.new(:uchar, size*4)
res = HidApi.hid_get_manufacturer_string(@device, buffer, size)
raise "can't read manufacturer_string" unless res==0
# convert wchar_t to uchar and strip unecessary 0 bytes.
v=buffer.read_bytes(size).unpack("C*").delete_if{|v| v==0x0}.pack("C*")
return v
end
# same as get_manufacturer_string
def get_product_string
size=255
buffer = FFI::MemoryPointer.new(:char, size*4)
res = HidApi.hid_get_product_string(@device, buffer, size)
return buffer.read_bytes(size).unpack("C*").delete_if{|v| v==0x0}.pack("C*")
end
# same as get_manufacturer_string
def get_serial_number_string
size=255
buffer = FFI::MemoryPointer.new(:short, size*4)
res = HidApi.hid_get_serial_number_string(@device, buffer, size)
raise "can't read serial_string" unless res==0
return buffer.read_bytes(size).unpack("C*").delete_if{|v| v==0x0}.pack("C*")
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=15
# There are currently two major commands (101 and 102) for the write command/data functions.
# A major command of 101 will send an 8 byte write command. A major command of 102 will
# send a 16 byte write command. In the future we will add two more commands to
# send 32 and 64 byte write commands. Note you can always send a higher number
# of bytes of a write command, but not the opposite.
def send_feature_report command
# puts "# send_feature_report()"
buffer = HidApi.pad_to_report_size(command).pack("C*")
res = HidApi.hid_send_feature_report @device, buffer, HidApi::REPORT_SIZE
# puts "send_feature_report ; res = #{res}"
# raise "send_feature_report error" if res<0
return res
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=23
def get_feature_report cmd, len=8
command=Array.new(len,0)
if cmd.is_a? Array
command[0]=cmd[0]
else
command[0]=cmd
end
buffer = command.pack("C*")
res = HidApi::C::hid_get_feature_report @device, buffer, len
return {
:len => res==-1?0:res,
:error => res==-1,
:data => res==-1?[]:buffer.unpack("C*")
}
end
# doc : chapter 6.4, page 15
def write
# FIXME
raise "error : write be done"
# SEND
# command_to_send = HidApi.pad_to_report_size([0]+ARGV.map(&:hex)).pack("C*")
# res = HidApi.hid_write dev, command_to_send, HidApi::REPORT_SIZE
# raise "command write failed" if res <= 0
end
# HIDAPI:hid_read and hid_read_timeout
#
# Read an Input report from a HID device.
#
# Input reports are returned to the host through the INTERRUPT IN endpoint. The first byte will contain the Report number if the device uses numbered reports.
#
# Parameters:
# device A device handle returned from hid_open().
# data A buffer to put the read data into.
# length The number of bytes to read. For devices with multiple reports, make sure to read an extra byte for the report number.
#
# Returns:
# This function returns the actual number of bytes read and -1 on error. If no packet was available to be read and the handle is in
# non-blocking mode, this function returns 0.
def read len=8, timeout=nil
command=Array.new(len,0)
buffer = command.pack("C*")
case timeout
when nil
res = HidApi.hid_read @device, buffer, len
raise ApplicationException, "IO error or device disconnected" if res==-1 && self.disconnected?
return {
:len => res==-1?0:res,
:error => res==-1,
:timeout => nil,
:data => res==-1?[]:buffer.unpack("C*")
}
else
res = HidApi.hid_read_timeout @device, buffer, 8, timeout
raise ApplicationException, "IO error or device disconnected" if res==-1 && self.disconnected?
return {
:len => res<=0?0:res,
:error => res==-1,
:timeout => res==0,
:data => res==-1?[]:buffer.unpack("C*")
}
end
end
end
class InputHandler
def initialize
@procs=[]
end
def on_button_event callback=nil
if callback==nil
@procs << Proc.new do | args |
yield args if block_given?
end
else
@procs << callback
end
end
end
# io=XinputHandler.new
#
# io.on_button_event { |args|
# puts "# on_button_event"
# pp args
# }
# io.mainloop
class XinputHandler < InputHandler
def initialize
super
@io=IO.popen('xinput test "Delcom Products Inc. USB FS IO"')
end
def mainloop
@io.each do |line|
case line
# read lines from xinput command in form : button SPACING (press|release) SPACING number
when /(button\s+(press|release))\s+(\d+)/;
args={
:string => $~[0],
:event => $~[1],
:button => $~[3]
}
# call all on_button_event() proc with args.
@procs.each { |p| p.call args }
end
end
end
end
# input handler based on HIDAPI (reading ports on internal micro-controler)
class HIDInputHandler < InputHandler
def initialize device
super()
@device=device # HIDAPI/Delcom device
end
def mainloop
while true
args = @device.wait_for_button_toggle
# call all on_button_event() proc with args.
@procs.each { |p| p.call args }
end
end
end
# programming delcom :
#
# read button events from xinput command from a pipe (popen)
# running external command line : xinput test "Delcom Products Inc. USB FS IO"
# you need to configure with Delcom Setup Utility DelcomSetup.exe (https://www.delcomproducts.com/productdetails.asp?ProductNum=890672)
# - button 1 (red button) -> mouse / hold-repeat / 1 (left)
# - button 2 (blue button) -> mouse / hold-repeat / 4 (center)
# FIXME : can't read events from xinput and simultaneously with HIDAPI.
# base Delcom API functions
# Delcom API : https://www.delcomproducts.com/downloads/USBIOHID.pdf
class DelcomApi < HidAPI
def initialize vendor_id, product_id, serial_number=0
super
end
# check if any device of the form /dev/input/delcom* is present.
def disconnected?
Dir.glob("/dev/input/delcom*").empty?
end
def shutdown
close
end
# internal use : get LSB mask given a LED name.
def led_name_to_mask color
case color
when :all
lsb=0xFF
when :blue
lsb=0b0010_0000
when :red
lsb=0b0100_0000 # FIXME
when :green
lsb=0b0001_0000 # FIXME
else
raise "error, unsupported color : #{color}"
end
return lsb
end
# may depend on device type.
def button_id_to_color id
colors = {
4 => :blue,
5 => :red
}
# raise "error : unknown id #{id}" unless colors.key?(id)
return nil unless colors.key?(id)
return colors[id]
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=24
# command = 100, len=8 bytes - read port 0 & 1
def read_ports timeout=100
cmd=100
get_feature_report [ cmd ], 2
end
# FIXME - need to be completed
def write_ports p0, p1
major=101
minor=10
raise "fixme"
send_feature_report [ major, minor, p0, p1]
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=23
# command = 7, len=8 bytes - scratchpad area contains 8 bytes of data
# where you can read and write arbitrary data for your application context
def read_scratchpad
puts "# read_scratchpad()"
raise "fixme"
pp self.get_feature_report 7
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=18
# command = 7, len=8 bytes - scratchpad area contains 8 bytes of data
# where you can read and write arbitrary data for your application context
def write_scratchpad val, addr
puts "# write_scratchpad()"
buff=Array.new(8,0)
buff[0]=37 # cmd
buff[1]=val # lsb-data
buff[2]=addr # msb-data
raise "fixme"
pp self.send_feature_report buff
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=24
# command = 10, len=8 bytes - reads the firmware information
# Byte 0-3: Unique Device Serial Number. DWORD Little Endian.
# Byte 4: Firmware Version.
# Byte 5: Firmware Date.
# Byte 6: Firmware Month.
# Byte 7: Firmware Year.
def get_firwmare_info
# raise "fixme"
return self.get_feature_report 10
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=17
# KO -> not working.
def led_pwr led, power
raise "fixme"
major=101
minor=34
send_feature_report [major, minor, led_name_to_mask(led), power]
end
def mainloop
@io.mainloop
end
# KO : not working ...
def sleep delay
require 'timeout'
# execute mainloop for "delay" seconds
status = Timeout::timeout(delay) {
self.mainloop
}
end
end
class DelcomButton < DelcomApi
# set input-handler.
def input_handler=io
@io=io
end
def read_buttons timeout=100
while true
infos=self.read 8, timeout
break unless infos[:timeout]
end
id=infos[:data][1]
if id == 0
# release state.
return {:button=> @last_btn, :event=>:release}
else
# register last button press for release state.
@last_btn=self.button_id_to_color(id)
return {:button=> @last_btn, :event=>:press}
end
end
def on_button_event callback=nil
if block_given?
callback=Proc.new do | args |
yield args
end
end
@io.on_button_event callback
end
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=15 - on, off mode (minor 12)
# doc : https://www.delcomproducts.com/downloads/USBIOHID.pdf#page=16 - blink mode - lock generator (minor 20)
# control LED :
# - mode :on, :off, :blink
# - led : :all, :blue, :red, :yellow
def led led, mode
major=101
minor=12
msb=0x00
lsb=led_name_to_mask(led)
case mode
when :on
minor=20
send_feature_report [major, minor, lsb, msb] # blink off
minor=12
send_feature_report [major, minor, lsb, msb] # led on
when :off
minor=20
send_feature_report [major, minor, lsb, msb] # blink off
minor=12
send_feature_report [major, minor, msb, lsb] # led off
when :blink
minor=20
send_feature_report [major, minor, msb, lsb] # blink on
minor=12
send_feature_report [major, minor, lsb, msb] # led on
end
end
# FIXME : don't support multiple button press.
# block reading button state until any button is pressed or released.
# we are polling port B for state change ; timeout is given in ms.
def wait_for_button_toggle timeout=nil
@last={} unless defined? @last
# read HID input device using timeout
while true
infos=self.read 8, timeout
break unless infos[:timeout]
end
id=infos[:data][1]
if id == 0
# release state.
return {:id=>@last[:id], :button=> @last[:btn], :event=>:release}
else
# register last button press for release state.
@last[:btn]=self.button_id_to_color(id)
@last[:id]=id
return {:id=>id, :button=> @last[:btn], :event=>:press}
end
end
end
STDOUT.sync = true
vendor_id = 0xfc5
product_id = 0xb080
serial_number = 0
if ARGV.size >= 1
test=ARGV[0].to_sym
else
test=:on_button_event
# test=:xinput
end
unless test==:xinput
button=DelcomButton.new vendor_id, product_id, serial_number
Signal.trap("INT") {
button.shutdown
exit
}
end
case test
when :infos
# query infos about USB device
pp button.get_manufacturer_string
pp button.get_product_string
pp button.get_serial_number_string
# KO
when :get_feature_report
pp button.send_feature_report [100]
pp button.get_feature_report 100
# KO
when :scratch_pad
pp button.read_scratchpad
pp button.write_scratchpad 12, 0
pp button.write_scratchpad 24, 1
pp button.read_scratchpad
when :led
if ARGV.size != 3
puts "usage : led [blue|red|all] [on|off|blink]"
exit 1
end
button.led(ARGV[1].to_sym, ARGV[2].to_sym)
when :led_on
button.led(:all, :on)
when :led_off
button.led(:all, :off)
when :hid_read
puts "# reading HID USB port for ever"
while true
# blocking read (timeout=nil) -> can't kill ruby process with CTRL-C
# so put read IO in an thread
# while true do pp button.read len=8, timeout=1000 ; end
thread = Thread.new { while true do pp button.read len=8, timeout=nil ; end }
while true do sleep 100 ; end # main thread doing nothing and waiting for CTRL-C
end
# - sample output pressing blue and red buttons
#
# - you need to configure the device using using DelcomSetup.exe utility :
# - button 1 (red button) -> mouse / hold-repeat / 1 (left)
# - button 2 (blue button) -> mouse / hold-repeat / 4 (center)
#
# {:len=>4, :error=>false, :timeout=>false, :data=>[223, 4, 0, 0, 0, 0, 0, 0]} -> blue button press
# {:len=>4, :error=>false, :timeout=>false, :data=>[223, 0, 0, 0, 0, 0, 0, 0]} -> blue button release
# {:len=>4, :error=>false, :timeout=>false, :data=>[223, 5, 0, 0, 0, 0, 0, 0]} -> red button press
# {:len=>4, :error=>false, :timeout=>false, :data=>[223, 0, 0, 0, 0, 0, 0, 0]} -> red button release
# - joystick - hold-release - button 1
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 1, 0, 0, 0, 0, 0, 0]}
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 0, 0, 0, 0, 0, 0, 0]}
#
# - joystick - hold-release - button 2
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 2, 0, 0, 0, 0, 0, 0]}
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 0, 0, 0, 0, 0, 0, 0]}
#
# - joystick - hold-release - button 3
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 4, 0, 0, 0, 0, 0, 0]}
# {:len=>7, :error=>false, :timeout=>nil, :data=>[221, 0, 0, 0, 0, 0, 0, 0]}
# try reading from HID device and handle disconnexions.
when :disconnect_test
Thread::abort_on_exception = true
puts "# reading HID USB port for ever"
begin
while true
thread = Thread.new { while true do pp button.read len=8, timeout=nil ; end }
# thread = Thread.new { while true do puts "disconnected ?" ; raise ApplicationException, "device disconnected" if button.disconnected? ; sleep 1 ; end }
while true do sleep 100 ; end # main thread doing nothing and waiting for CTRL-C
end
rescue
button.reconnect
retry
end
# auto-reconnect to usb device
when :auto_reconnect
Thread::abort_on_exception = true # abort all threads on exception from IO error
button.auto_reconnect # activate autoreconnect feature
puts "# reading HID USB port for ever ; trying to auto-reconnect usb device."
begin
while true
thread = Thread.new do
while true
infos=button.read len=8, timeout=nil
if infos[:error]
sleep 1
else
pp infos
end
end
end
while true do sleep 100 ; end # main thread doing nothing and waiting for CTRL-C
end
rescue
sleep 1
retry
end
when :read_buttons
puts "# reading button for ever ..."
while true
infos = button.read_buttons
pp infos unless infos[:timeout]
sleep 0.1
end
when :xinput
# can't use simultaneously HIDAPI and xinput.
io=XinputHandler.new
io.on_button_event { |args|
puts "# on_button_event"
pp args
}
io.mainloop
when :hid_io
io=HIDInputHandler.new button # receive events from HIDAPI / port reading.
io.on_button_event { |args|
puts "## button event"
pp args
}
io.mainloop
when :on_button_event
# create a thread to avoid IO blocking (in hid::read), blocking all ruby script, so we can interrupt with CTRL-C
thread = Thread.new do
button.input_handler=HIDInputHandler.new button # receive events from HIDAPI / port reading.
button.on_button_event { |args|
puts "## :on_button_event::button event " + args.to_s
# FIXME : does not work : need to get in mainloop for event reading. button.sleep should work, but need to
# be fixed as well.
# delay=0.3
# [1..7].each do
# button.led(:all, :off) ; sleep delay ; button.led(:all, :on) ; sleep delay
# end
}
button.mainloop
end
# main thread
while true do sleep 100 ; end # main thread doing nothing and waiting for CTRL-C
# send button events to MQTT server
# you can join server using mosquitto cli command : mosquitto_sub -h atrix -t "/path/to/your/topic"
# install MQTT client for ruby using this command : sudo gem install mqtt
# doc : https://github.com/njh/ruby-mqtt
when :mqtt
require 'rubygems'
require 'mqtt'
server=`hostname`.chomp
topic="/org/sub/network/#{server}/device/usb/button/delcom"
puts "topic=#{topic}"
mqtt=MQTT::Client.connect(server)
# create a thread to avoid IO blocking (in hid::read), blocking all ruby script, so we can interrupt with CTRL-C
thread = Thread.new do
button.input_handler=HIDInputHandler.new button # receive events from HIDAPI / port reading.
button.on_button_event { |args|
puts "## :on_button_event::button event " + args.to_s
mqtt.publish(topic, args.to_s)
}
button.mainloop
end
# main thread
while true do sleep 100 ; end # main thread doing nothing and waiting for CTRL-C
else
puts "unsupported command (#{ARGV[0]})"
exit 1
end
@mqu
Copy link
Author

mqu commented Jul 17, 2015

rev 0.5 : added support for MQTT notification as a use case.

@mqu
Copy link
Author

mqu commented Jul 20, 2015

rev 0.7 : finished HidAPI::get_[serial|manufactured|product]_string methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment