|
#!/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 |
rev 0.2 :