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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebdc824ab993ec59f3e5ba32fbef04a461e3f0354b8ea8723a17fa6125ba19a1
4
- data.tar.gz: 783826b2949fc55f1f8b98c34aca2f39d79f9fe445acbe3863b33e1435f123e1
3
+ metadata.gz: b64ad35673afb17f7d07e21cd404043cf832637c9247a6697a08148ca781977e
4
+ data.tar.gz: fd871f3815d45d51731a686cda177a28d1fb8315c72dd9a299da91bfeee1a091
5
5
  SHA512:
6
- metadata.gz: '059792265ac5e663899a421176034d49bf555c61c5a3ca160f62cb5e53054fbb05e1b1fd5d13016f1003d0da9b9a033f1c20af17615e0852d506a0ce7031b52d'
7
- data.tar.gz: 825f0ba1eab46d7fc3517954050d68a0b43bab0ce34d0cc05cfdf1696d87a2d2679a0b878d2691719dd04e393e0750dbee6dabab97fd55a34639df9ac502a264
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 runs from 1 to 16, and that range is fixed:
38
- this gem targets the 16-port model, so on a smaller hub the commands
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
- | `restore` | Load the port state back from flash |
78
- | `reset` | Reset the hub; port power is *not* maintained |
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 all ports, then switch off ports 4, 5 and 6
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
- ExSYS::ManagedUSB::PORTS.each do |p|
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
- Switching is a read-modify-write, and the library holds the serial line
145
- -- locked -- across the whole exchange, so two processes driving the
146
- same hub cannot lose each other's changes. The wire protocol is
147
- documented in the `ExSYS::ManagedUSB` class comment.
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', 'Run verbosely'
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(*ARGV.map(&:to_i), commit: $opts[:commit])
71
+ $hub.on(*ports.call, commit: $opts[:commit])
72
+ verbose.call
53
73
 
54
74
  when 'off'
55
- $hub.off(*ARGV.map(&:to_i), commit: $opts[:commit])
75
+ $hub.off(*ports.call, commit: $opts[:commit])
76
+ verbose.call
56
77
 
57
78
  when 'toggle'
58
- $hub.toggle(*ARGV.map(&:to_i), commit: $opts[:commit])
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
- $hub.reset
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
- $hub.restore
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}"