o3d3xx 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in o3d3xx.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Christian Ege
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # o3d3xx Ruby library
2
+
3
+ Ruby libray and tools for the ifm Efector O3D3xx series of Time of Flight (ToF) Cameras
4
+ This library adds support for the following interfaces:
5
+
6
+ - XML-RPC, provides an interface for camera configuration and set-up
7
+ - PCIC, provides result data and images
8
+ - SWUpdate, provides software updates
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'o3d3xx'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install o3d3xx
23
+
24
+ ## Usage
25
+
26
+ ### PCIC Client
27
+ The PCIC is the propritary process interface which is based on TC/IP
28
+
29
+ ```
30
+ pcic = O3D3XX::PCIC.new()
31
+ pcic.connect('172.25.125.26','50010')
32
+ pcic.transfer('p0')
33
+ pcic.async_trigger()
34
+ ```
35
+
36
+ ### Firmware Update
37
+
38
+ This assumes the device is already bootet in swupdate mode
39
+
40
+ ```
41
+ o3d3xx-fwupdate.rb -f ~/Downloads/Goldeneye_1.5.205-unstable.swu -I 172.25.125.26 -r
42
+ ```
43
+ The possible command line options are:
44
+ ```
45
+ Usage: o3d3xx-fwupdate.rb [options]
46
+ -f, --file SWU-IMG Image file to upload
47
+ -r, --reboot Force reboot to productive mode after all other action
48
+ -I, --ip-addr IP-ADDR Set TCP/IP address of target
49
+ -p, --start-productive Start productive system only without uploading file
50
+ -h, --help Display this help message
51
+ ```
52
+
53
+
54
+ ## Contributors
55
+
56
+ * [Christian Ege](https://github.com/graugans/)
57
+ * [Daniel Schnell](https://github.com/lumpidu)
58
+ * [Christoph Freundl](https://github.com/cfreundl)
59
+
60
+ ## Contributing
61
+
62
+ 1. Fork it
63
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
64
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
65
+ 4. Push to the branch (`git push origin my-new-feature`)
66
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env ruby
2
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
3
+ # Copyright:: Copyright (c) 2014 - 2016
4
+ # License:: MIT
5
+ #
6
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
7
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
8
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
9
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
10
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
11
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
12
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+
14
+
15
+ require 'timeout'
16
+ require 'optparse'
17
+ require 'o3d3xx'
18
+
19
+
20
+ def usage(opts)
21
+ puts opts
22
+ end
23
+
24
+
25
+ # Check sanity of given options
26
+ def check_options(opts, optparse)
27
+ rv = false
28
+ loop do
29
+ if (! opts[:ip_addr])
30
+ usage(optparse)
31
+ puts 'Required argument(s) -I/--ip-addr missing !'
32
+ break
33
+ end
34
+
35
+ if ((opts[:ip_addr]) && (! (opts[:start_productive] || opts[:file] || opts[:reboot])))
36
+ usage(optparse)
37
+ puts 'You have to specify at least one of -p/-f/-r!'
38
+ break
39
+ end
40
+
41
+ if (((opts[:start_productive]) && (opts[:file] || opts[:reboot]) ))
42
+ usage(optparse)
43
+ puts 'Options -p and -r/-f don\'t fit together'
44
+ break
45
+ end
46
+
47
+ rv = true
48
+ break
49
+ end
50
+ rv
51
+ end
52
+
53
+ options = {}
54
+ optparse = OptionParser.new do|opts|
55
+ opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
56
+
57
+ options[:file] = nil
58
+ opts.on( '-f', '--file SWU-IMG', 'Image file to upload' ) do |file|
59
+ options[:file] = file
60
+ end
61
+ options[:reboot] = false
62
+ opts.on( '-r', '--reboot', 'Force reboot to productive mode after all other action' ) do
63
+ options[:reboot] = true
64
+ end
65
+
66
+ options[:ip_addr] = nil
67
+ opts.on( '-I', '--ip-addr IP-ADDR', 'Set TCP/IP address of target' ) do |ip_addr|
68
+ options[:ip_addr] = ip_addr
69
+ end
70
+
71
+ options[:start_productive] = false
72
+ opts.on( '-p', '--start-productive', 'Start productive system only without uploading file' ) do
73
+ options[:start_productive] = true
74
+ end
75
+
76
+ opts.on( '-h', '--help', 'Display this help message' ) do
77
+ usage(opts)
78
+ exit 0
79
+ end
80
+ end
81
+
82
+ optparse.parse!(ARGV)
83
+
84
+ exit(1) unless check_options(options, optparse)
85
+
86
+ ip_addr = options[:ip_addr] || ENV['IP_ADDR'] || raise('Error: no ip address given!')
87
+
88
+ swupdate_settings = {
89
+ :host => ip_addr,
90
+ :port => 8080,
91
+ :ssh_ident => options[:identity]
92
+ }
93
+
94
+ swupdate = O3D3XX::Swupdate.new(swupdate_settings)
95
+
96
+ rv = 0
97
+
98
+ if options[:file]
99
+ if swupdate.read_status_empty()
100
+ print "Uploading swu image #{options[:file]} ..."
101
+ swupdate.upload_file(options[:file])
102
+ if swupdate.wait_for_status(O3D3XX::Swupdate::UPLOAD_SUCCESS, 60)
103
+ puts ' OK'
104
+ rv = 0
105
+ else
106
+ puts ' FAILED!'
107
+ rv = 1
108
+ end
109
+ else
110
+ puts 'Status of http server could not settle!?'
111
+ end
112
+ end
113
+
114
+ if (options[:start_productive] || options[:reboot]) && (rv == 0)
115
+ print 'Starting productive mode ...'
116
+ if swupdate.restart_device()
117
+ puts ' OK'
118
+ rv = 0
119
+ else
120
+ puts ' FAILED!'
121
+ end
122
+ end
123
+
124
+ exit(rv)
125
+ # EOF
data/lib/o3d3xx.rb ADDED
@@ -0,0 +1,16 @@
1
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
2
+ # Copyright:: Copyright (c) 2014 - 2016
3
+ # License:: MIT
4
+ #
5
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
6
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
7
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
8
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
9
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
10
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
11
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+
13
+ require "o3d3xx/version"
14
+ require "o3d3xx/xmlrpc"
15
+ require "o3d3xx/pcic"
16
+ require "o3d3xx/swupdate"
@@ -0,0 +1,277 @@
1
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
2
+ # Copyright:: Copyright (c) 2014 - 2016
3
+ # License:: MIT
4
+ #
5
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
6
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
7
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
8
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
9
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
10
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
11
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+
13
+
14
+
15
+ require 'socket'
16
+ require 'timeout'
17
+ require 'logger'
18
+
19
+ module O3D3XX
20
+
21
+ CHUNKTYPE = {
22
+ CT_USERDATA: 0,
23
+ CT_RADIAL_DISTANCE_IMAGE: 100,
24
+ CT_AMPLITUDE_IMAGE: 103,
25
+ CT_INTENSITY_IMAGE: 102,
26
+ CT_NORM_AMPLITUDE_IMAGE: 101,
27
+ CT_CARTESIAN_X_COMPONENT: 200,
28
+ CT_CARTESIAN_Y_COMPONENT: 201,
29
+ CT_CARTESIAN_Z_COMPONENT: 202,
30
+ CT_CARTESIAN_ALL: 203,
31
+ CT_UNIT_VECTOR_E1: 220,
32
+ CT_UNIT_VECTOR_E2: 221,
33
+ CT_UNIT_VECTOR_E3: 222,
34
+ CT_UNIT_VECTOR_ALL: 223,
35
+ CT_CONFIDENCE_IMAGE: 300,
36
+ CT_RAWDATA: 301,
37
+ CT_DIAGNOSTIC: 302,
38
+ CT_EXTRINSIC_CALIBRATION: 400,
39
+ CT_JSON_MODEL: 500,
40
+ }
41
+
42
+ CHUNKPIXELFORMAT = {
43
+ PF_FORMAT_8U: 0,
44
+ PF_FORMAT_8S: 1,
45
+ PF_FORMAT_16U: 2,
46
+ PF_FORMAT_16S: 3,
47
+ PF_FORMAT_32U: 4,
48
+ PF_FORMAT_32S: 5,
49
+ PF_FORMAT_32F: 6,
50
+ PF_FORMAT_64U: 7,
51
+ PF_FORMAT_64F: 8,
52
+ PF_FORMAT_16U2: 9,
53
+ PF_FORMAT_32F3: 10,
54
+ PF_FORMAT_12U: 11,
55
+ }
56
+
57
+ class PCIC
58
+ def initialize()
59
+ @remote = nil
60
+ @protocol = 3
61
+ @logger = Logger.new(STDOUT)
62
+ @logger.level = Logger::WARN
63
+ end
64
+ def connect(host="192.168.0.69",port="50010")
65
+ unless @remote == nil
66
+ disconnect()
67
+ end
68
+ @remote = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
69
+ @protocol = 3
70
+ begin
71
+ timeout(3) do
72
+ @remote.connect(Socket.pack_sockaddr_in(port, host))
73
+ end
74
+ rescue
75
+ @logger.fatal("Unable to connect to tcp://#{host}:#{port}")
76
+ @remote = nil
77
+ return false
78
+ end
79
+ #@protocol = detect_protocol()
80
+ #set_version(3)
81
+ end
82
+ def disconnect()
83
+ @remote.close()
84
+ @remote = nil
85
+ end
86
+ def detect_protocol()
87
+ proto = nil
88
+ begin
89
+ result = nil
90
+ @logger.debug("detect_protocol ")
91
+ begin
92
+ timeout(11) do
93
+ send_v3("V?")
94
+ result = @remote.recvfrom(20)
95
+ @logger.debug("Received: #{result} ")
96
+ end
97
+ rescue Timeout::Error => e
98
+ @logger.fatal("Timeout while receiving data occurred: #{e.message}")
99
+ return
100
+ end
101
+ test = result.join()
102
+ proto = nil
103
+ proto = 1 if test =~ /(\?)|(\d{2} \d{2} \d{2})\r\n/
104
+ proto = 2 if test =~ /\d{4}(\?)|(\d{2} \d{2} \d{2})\r\n/
105
+ proto = 3 if test =~ /\d{4}L\d{9}\r\n\d{4}/
106
+ proto = 4 if test =~ /L\d{9}\r\n(\?)|(\d{2} \d{2} \d{2})\r\n/
107
+ # If the device is in continuous mode and asynchronous messages are enabled
108
+ # we have to pull the complete frame out of the socket. Otherwise every consecutive
109
+ # PCIC call will fail due to the fact that we start in the middle of a frame.
110
+ if(proto == 3 )
111
+ ticket,length = test.match(/(\d{4})L(\d{9})\r\n/i).captures
112
+ timeout(5) do
113
+ # get the rest of the response
114
+ trailer = @remote.read(length.to_i-4)
115
+ end
116
+ # recv_v3 handles asynchronous messages so let it handle them.
117
+ dum = recv_v3() if '0000' == ticket
118
+ end
119
+ raise 'Unable to detect PCIC protocol' if proto == nil
120
+ rescue Exception => e
121
+ puts e.message
122
+ puts e.backtrace.inspect
123
+ raise('Timeout while detecting version')
124
+ proto = nil
125
+ end
126
+ proto
127
+ end
128
+ def set_version(version=3)
129
+ res = transfer("v%02d"%version)
130
+ @protocol = version
131
+ res
132
+ end
133
+ def version()
134
+ return transfer("V?")
135
+ end
136
+ def help()
137
+ transfer("H?")
138
+ end
139
+ def device_info()
140
+ transfer("G?")
141
+ end
142
+ def async_trigger()
143
+ return transfer("t")
144
+ end
145
+ def sync_trigger()
146
+ transfer("T?")
147
+ end
148
+
149
+ def get_lastimage(img = 3)
150
+ transfer("I%02d?"%img)
151
+ end
152
+
153
+
154
+ def async_result(state=false)
155
+ data = "p%d" % [ state ? 1 : 0 ]
156
+ res = true
157
+ transfer(data)
158
+ res
159
+ end
160
+ def transfer(data)
161
+ raise 'Protocol #{@protocol} currently not supported' if @protocol == 1
162
+ raise 'Protocol #{@protocol} currently not supported' if @protocol == 2
163
+ return transfer_v3(data) if @protocol == 3
164
+ raise 'Protocol #{@protocol} currently not supported' if @protocol == 4
165
+ end
166
+ def send_v3(data)
167
+ ticket = "%04d" % [1000+rand(8999)]
168
+ length = "%09d" % [data.bytesize+6]
169
+ sendbuf = "#{ticket}L#{length}\r\n#{ticket}#{data}\r\n"
170
+ @logger.info("Request (#{@protocol}) : %{sendbuf}" % {:sendbuf => escape_pcic_string(sendbuf)})
171
+ timeout(5) do
172
+ @remote.send(sendbuf,0)
173
+ end
174
+ end
175
+ def transfer_v3(data,omit_async=true)
176
+ send_v3(data)
177
+ continue = true
178
+ response = nil
179
+ begin
180
+ async,ticket,response = recv_v3()
181
+ continue = false; continue = true if async and omit_async
182
+ end while continue
183
+ return response
184
+ end
185
+
186
+ def recv_v3()
187
+ data = nil
188
+ async = false
189
+ trailer = nil
190
+ # The V3 response shall look like this
191
+ # <4 Byte ticket >L<9 Byte Length in dec>\r\n<4 Byte ticket ><Response>\r\n
192
+ # receive <ticket>L<length>\r\n to check if we in sync and how much
193
+ # data we have to receive in the next step
194
+ timeout(5) do
195
+ data = @remote.read(16)
196
+ end
197
+ @logger.info("Header : %{res}" % {:res => escape_pcic_string(data)})
198
+ begin
199
+ ticket,length = data.match(/(\d{4})L(\d{9})\r\n/i).captures
200
+ rescue
201
+ @logger.fatal(">>> rescue: #{data} <<<")
202
+ end
203
+ size = length.to_i
204
+ @logger.info("Length : %d " % size )
205
+ if ticket == "0000"
206
+ @logger.info('ASYNC reply Caught !!!!')
207
+ async = true
208
+ else
209
+ async = false
210
+ end
211
+ timeout(5) do
212
+ trailer = @remote.read(size)
213
+ end
214
+ if size < 80 and trailer.length < 80
215
+ @logger.debug("Trailer : %{res} <<<<" % {:res => escape_pcic_string(trailer)})
216
+ else
217
+ escaped = escape_pcic_string(trailer)
218
+ @logger.debug("Trailer : %{start} [...] %{stop}" % {:start => escaped[0..20], :stop => escaped[-20..-1]})
219
+ end
220
+ ticket_trailer = trailer[0..3]
221
+ unless ticket_trailer == ticket
222
+ raise "Ticket mismatch. Header ticket: #{ticket} trailer ticket: #{ticket_trailer}"
223
+ end
224
+ # remove ticket and trailing \r\n
225
+ return async,ticket,trailer[4..-3]
226
+ end
227
+
228
+ def escape_pcic_string(data)
229
+ result = ""
230
+ result = data.gsub("\n","\\\\n").gsub("\r","\\\\r") unless data == nil
231
+ end
232
+ end
233
+
234
+ # Handle PCIC binary/image chunks
235
+ class Chunk
236
+ def self.parse(data)
237
+ chunk_header = data.unpack('L<9*')
238
+ result = {
239
+ :CHUNK_TYPE => chunk_header[0],
240
+ :CHUNK_SIZE => chunk_header[1],
241
+ :HEADER_SIZE => chunk_header[2],
242
+ :HEADER_VERSION => chunk_header[3],
243
+ :IMAGE_WIDTH => chunk_header[4],
244
+ :IMAGE_HEIGTH => chunk_header[5],
245
+ :PIXEL_FORMAT => chunk_header[6],
246
+ :TIME_STAMP => chunk_header[7],
247
+ :FRAME_COUNT => chunk_header[8],
248
+ :PIXEL_DATA => data[chunk_header[2]...chunk_header[1]],
249
+ }
250
+ end
251
+ def self.info(chunk)
252
+ info = Array.new
253
+ info.push("CHUNK type : #{chunk[:CHUNK_TYPE]}")
254
+ info.push("CHUNK size : #{chunk[:CHUNK_SIZE]}")
255
+ info.push("CHUNK header size : #{chunk[:HEADER_SIZE]}")
256
+ info.push("CHUNK version : #{chunk[:HEADER_VERSION]}")
257
+ info.push("CHUNK height : #{chunk[:IMAGE_HEIGTH]}")
258
+ info.push("CHUNK width : #{chunk[:IMAGE_WIDTH]}")
259
+ info.push("CHUNK format : #{chunk[:PIXEL_FORMAT]}")
260
+ info.push("CHUNK time stamp : #{chunk[:TIME_STAMP]}")
261
+ info.push("CHUNK frame count : #{chunk[:FRAME_COUNT]}")
262
+ info.push("CHUNK data size : #{chunk[:PIXEL_DATA].length}")
263
+ end
264
+
265
+ def self.parseChunkArray(data)
266
+ result = Array.new
267
+ offset = 0
268
+ while offset < data.size
269
+ chunk = self.parse(data[offset..data.size])
270
+ result << chunk
271
+ offset += chunk[:CHUNK_SIZE]
272
+ end
273
+ return result
274
+ end
275
+
276
+ end
277
+ end
@@ -0,0 +1,142 @@
1
+
2
+ # The module provides access to the swupdate process
3
+ #
4
+ # swupdate - Software Update for Embedded Systems provides a web ui to upload
5
+ # an installation file to an embedded target. This can also be used for an automated
6
+ # installation process.
7
+ #
8
+ #
9
+ # For manual access an web ui is provided
10
+ # Web-Browser: http://<ip of the device>:8080
11
+ #
12
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
13
+ # Copyright:: Copyright (c) 2014 - 2016
14
+ # License:: MIT
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+
25
+ require 'timeout'
26
+ require 'uri'
27
+ require 'net/http'
28
+ require 'json'
29
+
30
+ module O3D3XX
31
+
32
+ class Swupdate
33
+ UPLOAD_SUCCESS={"Status"=>"3", "Msg"=>"SWUPDATE successful !", "Error"=>"0"}
34
+
35
+ def initialize (settings={})
36
+ raise 'No host name given !' if settings[:host].nil?
37
+ @config = {
38
+ :host => settings[:host],
39
+ }
40
+ if settings[:port].nil?
41
+ @config[:port] = 8080
42
+ else
43
+ @config[:port] = settings[:port]
44
+ end
45
+ @base_uri = "http://#{@config[:host]}:#{@config[:port]}"
46
+ end
47
+
48
+ # Uploads a file to swupdate system. So far this file has
49
+ # to be a swu image file.
50
+ # This call is asynchronous to the following installation
51
+ # procedure, i.e. one has to poll for installation finish
52
+ # via query_status()
53
+ #
54
+ # @param filename filename of swu image to install on target
55
+ #
56
+ def upload_file(filename)
57
+ raise 'Invalid file name given !' unless File.exist?(filename)
58
+ uri = URI.parse("#{@base_uri}/handle_post_request")
59
+ http = Net::HTTP.new(uri.host, uri.port)
60
+ request = Net::HTTP::Post.new(uri.request_uri)
61
+ request.body = File.read(filename)
62
+ request['Content-Type'] = 'application/octet-stream'
63
+ request['Connection'] = 'keep-alive'
64
+ request['X_FILENAME'] = "#{File.basename(filename)}"
65
+ http.request(request)
66
+ end
67
+
68
+ # Reads status queue empty on http server, i.e.
69
+ # will return status from server until two consecutive
70
+ # status values are identical.
71
+ #
72
+ # @param timeout Time to wait for http server to settle
73
+ #
74
+ def read_status_empty(timeout = 5)
75
+ rv = false
76
+ Timeout::timeout(timeout) do
77
+ loop do
78
+ rv1 = query_status
79
+ rv2 = query_status
80
+ break if rv1 == rv2
81
+ end
82
+ rv = true
83
+ end
84
+ rv
85
+ end
86
+
87
+ # Query status from http server
88
+ #
89
+ # @return json representation of http status
90
+ # e.g. {"Status"=>"0", "Msg"=>"", "Error"=>"0"}
91
+ #
92
+ def query_status()
93
+ rv = ''
94
+ uri = URI.parse("#{@base_uri}/getstatus.json")
95
+ begin
96
+ response = Net::HTTP.get_response(uri)
97
+ rv = JSON.parse(response.body)
98
+ #Net::HTTP.get_print(uri)
99
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError,
100
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
101
+ end
102
+ #puts rv
103
+ rv
104
+ end
105
+
106
+ # Waits for specific status of http server with max. timeout
107
+ #
108
+ # @param status_hash Hash of status values, e.g.
109
+ # {"Status"=>"0", "Msg"=>"", "Error"=>"0"}
110
+ #
111
+ # @param timeout Time in seconds to wait for given status to be returned
112
+ # from http server
113
+ #
114
+ def wait_for_status(status_hash, timeout)
115
+ rv = false
116
+ Timeout::timeout(timeout) do
117
+ loop do
118
+ rv1 = query_status
119
+ break if rv1 == status_hash
120
+ # Print error messages
121
+ if rv1['Status'] && (rv1['Status'] == '4')
122
+ puts rv1['Msg'] if rv1['Msg']
123
+ end
124
+ sleep 1
125
+ end
126
+ rv = true
127
+ end
128
+ rv
129
+ end
130
+
131
+
132
+ # Restarts device
133
+ def restart_device()
134
+ uri = URI.parse("#{@base_uri}/reboot_to_live")
135
+ http = Net::HTTP.new(uri.host, uri.port)
136
+ request = Net::HTTP::Post.new(uri.request_uri)
137
+ request['Connection'] = 'keep-alive'
138
+ http.request(request)
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,15 @@
1
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
2
+ # Copyright:: Copyright (c) 2014 - 2016
3
+ # License:: MIT
4
+ #
5
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
6
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
7
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
8
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
9
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
10
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
11
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+
13
+ module O3D3XX
14
+ VERSION = "0.0.1"
15
+ end
@@ -0,0 +1,71 @@
1
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
2
+ # Copyright:: Copyright (c) 2014 - 2016
3
+ # License:: MIT
4
+ #
5
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
6
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
7
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
8
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
9
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
10
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
11
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12
+
13
+
14
+ require 'xmlrpc/client'
15
+ require 'timeout'
16
+
17
+ class XmlRPCBase
18
+
19
+ attr_reader :rpc_cl
20
+
21
+ # Initializes XMLRPC connection. Needs connection and
22
+ # Request parameters via given settings hash
23
+ #
24
+ # @param settings hash of settings: :host, :port, :path
25
+ #
26
+ def initialize(settings={})
27
+ raise 'No host name given !' if settings[:host].nil?
28
+ raise 'No port given !' if settings[:port].nil?
29
+ raise 'No endpoint given !' if settings[:path].nil?
30
+ @config = {
31
+ :host => settings[:host],
32
+ :port => settings[:port],
33
+ :path => '/api/rpc/v1/' + settings[:path]
34
+ }
35
+
36
+ @rpc_cl = XMLRPC::Client.new(@config[:host],
37
+ @config[:path],
38
+ @config[:port], nil, nil,
39
+ nil, nil, nil, 20) # 20 seconds connection timeout
40
+ #dump
41
+ end
42
+
43
+
44
+ # Calls any method via xmlrpc
45
+ def method_missing(meth, *args)
46
+ arg = args
47
+ begin
48
+ @rpc_cl.call_async(meth.to_s, *arg)
49
+ rescue XMLRPC::FaultException => e
50
+ if e.message.include? 'method not found'
51
+ super
52
+ else
53
+ raise
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ # Returns configuration object
60
+ def getConfig
61
+ return @config
62
+ end
63
+
64
+
65
+ # Dumps configuration
66
+ def dump
67
+ puts "Host: #{@config[:host]}"
68
+ puts "Port: #{@config[:port]}"
69
+ puts "URL : #{@config[:path]}"
70
+ end
71
+ end
@@ -0,0 +1,160 @@
1
+ # The module provides a transparent XML-RPC proxy for access to the O3D3XX
2
+ # class of devices.
3
+ # To get an rough overview of the API connect to the device using your preferred
4
+ # Web-Browser: http://192.168.0.69/api/rpc/v1/com.ifm.efector/
5
+ #
6
+ # Author:: Christian Ege (mailto:k4230r6@gmail.com)
7
+ # Copyright:: Copyright (c) 2014 - 2016
8
+ # License:: MIT
9
+ #
10
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
11
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
12
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
14
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
15
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
16
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+
18
+
19
+ require 'o3d3xx/xml_rpc_base'
20
+
21
+ module O3D3XX
22
+
23
+ # This class encapsulates the XML-RPC network configuration settings
24
+ class NetworkProxy < XmlRPCBase
25
+ def initialize(proxy)
26
+ config = proxy.getConfig()
27
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/edit/device/network/"
28
+ super(:host => config[:host], :port => config[:port], :path => path)
29
+ end
30
+ end
31
+
32
+ module Application
33
+ TriggerMode = {
34
+ :FREE_RUN => 1,
35
+ :PCIC => 2,
36
+ :POSITIVE_EDGE => 3,
37
+ :NEGATIVE_EDGE => 4,
38
+ :BOTH_EDGE => 5,
39
+ }
40
+ end
41
+
42
+ # This class encapsulates the XML-RPC imager access
43
+ class ImagerProxy < XmlRPCBase
44
+ def initialize(proxy)
45
+ config = proxy.getConfig()
46
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/edit/application/imager_001" # On O3D3xx only imager_001 is available
47
+ super(:host => config[:host], :port => config[:port], :path => path)
48
+ end
49
+ end
50
+
51
+
52
+ # This class encapsulates the XML-RPC application access
53
+ class ApplicationProxy < XmlRPCBase
54
+ def initialize(proxy)
55
+ config = proxy.getConfig()
56
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/edit/application/"
57
+ super(:host => config[:host], :port => config[:port], :path => path)
58
+ @img_proxy = O3D3xx::ImagerProxy.new(proxy)
59
+ end
60
+
61
+ def getImagerProxy()
62
+ @img_proxy
63
+ end
64
+
65
+ end
66
+
67
+ # This class encapsulates the XML-RPC device access
68
+ class DeviceProxy < XmlRPCBase
69
+ def initialize(proxy)
70
+ config = proxy.getConfig()
71
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/edit/device/"
72
+ super(:host => config[:host], :port => config[:port], :path => path)
73
+ @net_proxy = O3D3xx::NetworkProxy.new(proxy)
74
+ end
75
+
76
+ def getNetworkProxy()
77
+ @net_proxy
78
+ end
79
+ end
80
+
81
+ # This class encapsulates the XML-RPC Edit-Mode access
82
+ class EditProxy < XmlRPCBase
83
+ def initialize(proxy)
84
+ config = proxy.getConfig()
85
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/edit/"
86
+ super(:host => config[:host], :port => config[:port], :path => path)
87
+ @proxy = proxy
88
+ @device = O3D3xx::DeviceProxy.new(@proxy)
89
+ @application = nil
90
+ end
91
+
92
+ def getApplicationProxy(index)
93
+ self.editApplication(index)
94
+ @application = O3D3xx::ApplicationProxy.new(@proxy)
95
+ end
96
+
97
+ def closeApplication()
98
+ self.stopEditingApplication() unless @application == nil
99
+ @application = nil
100
+ end
101
+
102
+ def getDeviceProxy()
103
+ @device
104
+ end
105
+ end
106
+
107
+ module Session
108
+ OperationMode = {
109
+ :RUN => 0,
110
+ :EDIT => 1,
111
+ }
112
+ end
113
+
114
+
115
+ # This class encapsulates the XML-RPC session access
116
+ class SessionProxy < XmlRPCBase
117
+ def initialize(proxy)
118
+ config = proxy.getConfig()
119
+ path = "com.ifm.efector/session_#{proxy.getSessionID()}/"
120
+ super(:host => config[:host], :port => config[:port], :path => path)
121
+ @edit = nil
122
+ @proxy = proxy
123
+ end
124
+
125
+ def getEditObjectProxy()
126
+ if nil == @edit
127
+ self.setOperatingMode(O3D3xx::Session::OperationMode[:EDIT])
128
+ @edit = O3D3xx::EditProxy.new(@proxy)
129
+ end
130
+ @edit
131
+ end
132
+ def closeEdit()
133
+ self.setOperatingMode(O3D3xx::Session::OperationMode[:RUN]) unless @edit == nil
134
+ @edit = nil
135
+ end
136
+ end
137
+
138
+ # This class encapsulates the XML-RPC general o3d3xx access
139
+ class O3D3xxProxy < XmlRPCBase
140
+
141
+ def initialize(host='192.168.0.69', port=80)
142
+ super(:host => host, :port => port, :path => 'com.ifm.efector/')
143
+ @session = nil
144
+ end
145
+
146
+ def getSessionProxy(*args)
147
+ @session_id = requestSession(*args)
148
+ @session = O3D3xx::SessionProxy.new(self)
149
+ end
150
+ def closeSession()
151
+ @session_id = nil
152
+ @session.cancelSession() unless @session == nil
153
+ @session = nil
154
+ end
155
+
156
+ def getSessionID
157
+ return @session_id
158
+ end
159
+ end
160
+ end
data/o3d3xx.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'o3d3xx/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "o3d3xx"
8
+ spec.version = O3D3XX::VERSION
9
+ spec.authors = ["Christian Ege"]
10
+ spec.email = ["k4230r6@gmail.com"]
11
+ spec.description = "Ruby interface for ifm efector O3d3xx"
12
+ spec.summary = "Ruby interface for ifm efector O3d3xx"
13
+ spec.homepage = "https://github.com/graugans/ruby-o3d3xx"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.required_ruby_version = ">= 1.9.3"
24
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: o3d3xx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christian Ege
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-03-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Ruby interface for ifm efector O3d3xx
47
+ email:
48
+ - k4230r6@gmail.com
49
+ executables:
50
+ - o3d3xx-fwupdate.rb
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - .gitignore
55
+ - Gemfile
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - bin/o3d3xx-fwupdate.rb
60
+ - lib/o3d3xx.rb
61
+ - lib/o3d3xx/pcic.rb
62
+ - lib/o3d3xx/swupdate.rb
63
+ - lib/o3d3xx/version.rb
64
+ - lib/o3d3xx/xml_rpc_base.rb
65
+ - lib/o3d3xx/xmlrpc.rb
66
+ - o3d3xx.gemspec
67
+ homepage: https://github.com/graugans/ruby-o3d3xx
68
+ licenses:
69
+ - MIT
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: 1.9.3
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 1.8.23
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Ruby interface for ifm efector O3d3xx
92
+ test_files: []