exsys 0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
...
|