exsys 0.6 → 1.0
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 +4 -4
- data/README.md +80 -12
- data/bin/exsys-usb +54 -7
- data/lib/exsys/managed-usb.rb +328 -104
- data/lib/exsys/version.rb +1 -1
- data/test/support/fake_hub.rb +52 -26
- data/test/support/uart.rb +23 -6
- data/test/test_exsys_usb.rb +100 -5
- data/test/test_managed_usb.rb +268 -18
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b64ad35673afb17f7d07e21cd404043cf832637c9247a6697a08148ca781977e
|
|
4
|
+
data.tar.gz: fd871f3815d45d51731a686cda177a28d1fb8315c72dd9a299da91bfeee1a091
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ba2590f1165d88b24642f02a27b14ce5855f42c793e46ad1d2a697ebe6c795eeddb34ffb8e0d411988e920740de7bc189839545446107bdc850332c906d19a7
|
|
7
|
+
data.tar.gz: 6c9f2cdf259a245308884fba34601a3c7040c656659472b99d0c8fed03435bc8233bb5aad21eaf87970eb2503aabdce611a62d37448a2f74e177be15d65b43be
|
data/README.md
CHANGED
|
@@ -34,9 +34,8 @@ group such as `dialout` or `plugdev`, so check `ls -l` on it and add
|
|
|
34
34
|
yourself to that group rather than reaching for `sudo`.
|
|
35
35
|
|
|
36
36
|
The hub answers only to its password, `pass` unless it has been
|
|
37
|
-
changed. Port numbering
|
|
38
|
-
|
|
39
|
-
that take no port list would address ports that are not there.
|
|
37
|
+
changed. Port numbering starts at 1 and runs to however many ports
|
|
38
|
+
the hub reports having: the gem asks it, rather than assuming sixteen.
|
|
40
39
|
|
|
41
40
|
|
|
42
41
|
## Install
|
|
@@ -73,9 +72,22 @@ exsys-usb -d ${dev} -c on 1 # Turn on port 1, and save to flash
|
|
|
73
72
|
| `off [PORT...]` | Unpower the listed ports, or every port |
|
|
74
73
|
| `toggle [PORT...]` | Invert the listed ports, or every port |
|
|
75
74
|
| `set PORT:STATE...` | Set the listed ports; `-D` decides the rest |
|
|
75
|
+
| `status [PORT...]` | Report the ports, one `N on` / `N off` per line |
|
|
76
|
+
| `query` | What the hub says it is: id, ports, firmware |
|
|
76
77
|
| `commit` | Save the current port state to flash |
|
|
77
|
-
| `
|
|
78
|
-
| `reset` |
|
|
78
|
+
| `factory-reset` | Factory reset; refuses without `--yes` |
|
|
79
|
+
| `reset` | Reboot the hub; refuses without `--yes` |
|
|
80
|
+
|
|
81
|
+
> [!WARNING]
|
|
82
|
+
> `reset` and `factory-reset` both drop power to every port, so both
|
|
83
|
+
> refuse without `--yes`. `reset` reboots the whole hub; it is not the
|
|
84
|
+
> way to power-cycle one device, which is `off` then `on`.
|
|
85
|
+
>
|
|
86
|
+
> `factory-reset` is not the inverse of `commit`. It issues the hub's
|
|
87
|
+
> `RD` command: every port drops and the password goes back to `pass`.
|
|
88
|
+
> Nothing in the protocol reloads a saved state -- the hub applies it
|
|
89
|
+
> at power-on by itself.
|
|
90
|
+
|
|
79
91
|
|
|
80
92
|
A port state in `set` is written `PORT:STATE`, where `STATE` is one of
|
|
81
93
|
`1`, `on`, `ON`, `true`, `TRUE`, `t`, `T` or their false counterparts
|
|
@@ -88,6 +100,8 @@ A port state in `set` is written `PORT:STATE`, where `STATE` is one of
|
|
|
88
100
|
| `-d`, `--device=DEV` | Serial line to the hub (required) |
|
|
89
101
|
| `-p`, `--password=STR`| Hub password; defaults to `pass` |
|
|
90
102
|
| `-c`, `--commit` | Also write the new state to flash |
|
|
103
|
+
| `-y`, `--yes` | Mean a destructive action |
|
|
104
|
+
| `-v`, `--verbose` | Report the port states after a change |
|
|
91
105
|
| `-D`, `--default=BOOL`| State for the ports `set` does not name |
|
|
92
106
|
| `--debug[=FILE]` | Trace the serial exchange to stderr, or FILE |
|
|
93
107
|
| `-V`, `--version` | Print the library version |
|
|
@@ -97,6 +111,15 @@ The debug trace shows every frame sent and received, with the password
|
|
|
97
111
|
blanked out; when it is written to a file, that file is created
|
|
98
112
|
readable only by you.
|
|
99
113
|
|
|
114
|
+
`status` prints one port per line, which greps and awks without
|
|
115
|
+
parsing:
|
|
116
|
+
|
|
117
|
+
~~~sh
|
|
118
|
+
exsys-usb -d ${dev} status # every port
|
|
119
|
+
exsys-usb -d ${dev} status 3 7 # just those two
|
|
120
|
+
exsys-usb -d ${dev} -v on 3 # switch, then report
|
|
121
|
+
~~~
|
|
122
|
+
|
|
100
123
|
### Exit status
|
|
101
124
|
|
|
102
125
|
`0` when the command was carried out, `1` otherwise -- a malformed
|
|
@@ -116,11 +139,11 @@ exsys-usb -d /dev/ttyU0 off 3 || echo "could not switch port 3 off"
|
|
|
116
139
|
# and enable debug output to stderr
|
|
117
140
|
hub = ExSYS::ManagedUSB.new('/dev/ttyU0', debug: STDERR)
|
|
118
141
|
|
|
119
|
-
# Chain turning on
|
|
120
|
-
hub.on.off(4,5,6)
|
|
142
|
+
# Chain turning on every port, then switch off ports 4, 5 and 6
|
|
143
|
+
hub.on(:all).off(4,5,6)
|
|
121
144
|
|
|
122
145
|
# Toggle each port in turn
|
|
123
|
-
|
|
146
|
+
hub.ports.each do |p|
|
|
124
147
|
hub.toggle(p)
|
|
125
148
|
end
|
|
126
149
|
|
|
@@ -141,10 +164,55 @@ hub.get(:on) # => [ 1, 3 ]
|
|
|
141
164
|
hub.get(:off) # => [ 2, 4, ... ]
|
|
142
165
|
~~~
|
|
143
166
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
`on`, `off` and `toggle` want an explicit port list, and `:all` is how
|
|
168
|
+
you say every port. An empty list is refused rather than read as
|
|
169
|
+
"all": `hub.off(*ports)` with an empty `ports` is the very same call as
|
|
170
|
+
`hub.off`, so a computed list that came back empty would otherwise
|
|
171
|
+
switch all sixteen. The command line is unaffected -- naming no port
|
|
172
|
+
there still means every port.
|
|
173
|
+
|
|
174
|
+
Switching is a read-modify-write, and the library holds the serial
|
|
175
|
+
line -- locked -- across the whole exchange, so two processes driving
|
|
176
|
+
the same hub cannot lose each other's changes.
|
|
177
|
+
|
|
178
|
+
A read-decide-write spans two calls, so it needs the line held across
|
|
179
|
+
both. Wrap them in a session:
|
|
180
|
+
|
|
181
|
+
~~~ruby
|
|
182
|
+
hub.session do
|
|
183
|
+
hub.on(1) unless hub.get[1]
|
|
184
|
+
end
|
|
185
|
+
~~~
|
|
186
|
+
|
|
187
|
+
The hub will also describe itself, over the same line and without a
|
|
188
|
+
password:
|
|
189
|
+
|
|
190
|
+
~~~ruby
|
|
191
|
+
hub.query # => { id: "CENTOS", ports: 16, firmware: "v02",
|
|
192
|
+
# raw: "CENTOS000516v02" }
|
|
193
|
+
hub.port_count # => 16, asked once and remembered
|
|
194
|
+
~~~
|
|
195
|
+
|
|
196
|
+
`:all` covers exactly those ports, and a port the hub does not have is
|
|
197
|
+
refused. The count is read from the same field the vendor's own tool
|
|
198
|
+
reads, checked against it for hubs reporting 4, 8, 16 and 32 ports.
|
|
199
|
+
|
|
200
|
+
It is asked once and kept for the life of the object, which outlasts
|
|
201
|
+
any one connection -- the serial line is opened per operation, not
|
|
202
|
+
held. So a hub object is bound to the hub it first asked. If the
|
|
203
|
+
device is unplugged and another appears under the same name, build a
|
|
204
|
+
new one; nothing in the library can notice the swap.
|
|
205
|
+
|
|
206
|
+
`hub.factory_reset(confirm: true)` issues `RD` and carries the warning
|
|
207
|
+
above. The keyword is required: it is the one operation here that
|
|
208
|
+
nothing undoes, and the one most easily reached by misunderstanding.
|
|
209
|
+
It was called `restore` up to 0.6; the old name now raises rather than
|
|
210
|
+
run.
|
|
211
|
+
|
|
212
|
+
Sessions nest, so the methods above stay correct when called inside
|
|
213
|
+
one, and a session belongs to the thread that opened it: another thread
|
|
214
|
+
opens, and locks, its own line. The wire protocol is documented in the
|
|
215
|
+
`ExSYS::ManagedUSB` class comment.
|
|
148
216
|
|
|
149
217
|
|
|
150
218
|
## Tests
|
data/bin/exsys-usb
CHANGED
|
@@ -4,7 +4,7 @@ require 'optparse'
|
|
|
4
4
|
require 'exsys'
|
|
5
5
|
require 'exsys/managed-usb'
|
|
6
6
|
|
|
7
|
-
$opts = { :commit => false, :default => nil }
|
|
7
|
+
$opts = { :commit => false, :default => nil, :yes => false }
|
|
8
8
|
parser = OptionParser.new do |op|
|
|
9
9
|
op.banner = "Usage: #{op.program_name} ACTION"
|
|
10
10
|
|
|
@@ -12,8 +12,9 @@ parser = OptionParser.new do |op|
|
|
|
12
12
|
op.on '-D', '--default=BOOLEAN', TrueClass, 'Default state if not specified'
|
|
13
13
|
op.on '-p', '--password=STRING', 'Hub password'
|
|
14
14
|
op.on '-c', '--commit', 'Commit change to flash memory'
|
|
15
|
+
op.on '-y', '--yes', 'Confirm a destructive action'
|
|
15
16
|
op.on '--debug[=FILE]', 'Debug output file'
|
|
16
|
-
op.on '-v', '--[no-]verbose', '
|
|
17
|
+
op.on '-v', '--[no-]verbose', 'Report port states after a change'
|
|
17
18
|
op.on '-V', '--version', 'Version' do
|
|
18
19
|
puts "ExSYS library : #{ExSYS::VERSION}"
|
|
19
20
|
exit
|
|
@@ -43,19 +44,40 @@ begin
|
|
|
43
44
|
$hub = ExSYS::ManagedUSB.new($opts[:device], $opts[:password],
|
|
44
45
|
debug: debug)
|
|
45
46
|
|
|
47
|
+
# Naming no port on the command line still means every port. The
|
|
48
|
+
# library wants that said outright rather than inferred from an
|
|
49
|
+
# empty list, so it is spelled out here.
|
|
50
|
+
ports = -> { ARGV.empty? ? [ ExSYS::ManagedUSB::ALL ]
|
|
51
|
+
: ARGV.map(&:to_i) }
|
|
52
|
+
|
|
53
|
+
# One line per port, "3 on", which greps and awks cleanly. Used by
|
|
54
|
+
# the status action and by --verbose after a change.
|
|
55
|
+
report = ->(only = []) {
|
|
56
|
+
state = $hub.get
|
|
57
|
+
(only.empty? ? $hub.ports : only).each do |p|
|
|
58
|
+
raise "invalid port: #{p}" unless state.key?(p)
|
|
59
|
+
puts "#{p} #{state[p] ? 'on' : 'off'}"
|
|
60
|
+
end
|
|
61
|
+
}
|
|
62
|
+
# Only meaningful after something changed.
|
|
63
|
+
verbose = -> { report.call if $opts[:verbose] }
|
|
64
|
+
|
|
46
65
|
case action = ARGV.shift
|
|
47
66
|
when nil
|
|
48
67
|
puts parser
|
|
49
68
|
exit
|
|
50
69
|
|
|
51
70
|
when 'on'
|
|
52
|
-
$hub.on(*
|
|
71
|
+
$hub.on(*ports.call, commit: $opts[:commit])
|
|
72
|
+
verbose.call
|
|
53
73
|
|
|
54
74
|
when 'off'
|
|
55
|
-
$hub.off(*
|
|
75
|
+
$hub.off(*ports.call, commit: $opts[:commit])
|
|
76
|
+
verbose.call
|
|
56
77
|
|
|
57
78
|
when 'toggle'
|
|
58
|
-
$hub.toggle(*
|
|
79
|
+
$hub.toggle(*ports.call, commit: $opts[:commit])
|
|
80
|
+
verbose.call
|
|
59
81
|
|
|
60
82
|
when 'set'
|
|
61
83
|
t = ExSYS::ManagedUSB::TRUE_LIST .to_h {|e| [ e.to_s, e ]}
|
|
@@ -67,15 +89,40 @@ begin
|
|
|
67
89
|
[$1.to_i, tf[$2]]
|
|
68
90
|
}
|
|
69
91
|
$hub.set(a, $opts[:default], commit: $opts[:commit])
|
|
92
|
+
verbose.call
|
|
70
93
|
|
|
94
|
+
when 'status'
|
|
95
|
+
report.call(ARGV.map(&:to_i))
|
|
96
|
+
|
|
97
|
+
when 'query'
|
|
98
|
+
q = $hub.query
|
|
99
|
+
puts "id: #{q[:id]}"
|
|
100
|
+
puts "ports: #{q[:ports]}"
|
|
101
|
+
puts "firmware: #{q[:firmware]}"
|
|
102
|
+
|
|
71
103
|
when 'commit'
|
|
72
104
|
$hub.commit
|
|
73
105
|
|
|
74
106
|
when 'reset'
|
|
75
|
-
$
|
|
107
|
+
unless $opts[:yes]
|
|
108
|
+
raise 'reset reboots the hub, and every port loses power ' \
|
|
109
|
+
'while it does; pass --yes to mean it'
|
|
110
|
+
end
|
|
111
|
+
$hub.reset(confirm: true)
|
|
112
|
+
|
|
113
|
+
when 'factory-reset'
|
|
114
|
+
unless $opts[:yes]
|
|
115
|
+
raise 'factory-reset drops every port and resets the ' \
|
|
116
|
+
'password, and nothing undoes it; pass --yes to mean it'
|
|
117
|
+
end
|
|
118
|
+
$hub.factory_reset(confirm: true)
|
|
76
119
|
|
|
120
|
+
# A tombstone rather than an alias: anyone typing the old name is
|
|
121
|
+
# expecting the inverse of commit, which this never was.
|
|
77
122
|
when 'restore'
|
|
78
|
-
|
|
123
|
+
raise 'restore was renamed factory-reset: it restores the hub ' \
|
|
124
|
+
'to factory defaults, dropping every port and resetting ' \
|
|
125
|
+
'the password, and is not the inverse of commit'
|
|
79
126
|
|
|
80
127
|
else
|
|
81
128
|
raise "unknown action: #{action}"
|