exsys 0.5

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -0,0 +1,3 @@
1
+ module ExSYS
2
+ VERSION = 0.5 # Version
3
+ end
data/lib/exsys.rb ADDED
@@ -0,0 +1,5 @@
1
+ module ExSYS
2
+ end
3
+
4
+ require_relative 'exsys/version'
5
+ require_relative 'exsys/managed-usb'
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
+ ...