exsys 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +44 -0
- data/bin/exsys-usb +76 -0
- data/exsys.gemspec +30 -0
- data/lib/exsys/managed-usb.rb +228 -0
- data/lib/exsys/version.rb +3 -0
- data/lib/exsys.rb +5 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8f8a542193c2ef67c427b47ee5ee95c448ecb9f3aced7d1a763f4e3159340eb2
|
4
|
+
data.tar.gz: 04da256b271c0c43d07e258c808eff8659c2b95ecc03d7424ec8101d46f5c126
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e2555919ac712c8b4d4064b4d4d9cd8e69d8aa74c3ce71dbb86be23deb6e20cf0be89907d7d41bf087010f139dec32dcdd6fd6b989ea21aad53a46e83721bf1
|
7
|
+
data.tar.gz: da9630c153d83bbedc4f840707aa32ac90f2320d038e43ae01d8bfea62db52c090d2da9a39694545518e7b4829cf197a3487e64c4c052a9233cc4a8254154a07
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
Controlling a [ExSYS Managed USB hub][1] without being
|
3
|
+
constrained by the official binary-only support.
|
4
|
+
|
5
|
+
|
6
|
+
Executable
|
7
|
+
~~~sh
|
8
|
+
dev=/dev/ttyU0
|
9
|
+
exsys-hub -d ${dev} on # All on
|
10
|
+
exsys-hub -d ${dev} off # All off
|
11
|
+
exsys-hub -d ${dev} on 1 2 # Only turn on port 1 and 2
|
12
|
+
exsys-hub -d ${dev} toggle 3, 5 # Toggle port 3 and 5
|
13
|
+
exsys-hub -d ${dev} set 3:on 5:off # Turn on port 3, turn off port 5
|
14
|
+
exsys-hub -d ${dev} -D false set 3:on # Turn on port 3, turn off all other ports
|
15
|
+
~~~
|
16
|
+
|
17
|
+
Library:
|
18
|
+
|
19
|
+
~~~ruby
|
20
|
+
# Instanciate hub (Linux: ttyUSB?, FreeBSD: ttyU?)
|
21
|
+
# and enable debug output to stderr
|
22
|
+
hub = ExSYS::ManagedUSB.new('/dev/ttyU0', debug: STDERR)
|
23
|
+
|
24
|
+
# Chaining turning on all port, and swithing off ports 4,5,6
|
25
|
+
hub.on.off(4,5,6)
|
26
|
+
|
27
|
+
# Perform sequential toggle of all individual ports
|
28
|
+
ExSYS::ManagedUSB::PORTS.each do |p|
|
29
|
+
hub.toggle(p)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set ports states for 1 and 2
|
33
|
+
hub.set({ 1 => true, 2 => false })
|
34
|
+
|
35
|
+
# Set ports states for 1 and 2, forcing other ports to off
|
36
|
+
hub.set({ 1 => true, 2 => true }, false)
|
37
|
+
~~~
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
[1]: https://www.exsys-shop.de/shopware/en/categories/hubsdocks/usb-hubs-managed/1263/managed-16-port-usb-3.2-gen-1-metal-hub-with-15kv-esd-surge-protection-din-rail?c=35
|
data/bin/exsys-usb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'exsys'
|
5
|
+
require 'exsys/managed-usb'
|
6
|
+
|
7
|
+
$opts = { :commit => false, :default => nil }
|
8
|
+
parser = OptionParser.new do |op|
|
9
|
+
op.banner = "Usage: #{op.program_name} ACTION"
|
10
|
+
|
11
|
+
op.on '-d', '--device=DEV', 'Serial line to hub'
|
12
|
+
op.on '-D', '--default=BOOLEAN', TrueClass, 'Default state if not specified'
|
13
|
+
op.on '-p', '--password=STRING', 'Hub password'
|
14
|
+
op.on '-c', '--commit', 'Commit change to flash memory'
|
15
|
+
op.on '--debug[=FILE]', 'Debug output file'
|
16
|
+
op.on '-v', '--[no-]verbose', 'Run verbosely'
|
17
|
+
op.on '-V', '--version', 'Version' do
|
18
|
+
puts "ExSYS library : #{ExSYS::VERSION}"
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
op.on '-h', '--help', 'Help' do
|
22
|
+
puts op
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
parser.parse!(into: $opts)
|
27
|
+
|
28
|
+
debug = if $opts.include?(:debug)
|
29
|
+
if $opts[:debug].nil?
|
30
|
+
then STDERR
|
31
|
+
else File.open($opts[:debug], File::RDWR|File::CREAT)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
$hub = ExSYS::ManagedUSB.new($opts[:device], $opts[:password],
|
35
|
+
debug: debug)
|
36
|
+
|
37
|
+
|
38
|
+
begin
|
39
|
+
case action = ARGV.shift
|
40
|
+
when nil
|
41
|
+
puts parser
|
42
|
+
exit
|
43
|
+
|
44
|
+
when 'on'
|
45
|
+
puts ARGV.map(&:to_i).join(',')
|
46
|
+
$hub.on(*ARGV.map(&:to_i), commit: $opts[:commit])
|
47
|
+
|
48
|
+
when 'off'
|
49
|
+
$hub.off(*ARGV.map(&:to_i), commit: $opts[:commit])
|
50
|
+
|
51
|
+
when 'toggle'
|
52
|
+
$hub.toggle(*ARGV.map(&:to_i), commit: $opts[:commit])
|
53
|
+
|
54
|
+
when 'set'
|
55
|
+
t = ExSYS::ManagedUSB::TRUE_LIST .to_h {|e| [ e.to_s, e ]}
|
56
|
+
f = ExSYS::ManagedUSB::FALSE_LIST.to_h {|e| [ e.to_s, e ]}
|
57
|
+
tf = t.merge(f) { raise "true/false conflict (internal error)" }
|
58
|
+
r = tf.keys.map {|e| Regexp.escape(e)}
|
59
|
+
a = ARGV.to_h {|e|
|
60
|
+
raise "invalid argument" unless e =~ /^(\d+):(#{r.join('|')})$/
|
61
|
+
[$1.to_i, tf[$2]]
|
62
|
+
}
|
63
|
+
$hub.set(a, $opts[:default], commit: $opts[:commit])
|
64
|
+
|
65
|
+
when 'commit'
|
66
|
+
$hub.commit
|
67
|
+
|
68
|
+
when 'reset'
|
69
|
+
$hub.reset
|
70
|
+
|
71
|
+
when 'restore'
|
72
|
+
$hub.restore
|
73
|
+
end
|
74
|
+
#rescue => e
|
75
|
+
# $stderr.puts "#{parser.program_name}: #{e}"
|
76
|
+
end
|
data/exsys.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'lib/exsys/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'exsys'
|
7
|
+
s.version = ExSYS::VERSION
|
8
|
+
s.summary = "ExSYS managed USB hub support"
|
9
|
+
s.description = <<~EOF
|
10
|
+
|
11
|
+
Provide command support to control ExSYS Managed USB hub.
|
12
|
+
|
13
|
+
EOF
|
14
|
+
|
15
|
+
s.homepage = 'https://github.com/sdalu/ruby-exsys'
|
16
|
+
s.license = 'MIT'
|
17
|
+
|
18
|
+
s.authors = [ "Stéphane D'Alu" ]
|
19
|
+
s.email = [ 'stephane.dalu@insa-lyon.fr' ]
|
20
|
+
|
21
|
+
s.files = %w[ README.md exsys.gemspec ] +
|
22
|
+
Dir['lib/**/*.rb']
|
23
|
+
|
24
|
+
s.bindir = 'bin'
|
25
|
+
s.executables << 'exsys-usb'
|
26
|
+
|
27
|
+
s.add_dependency 'uart'
|
28
|
+
s.add_development_dependency 'yard', '~>0'
|
29
|
+
s.add_development_dependency 'rake', '~>13'
|
30
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require "uart"
|
2
|
+
|
3
|
+
module ExSYS
|
4
|
+
|
5
|
+
# Control a ExSYS Managed USB hub
|
6
|
+
class ManagedUSB
|
7
|
+
SPEED = 9600 # @!visibility private
|
8
|
+
PASSWORD = 'pass'.freeze # @!visibility private
|
9
|
+
PORTS = 1.upto(16).to_a.freeze # @!visibility private
|
10
|
+
TRUE_LIST = [ 1, :on, :ON, :true, :TRUE, :t, :T, true ].freeze # @!visibility private
|
11
|
+
FALSE_LIST = [ 0, :off, :OFF, :false, :FALSE, :f, :F, false ].freeze # @!visibility private
|
12
|
+
|
13
|
+
# Error handling class
|
14
|
+
class Error < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Initialize object.
|
18
|
+
#
|
19
|
+
# @param line [String] Serial line
|
20
|
+
# (usually /dev/ttyU? or /dev/ttyUSB?)
|
21
|
+
# @param password [String] Hub password
|
22
|
+
# @param debug [IO] Write debug output
|
23
|
+
def initialize(line, password = nil, debug: nil)
|
24
|
+
password ||= PASSWORD
|
25
|
+
if password.size > 8
|
26
|
+
raise ArgumentError, "password too long"
|
27
|
+
end
|
28
|
+
@line = line
|
29
|
+
@password = password.ljust(8)
|
30
|
+
@debug = debug
|
31
|
+
end
|
32
|
+
|
33
|
+
# Toggle all or specified ports
|
34
|
+
#
|
35
|
+
# @param commit [Boolean] Commit to flash memory
|
36
|
+
def toggle(*ports, commit: false)
|
37
|
+
_set(_get ^ mask(ports, :all), commit: commit)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Turn on all or specified ports
|
41
|
+
#
|
42
|
+
# @param commit [Boolean] Commit to flash memory
|
43
|
+
def on(*ports, commit: false)
|
44
|
+
_set(_get | mask(ports, :all), commit: commit)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Turn off all or specified ports
|
48
|
+
#
|
49
|
+
# @param commit [Boolean] Commit to flash memory
|
50
|
+
def off(*ports, commit: false)
|
51
|
+
_set(_get & ~mask(ports, :all), commit: commit)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set state for the specified ports
|
55
|
+
#
|
56
|
+
# Port specification can have one of the folling format
|
57
|
+
#
|
58
|
+
# 1. hash of port values: { 1 => :on, 2 => :off, ...}
|
59
|
+
# 2. hash of port states: { :on => [1, 3], :off => 4 }
|
60
|
+
#
|
61
|
+
# In the case 1. the state values can be specified by
|
62
|
+
#
|
63
|
+
# * True: 1, :on, :ON, :true, :TRUE, true
|
64
|
+
# * False: 0, :off, :OFF, :false, :FALSE, false
|
65
|
+
#
|
66
|
+
# The port states that are not specified will acquire the
|
67
|
+
# value specified by the default parameter (nil being the
|
68
|
+
# hub port current value)
|
69
|
+
#
|
70
|
+
# @param dataset [Hash] Port state specification
|
71
|
+
# @param default [Boolean,nil] Default value to use if unspecified
|
72
|
+
# @param commit [Boolean] Commit to flash memory
|
73
|
+
def set(dataset, default = nil, commit: false)
|
74
|
+
val = _get
|
75
|
+
|
76
|
+
# Normalize
|
77
|
+
keys = dataset.keys
|
78
|
+
if (keys - PORTS).empty?
|
79
|
+
dataset = dataset.transform_values do |v|
|
80
|
+
case v
|
81
|
+
when * TRUE_LIST then true
|
82
|
+
when *FALSE_LIST then false
|
83
|
+
when nil
|
84
|
+
else raise ArgumentError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
elsif (keys - [:on, :off]).empty?
|
88
|
+
on = Array(dataset[:on ])
|
89
|
+
off = Array(dataset[:off])
|
90
|
+
|
91
|
+
unless (on & off).empty?
|
92
|
+
raise ArgumentError, "on/off overlap"
|
93
|
+
end
|
94
|
+
|
95
|
+
dataset = {}
|
96
|
+
dataset.merge!(on .to_h {|k| [k, true ] })
|
97
|
+
dataset.merge!(off.to_h {|k| [k, false ] })
|
98
|
+
else
|
99
|
+
raise ArgumentError
|
100
|
+
end
|
101
|
+
|
102
|
+
# Fill unspecified
|
103
|
+
unless default.nil?
|
104
|
+
(PORTS - dataset.keys).each do |k|
|
105
|
+
dataset.merge!(k => default)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Compute value
|
110
|
+
dataset.compact.each do |k,v|
|
111
|
+
flg = 1 << (k-1)
|
112
|
+
if v
|
113
|
+
then val |= flg
|
114
|
+
else val &= ~flg
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
_set(val, commit: commit)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get hub current state for all ports
|
122
|
+
#
|
123
|
+
# Return value depend of the asked type (default: ports)
|
124
|
+
#
|
125
|
+
# * ports : { 1 => true, 2 => false, ...}
|
126
|
+
# * on_off: { :on => [1,2,3,...], :off => [6,7,...] }
|
127
|
+
# * on : [ 1, 2, 3, ... ]
|
128
|
+
# * off : [ 1, 2, 3, ... ]
|
129
|
+
#
|
130
|
+
# @param type [:ports, :on_off, :on, :off] Type of returned value
|
131
|
+
def get(type = :ports)
|
132
|
+
val = _get
|
133
|
+
h = PORTS.reduce({}) {|acc, obj|
|
134
|
+
acc.merge(obj => (val & (1 << (obj-1))).positive?)
|
135
|
+
}
|
136
|
+
|
137
|
+
case type
|
138
|
+
when :ports
|
139
|
+
h
|
140
|
+
when :on_off
|
141
|
+
h.reduce({}) {|acc, (k,v)|
|
142
|
+
acc.merge(v ? :on : :off => [ k ]) {|k,o,n| o + n }
|
143
|
+
}
|
144
|
+
when :on
|
145
|
+
h.select {|k,v| v }.keys
|
146
|
+
when :off
|
147
|
+
h.reject {|k,v| v }.keys
|
148
|
+
else
|
149
|
+
raise ArgumentError
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Restore port states from the flash memory
|
154
|
+
def restore
|
155
|
+
action('RD', @password).then { self }
|
156
|
+
end
|
157
|
+
|
158
|
+
# Save the port states to the flash memory
|
159
|
+
def commit
|
160
|
+
action('WP', @password).then { self }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Perform a hub reset action
|
164
|
+
#
|
165
|
+
# @note power is not maintained accros a reset
|
166
|
+
def reset
|
167
|
+
action('RH', @password, reply: false).then { self }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Change the hub protection password
|
171
|
+
def password(new)
|
172
|
+
new = PASSWORD if new.nil?
|
173
|
+
raise ArgumentError, 'password too long' if new.size > 8
|
174
|
+
new_password = new.ljust(8)
|
175
|
+
action('CP', @password, new_password)
|
176
|
+
@password = new_password
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def mask(ports, empty = :none)
|
183
|
+
case empty
|
184
|
+
when :none
|
185
|
+
when :all
|
186
|
+
ports = PORTS if ports.empty?
|
187
|
+
else raise ArgumentError
|
188
|
+
end
|
189
|
+
|
190
|
+
ports.reduce(0) {|acc, obj| acc |= 1 << (obj-1) }
|
191
|
+
end
|
192
|
+
|
193
|
+
def _get
|
194
|
+
data = action('GP', check: false)
|
195
|
+
|
196
|
+
if (data.size == 3) && (data[0] == 'E')
|
197
|
+
raise Error, data[1..-1]
|
198
|
+
elsif data.size != 8
|
199
|
+
raise Error
|
200
|
+
end
|
201
|
+
|
202
|
+
[ data ].pack('H4').unpack1('v')
|
203
|
+
end
|
204
|
+
|
205
|
+
def _set(v, commit: false)
|
206
|
+
dataset = ([v].pack('v').unpack1('H*') + 'ffff').upcase
|
207
|
+
action(commit ? 'FP' : 'SP', @password, dataset).then { self }
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
def action(*cmds, reply: true, check: true)
|
212
|
+
cmd = cmds.join
|
213
|
+
UART.open @line, SPEED do |serial|
|
214
|
+
@debug&.puts "<-- #{cmd}"
|
215
|
+
serial.write "#{cmd}\r"
|
216
|
+
if reply
|
217
|
+
serial.read.chomp.tap do |data|
|
218
|
+
@debug&.puts "--> #{data}"
|
219
|
+
if check && data[0] != 'G'
|
220
|
+
raise Error, data[1..-1]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
data/lib/exsys.rb
ADDED
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: exsys
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.5'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stéphane D'Alu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uart
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13'
|
55
|
+
description: |2+
|
56
|
+
|
57
|
+
Provide command support to control ExSYS Managed USB hub.
|
58
|
+
|
59
|
+
email:
|
60
|
+
- stephane.dalu@insa-lyon.fr
|
61
|
+
executables:
|
62
|
+
- exsys-usb
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- README.md
|
67
|
+
- bin/exsys-usb
|
68
|
+
- exsys.gemspec
|
69
|
+
- lib/exsys.rb
|
70
|
+
- lib/exsys/managed-usb.rb
|
71
|
+
- lib/exsys/version.rb
|
72
|
+
homepage: https://github.com/sdalu/ruby-exsys
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubygems_version: 3.5.19
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: ExSYS managed USB hub support
|
95
|
+
test_files: []
|
96
|
+
...
|