urest 0.99.1

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.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ <img align="right" height="100" src="https://github.com/etm/urest/blob/main/logo/urest_logo.png">
2
+
3
+ # UREST
4
+
5
+ A simple REST server for Universal robots in ruby, which uses the ur-sock library.
6
+
7
+ ## Getting Started
8
+
9
+ A simple ruby server, which uses the ur-sock library.
10
+
11
+ ### Prerequisites & Installation
12
+
13
+ This server has no special prerequisites. But it is always advisable to prepare
14
+ your machine for installation and development, and install some common
15
+ libraries and headers, which are used by common dependencies:
16
+
17
+ * Windows 10 users with WSL2 can use Fedora/Ubuntu instructions below 
18
+ * Mac OSX users can use brew: brew install libxml2 libxslt 
19
+ * Ubuntu (>=20.04) / Debian users can use their package manager: ```sudo apt-get install build-essential ruby-dev libxml2-dev libxslt-dev libz-dev libssl-dev librasqal-dev libraptor2-dev libicu-dev```
20
+ * Fedora (>=32) users can use their package manager: ```sudo dnf install @buildsys-build @development-tools @c-development ruby-devel libxml2-devel libxslt-devel zlib-devel rasqal-devel raptor2-devel libicu-devel``` 
21
+
22
+ You can then install the UREST gem with:
23
+
24
+ ```
25
+ gem install urest
26
+ ```
27
+
28
+ If you want to develop or extend the server, just use the following instruction
29
+ ```
30
+ git clone https://github.com/etm/urest
31
+ git clone https://github.com/fpauker/ur-sock
32
+ ```
33
+
34
+ Just follow the install instructions of the 2 projects.
35
+
36
+ ### Starting the server
37
+
38
+ To scaffold a server, first create a directory, then use the urest
39
+
40
+ ```
41
+ mkdir -p ~/run/urest
42
+ cd ~/run/urest
43
+ urest scaffold
44
+ ```
45
+
46
+ After changing the \.conf file to point to your UR, you can start the server with
47
+
48
+ ```
49
+ ./urest start
50
+ ```
51
+
52
+ or
53
+
54
+ ```
55
+ ./urest -v start
56
+ ```
57
+
58
+ to see verbose output.
59
+
60
+ ## Contributing
61
+
62
+ Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us.
63
+
64
+ ## Versioning
65
+
66
+ We use [SemVer](http://semver.org/) for versioning.
67
+
68
+ ## Authors
69
+
70
+ * **Jürgen Mangler**
71
+
72
+ See also the list of [contributors](https://github.com/etm/urest/AUTHORS) who participated in this project.
73
+
74
+ ## License
75
+
76
+ This project is licensed under the GPL3 License - see the [LICENSE.md](./LICENSE) file for details
data/Rakefile ADDED
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rubygems/package_task'
3
+
4
+ spec = eval(File.read('urest.gemspec'))
5
+
6
+ task :default => [:gem]
7
+
8
+ Gem::PackageTask.new(spec) do |pkg|
9
+ pkg.need_zip = true
10
+ pkg.need_tar = true
11
+ FileUtils.mkdir 'pkg' rescue nil
12
+ FileUtils.rm_rf Dir.glob('pkg/*')
13
+ FileUtils.ln_sf "#{pkg.name}.gem", "pkg/#{spec.name}.gem"
14
+ end
15
+
16
+ task :push => :gem do |r|
17
+ `gem push pkg/urest.gem`
18
+ end
19
+
20
+ task :install => :gem do |r|
21
+ `gem install pkg/urest.gem`
22
+ end
data/lib/rtde.conf.xml ADDED
@@ -0,0 +1,125 @@
1
+ <?xml version="1.0"?>
2
+ <rtde_config>
3
+ <recipe key="speed">
4
+ <field name="speed_slider_mask" type="UINT32"/>
5
+ <field name="speed_slider_fraction" type="DOUBLE"/>
6
+ </recipe>
7
+ <recipe key="inbit">
8
+ <!--Only put Input Registers here which are used-->
9
+ <field name="input_bit_register_64" type="BOOL"/>
10
+ <field name="input_bit_register_65" type="BOOL"/>
11
+ </recipe>
12
+ <recipe key="inint">
13
+ <!--Only put Input Registers here which are used-->
14
+ <field name="input_int_register_0" type="INT32"/>
15
+ <field name="input_int_register_1" type="INT32"/>
16
+ <field name="input_int_register_2" type="INT32"/>
17
+ <field name="input_int_register_3" type="INT32"/>
18
+ <field name="input_int_register_4" type="INT32"/>
19
+ <field name="input_int_register_5" type="INT32"/>
20
+ <field name="input_int_register_6" type="INT32"/>
21
+ <field name="input_int_register_7" type="INT32"/>
22
+ <field name="input_int_register_8" type="INT32"/>
23
+ <field name="input_int_register_9" type="INT32"/>
24
+ </recipe>
25
+ <recipe key="indoub">
26
+ <!--Only put Input Registers here which are used-->
27
+ <field name="input_double_register_0" type="DOUBLE"/>
28
+ <field name="input_double_register_1" type="DOUBLE"/>
29
+ <field name="input_double_register_2" type="DOUBLE"/>
30
+ <field name="input_double_register_3" type="DOUBLE"/>
31
+ <field name="input_double_register_4" type="DOUBLE"/>
32
+ <field name="input_double_register_5" type="DOUBLE"/>
33
+ <field name="input_double_register_6" type="DOUBLE"/>
34
+ <field name="input_double_register_7" type="DOUBLE"/>
35
+ <field name="input_double_register_8" type="DOUBLE"/>
36
+ <field name="input_double_register_9" type="DOUBLE"/>
37
+ </recipe>
38
+ <recipe key="out">
39
+ <field name="timestamp" type="DOUBLE"/>
40
+ <field name="target_q" type="VECTOR6D"/>
41
+ <field name="target_qd" type="VECTOR6D"/>
42
+ <field name="target_qdd" type="VECTOR6D"/>
43
+ <field name="target_current" type="VECTOR6D"/>
44
+ <field name="target_moment" type="VECTOR6D"/>
45
+ <field name="actual_q" type="VECTOR6D"/>
46
+ <field name="actual_qd" type="VECTOR6D"/>
47
+ <field name="actual_current" type="VECTOR6D"/>
48
+ <field name="joint_control_output" type="VECTOR6D"/>
49
+ <field name="actual_TCP_pose" type="VECTOR6D"/>
50
+ <field name="actual_TCP_speed" type="VECTOR6D"/>
51
+ <field name="actual_TCP_force" type="VECTOR6D"/>
52
+ <field name="target_TCP_pose" type="VECTOR6D"/>
53
+ <field name="target_TCP_speed" type="VECTOR6D"/>
54
+ <field name="actual_digital_input_bits" type="UINT64"/>
55
+ <field name="joint_temperatures" type="VECTOR6D"/>
56
+ <field name="actual_execution_time" type="DOUBLE"/>
57
+ <field name="robot_mode" type="INT32"/>
58
+ <field name="joint_mode" type="VECTOR6INT32"/>
59
+ <field name="safety_mode" type="INT32"/>
60
+ <field name="actual_tool_accelerometer" type="VECTOR3D"/>
61
+ <field name="speed_scaling" type="DOUBLE"/>
62
+ <field name="actual_momentum" type="DOUBLE"/>
63
+ <field name="actual_main_voltage" type="DOUBLE"/>
64
+ <field name="actual_robot_voltage" type="DOUBLE"/>
65
+ <field name="actual_robot_current" type="DOUBLE"/>
66
+ <field name="actual_joint_voltage" type="VECTOR6D"/>
67
+ <field name="actual_digital_output_bits" type="UINT64"/>
68
+ <field name="runtime_state" type="UINT32"/>
69
+ <field name="output_bit_register_64" type="BOOL"/>
70
+ <field name="target_speed_fraction" type="DOUBLE"/>
71
+
72
+ <!-- field name="output_bit_registers0_to_31" type="UINT32"/-->
73
+ <!-- field name="output_bit_registers32_to_63" type="UINT32"/-->
74
+
75
+ <!-- field name="output_int_register_0" type="INT32"/-->
76
+ <!-- field name="output_int_register_1" type="INT32"/-->
77
+ <!-- field name="output_int_register_2" type="INT32"/-->
78
+ <!-- field name="output_int_register_3" type="INT32"/-->
79
+ <!-- field name="output_int_register_4" type="INT32"/-->
80
+ <!-- field name="output_int_register_5" type="INT32"/-->
81
+ <!-- field name="output_int_register_6" type="INT32"/-->
82
+ <!-- field name="output_int_register_7" type="INT32"/-->
83
+ <!-- field name="output_int_register_8" type="INT32"/-->
84
+ <!-- field name="output_int_register_9" type="INT32"/-->
85
+ <!-- field name="output_int_register_10" type="INT32"/-->
86
+ <!-- field name="output_int_register_11" type="INT32"/-->
87
+ <!-- field name="output_int_register_12" type="INT32"/-->
88
+ <!-- field name="output_int_register_13" type="INT32"/-->
89
+ <!-- field name="output_int_register_14" type="INT32"/-->
90
+ <!-- field name="output_int_register_15" type="INT32"/-->
91
+ <!-- field name="output_int_register_16" type="INT32"/-->
92
+ <!-- field name="output_int_register_17" type="INT32"/-->
93
+ <!-- field name="output_int_register_18" type="INT32"/-->
94
+ <!-- field name="output_int_register_19" type="INT32"/-->
95
+ <!-- field name="output_int_register_20" type="INT32"/-->
96
+ <!-- field name="output_int_register_21" type="INT32"/-->
97
+ <!-- field name="output_int_register_22" type="INT32"/-->
98
+ <!-- field name="output_int_register_23" type="INT32"/-->
99
+
100
+ <!-- field name="output_double_register_0" type="DOUBLE"/-->
101
+ <!-- field name="output_double_register_1" type="DOUBLE"/-->
102
+ <!-- field name="output_double_register_2" type="DOUBLE"/-->
103
+ <!-- field name="output_double_register_3" type="DOUBLE"/-->
104
+ <!-- field name="output_double_register_4" type="DOUBLE"/-->
105
+ <!-- field name="output_double_register_5" type="DOUBLE"/-->
106
+ <!-- field name="output_double_register_6" type="DOUBLE"/-->
107
+ <!-- field name="output_double_register_7" type="DOUBLE"/-->
108
+ <!-- field name="output_double_register_8" type="DOUBLE"/-->
109
+ <!-- field name="output_double_register_9" type="DOUBLE"/-->
110
+ <!-- field name="output_double_register_10" type="DOUBLE"/-->
111
+ <!-- field name="output_double_register_11" type="DOUBLE"/-->
112
+ <!-- field name="output_double_register_12" type="DOUBLE"/-->
113
+ <!-- field name="output_double_register_13" type="DOUBLE"/-->
114
+ <!-- field name="output_double_register_14" type="DOUBLE"/-->
115
+ <!-- field name="output_double_register_15" type="DOUBLE"/-->
116
+ <!-- field name="output_double_register_16" type="DOUBLE"/-->
117
+ <!-- field name="output_double_register_17" type="DOUBLE"/-->
118
+ <!-- field name="output_double_register_18" type="DOUBLE"/-->
119
+ <!-- field name="output_double_register_19" type="DOUBLE"/-->
120
+ <!-- field name="output_double_register_20" type="DOUBLE"/-->
121
+ <!-- field name="output_double_register_21" type="DOUBLE"/-->
122
+ <!-- field name="output_double_register_22" type="DOUBLE"/-->
123
+ <!-- field name="output_double_register_23" type="DOUBLE"/-->
124
+ </recipe>
125
+ </rtde_config>
data/lib/urest.rb ADDED
@@ -0,0 +1,405 @@
1
+ require 'riddl/server'
2
+ require 'json'
3
+ require 'typhoeus'
4
+ if $dev
5
+ require_relative '../../ur-sock/lib/ur-sock'
6
+ else
7
+ require 'ur-sock'
8
+ end
9
+ require 'net/ssh'
10
+ require 'net/scp'
11
+
12
+ module UREST
13
+ SERVER = File.expand_path(File.join(__dir__,'urest.xml'))
14
+
15
+ def self::start_dash(opts) #{{{
16
+ opts['dash'] = UR::Dash.new(opts['ipadress']).connect rescue nil
17
+ end #}}}
18
+
19
+ def self::start_psi(opts) #{{{
20
+ opts['psi'] = UR::Psi.new(opts['ipadress']).connect rescue nil
21
+ end #}}}
22
+
23
+ def self::start_rtde(opts) #{{{
24
+ ### Loading config file
25
+ conf = UR::XMLConfigFile.new opts['rtde_config']
26
+ output_names, output_types = conf.get_recipe opts['rtde_config_recipe_base']
27
+ opts['rtde'] = UR::Rtde.new(opts['ipadress']).connect
28
+
29
+ ### Set Speed
30
+ if opts['rtde_config_recipe_speed']
31
+ speed_names, speed_types = conf.get_recipe opts['rtde_config_recipe_speed']
32
+ opts['speed'] = opts['rtde'].send_input_setup(speed_names, speed_types)
33
+ opts['speed']['speed_slider_mask'] = 1
34
+ end
35
+
36
+ ### Set register
37
+ if opts['rtde_config_recipe_inbit']
38
+ bit_names, bit_types = conf.get_recipe opts['rtde_config_recipe_inbit']
39
+ opts['inbit'] = opts['rtde'].send_input_setup(bit_names,bit_types)
40
+ end
41
+ if opts['rtde_config_recipe_inint']
42
+ int_names, int_types = conf.get_recipe opts['rtde_config_recipe_inint']
43
+ opts['inint'] = opts['rtde'].send_input_setup(int_names,int_types)
44
+ end
45
+ if opts['rtde_config_recipe_indoub']
46
+ doub_names, doub_types = conf.get_recipe opts['rtde_config_recipe_indoub']
47
+ opts['indoub'] = opts['rtde'].send_input_setup(doub_names,doub_types)
48
+ end
49
+
50
+ ### Setup output
51
+ if not opts['rtde'].send_output_setup(output_names, output_types,10)
52
+ puts 'Unable to configure output'
53
+ end
54
+ if not opts['rtde'].send_start
55
+ puts 'Unable to start synchronization'
56
+ end
57
+
58
+ ###Initialize all inputs
59
+ bit_names.each do |i|
60
+ opts['inbit'][i] = false
61
+ end
62
+ int_names.each do |i|
63
+ opts['inint'][i] = 0
64
+ end
65
+ doub_names.each do |i|
66
+ opts['indoub'][i] = 0.0
67
+ end
68
+ end #}}}
69
+
70
+ def self::protect_reconnect_run(opts) #{{{
71
+ tries = 0
72
+ begin
73
+ yield
74
+ rescue UR::Dash::Reconnect => e
75
+ puts e.message
76
+ tries += 1
77
+ if tries < 2
78
+ UREST::start_dash opts
79
+ retry
80
+ end
81
+ rescue UR::Psi::Reconnect => e
82
+ puts e.message
83
+ tries += 1
84
+ if tries < 2
85
+ UREST::start_psi opts
86
+ retry
87
+ end
88
+ end
89
+ end #}}}
90
+
91
+ def self::ssh_start(opts) #{{{
92
+ if opts['certificate']
93
+ opts['ssh'] = Net::SSH.start(opts['ipadress'], opts['username'], :port => opts['sshport'], :keys => [ opts['certificate'] ])
94
+ else
95
+ opts['ssh'] = opts['password'] ? Net::SSH.start(opts['ipadress'], opts['username'], :port => opts['sshport'], auth_methods: ['password'], password: opts['password']) : Net::SSH.start(opts['ipadress'], opts['username'], :port => opts['sshport'])
96
+ end
97
+ end #}}}
98
+
99
+ def self::download_program(opts,name) #{{{
100
+ counter = 0
101
+ begin
102
+ opts['ssh'].scp.download! name
103
+ rescue => e
104
+ counter += 1
105
+ UREST::ssh_start opts
106
+ retry if counter < 3
107
+ end
108
+ end #}}}
109
+
110
+ def self::upload_program(opts,name,program) #{{{
111
+ counter = 0
112
+ begin
113
+ opts['ssh'].scp.upload StringIO.new(program), File.join(opts['dir'],name)
114
+ rescue => e
115
+ counter += 1
116
+ UREST::ssh_start opts
117
+ retry if counter < 3
118
+ end
119
+ nil
120
+ end #}}}
121
+
122
+ def self::get_robot_programs(opts) #{{{
123
+ progs = []
124
+ begin
125
+ progs = opts['ssh'].exec!('ls ' + File.join(opts['dir'],'*.urp') + ' 2>/dev/null').split("\n")
126
+ progs.shift if progs[0] =~ /^bash:/
127
+ rescue => e
128
+ UREST::ssh_start opts
129
+ end
130
+ progs
131
+ end #}}}
132
+
133
+ def self::robotprogram_running?(opts) #{{{
134
+ opts['ps']== 'Playing'
135
+ end #}}}
136
+
137
+ def self::start_program(opts,fname) #{{{
138
+ unless UREST::robotprogram_running?(opts)
139
+ UREST::protect_reconnect_run(opts) do
140
+ opts['dash'].load_program(fname)
141
+ opts['dash'].start_program
142
+ end
143
+ end
144
+ end #}}}
145
+
146
+ class GetValue < Riddl::Implementation #{{{
147
+ def response
148
+ Riddl::Parameter::Complex.new('value','text/plain',@a[0])
149
+ end
150
+ end #}}}
151
+ class GetValues < Riddl::Implementation #{{{
152
+ def response
153
+ Riddl::Parameter::Complex.new('values','application/json',JSON::generate(@a[0]))
154
+ end
155
+ end #}}}
156
+
157
+ class GetProg < Riddl::Implementation #{{{
158
+ def response
159
+ opts = @a[0]
160
+ fname = File.join(opts['dir'],@r[-1] + '.urp')
161
+ if opts['progs'].include? fname
162
+ return Riddl::Parameter::Complex.new('file','application/octet-stream',UREST::download_program(opts,fname),File.basename(fname))
163
+ else
164
+ @status = 403
165
+ end
166
+ nil
167
+ end
168
+ end #}}}
169
+ class ForkProg < Riddl::Implementation #{{{
170
+ def response
171
+ opts = @a[0]
172
+ fname = File.join(opts['dir'],@r[-2])
173
+ if opts['progs'].include? fname + '.urp'
174
+ UREST::start_program(opts,fname)
175
+ else
176
+ @status = 403
177
+ end
178
+ nil
179
+ end
180
+ end #}}}
181
+ class WaitProg < Riddl::Implementation #{{{
182
+ def response
183
+ opts = @a[0]
184
+ fname = File.join(opts['dir'],@r[-2])
185
+ if opts['progs'].include? fname + '.urp'
186
+ if @h['CPEE_CALLBACK']
187
+ EM.defer do
188
+ UREST::start_program(opts,fname)
189
+ sleep 2
190
+ while UREST::robotprogram_running?(opts)
191
+ sleep 0.25
192
+ end
193
+ Typhoeus.put(@h['CPEE_CALLBACK'])
194
+ end
195
+ @headers << Riddl::Header.new("CPEE-CALLBACK", 'true')
196
+ else
197
+ UREST::start_program(opts,fname)
198
+ end
199
+ else
200
+ @status = 403
201
+ end
202
+ nil
203
+ end
204
+ end #}}}
205
+
206
+ class SetInt < Riddl::Implementation #{{{
207
+ def response
208
+ opts = @a[0]
209
+ value = @p[0].value
210
+ z = @r[-1].to_i
211
+ if z < 10
212
+ opts['inint']["input_int_register_" + z.to_s] = value.to_i
213
+ opts['rtde'].send(opts['inint'])
214
+ else
215
+ @status = 403
216
+ end
217
+ nil
218
+ end
219
+ end #}}}
220
+
221
+ def self::implementation(opts)
222
+ Proc.new do
223
+ startup do #{{{ # Init RTDE and DASH
224
+ opts['sshport'] ||= 22
225
+ opts['frequency'] ||= 0.5
226
+
227
+ opts['rtde_config'] ||= File.join(__dir__,'rtde.conf.xml')
228
+ opts['rtde_config_recipe_base'] ||= 'out'
229
+ opts['rtde_config_recipe_speed'] ||= 'speed'
230
+ opts['rtde_config_recipe_inbit'] ||= 'inbit'
231
+ opts['rtde_config_recipe_inint'] ||= 'inint'
232
+ opts['rtde_config_recipe_indoub'] ||= 'indoub'
233
+
234
+ opts['dash'] = nil
235
+ opts['rtde'] = nil
236
+ opts['programs'] = nil
237
+ opts['psi'] = nil
238
+
239
+ ### Connecting to universal robot
240
+ UREST::start_rtde opts
241
+ UREST::start_dash opts
242
+ UREST::start_psi opts
243
+
244
+ ### check if interfaces are ok
245
+ raise if !opts['dash'] || !opts['rtde'] || !opts['psi']
246
+
247
+ # Functionality for threading in loop
248
+ opts['doit_state'] = Time.now.to_i
249
+ opts['doit_progs'] = Time.now.to_i
250
+ opts['doit_rtde'] = Time.now.to_i
251
+
252
+ # Serious comment (we do the obvious stuff)
253
+ opts['speed'] = {}
254
+ opts['sn'] = opts['dash'].get_serial_number
255
+ opts['model'] = opts['dash'].get_robot_model
256
+
257
+ ### Manifest programs
258
+ opts['semaphore'] = Mutex.new
259
+ opts['progs'] = []
260
+ rescue Errno::ECONNREFUSED => e
261
+ print 'ECONNREFUSED: '
262
+ puts e.message
263
+ rescue UR::Dash::Reconnect => e
264
+ UREST::start_dash opts
265
+ puts e.message
266
+ puts e.backtrace
267
+ rescue UR::Psi::Reconnect => e
268
+ UREST::start_psi opts
269
+ puts e.message
270
+ puts e.backtrace
271
+ rescue => e
272
+ puts e.message
273
+ puts e.backtrace
274
+ raise
275
+ end # }}}
276
+
277
+ parallel do #{{{ periodic reading of data from UR
278
+ EM.add_periodic_timer(opts[:frequency]) do
279
+ if Time.now.to_i - 1 > opts['doit_state']
280
+ opts['doit_state'] = Time.now.to_i
281
+ opts['cp'] = opts['dash'].get_loaded_program
282
+ opts['rs'] = opts['dash'].get_program_state.split(' ')[0]
283
+ # update remote control state from dashboard server
284
+ opts['mo'] = opts['dash'].is_in_remote_control
285
+ opts['op'] = opts['dash'].get_operational_mode
286
+ end
287
+
288
+ if Time.now.to_i - 10 > opts['doit_progs']
289
+ opts['doit_progs'] = Time.now.to_i
290
+ Thread.new do
291
+ opts['semaphore'].synchronize do
292
+ # Content of thread
293
+ # check every 10 seconds for new programs
294
+ opts['progs'] = UREST::get_robot_programs(opts)
295
+ end unless opts['semaphore'].locked?
296
+ end
297
+ end
298
+
299
+ data = opts['rtde'].receive
300
+ if data
301
+ opts['ss'] = data['speed_scaling']
302
+
303
+ # State objects
304
+ opts['rm'] = UR::Rtde::ROBOTMODE[data['robot_mode']]
305
+ opts['sm'] = UR::Rtde::SAFETYMODE[data['safety_mode']]
306
+ opts['ps'] = UR::Rtde::PROGRAMSTATE[data['runtime_state']]
307
+
308
+ #speed slider or override
309
+ if opts['ov'] != (data['target_speed_fraction'] * 100).to_i
310
+ opts['ov'] = (data['target_speed_fraction'] * 100).to_i
311
+ end
312
+ else
313
+ if Time.now.to_i - 10 > opts['doit_rtde']
314
+ opts['doit_rtde'] = Time.now.to_i
315
+ UREST::start_rtde opts
316
+ end
317
+ end
318
+ rescue Errno::ECONNREFUSED => e
319
+ print 'ECONNREFUSED: '
320
+ puts e.message
321
+ rescue UR::Dash::Reconnect => e
322
+ UREST::start_dash opts
323
+ puts e.message
324
+ puts e.backtrace
325
+ rescue UR::Psi::Reconnect => e
326
+ UREST::start_psi opts
327
+ puts e.message
328
+ puts e.backtrace
329
+ rescue => e
330
+ puts e.message
331
+ puts e.backtrace
332
+ raise
333
+ end
334
+ end #}}}
335
+
336
+ on resource do
337
+ run GetValues, [ 'messages', 'registers', 'model', 'serialnumber', 'state', 'programs'] if get
338
+ on resource 'model' do #{{{
339
+ run GetValue, opts['model'] if get
340
+ end #}}}
341
+ on resource 'serialnumber' do #{{{
342
+ run GetValue, opts['sn'] if get
343
+ end #}}}
344
+ on resource 'state' do #{{{
345
+ run GetValues, {
346
+ :mode => opts['rm'].downcase,
347
+ :power => opts['rm'] == 'Running' ? 'on' : 'off',
348
+ :remote => opts['mo'] || 'false',
349
+ :program => File.basename(opts['cp'] || ''),
350
+ :program_state => (opts['rs'] || 'stopped').downcase,
351
+ :safety_mode => opts['sm'].downcase,
352
+ :speed => opts['ov'],
353
+ :speed_scaling => opts['ss']
354
+ } if get
355
+ on resource 'mode' do
356
+ run GetValue, opts['rm'].downcase if get
357
+ end
358
+ on resource 'power' do
359
+ run GetValue, opts['rm'] == 'Running' ? 'on' : 'off' if get
360
+ end
361
+ on resource 'remote' do
362
+ run GetValue, opts['mo'] || 'false' if get
363
+ end
364
+ on resource 'program' do
365
+ run GetValue, File.basename(opts['cp'] || '') if get
366
+ end
367
+ on resource 'program_state' do
368
+ run GetValue, (opts['rs'] || 'stopped').downcase if get
369
+ end
370
+ on resource 'safety_mode' do
371
+ run GetValue, opts['sm'].downcase if get
372
+ end
373
+ on resource 'speed' do
374
+ run GetValue, opts['ov'] if get
375
+ end
376
+ on resource 'speed_scaling' do
377
+ run GetValue, opts['ss'] if get
378
+ end
379
+ end #}}}
380
+ on resource 'programs' do
381
+ run GetValues, opts['progs'].map{|f|File.basename(f,'.urp')} if get
382
+ on resource do
383
+ run GetProg, opts if get
384
+ on resource 'fork' do
385
+ run ForkProg, opts if put
386
+ end
387
+ on resource 'wait' do
388
+ run WaitProg, opts if put
389
+ end
390
+ end
391
+ end
392
+ on resource 'registers' do
393
+ on resource 'input' do
394
+ on resource 'int' do
395
+ on resource '\d+' do
396
+ run SetInt, opts if put 'tint'
397
+ end
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
403
+ end
404
+
405
+ end