axis-netcam 0.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.
data/CHANGELOG.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.1.0 :: 2007-07-26
2
+
3
+ * First public release.
data/LICENSE.txt ADDED
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
data/Manifest.txt ADDED
@@ -0,0 +1,11 @@
1
+ CHANGELOG.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/axis-netcam.rb
7
+ lib/axis-netcam/camera.rb
8
+ lib/axis-netcam/version.rb
9
+ setup.rb
10
+ test/axis-netcam_test.rb
11
+ test/test_helper.rb
data/README.txt ADDED
@@ -0,0 +1,50 @@
1
+ = AxisNetcam
2
+
3
+ <i>Copyright 2007 Urbacon Ltd.</i>
4
+
5
+ For info and downloads please see:
6
+
7
+ http://rubyforge.org/projects/axis-netcam/
8
+
9
+ You can contact the author at:
10
+
11
+ matt at roughest dot net
12
+
13
+
14
+ <b>AxisNetcam provides a Ruby interface for interacting with network cameras
15
+ from Axis Communications.</b>
16
+
17
+ Note that only a subset of the full Axis API is currently implemented, but the most
18
+ useful functionality is in place.
19
+
20
+ Example usage:
21
+
22
+ require 'rubygems' # (if installed as a gem)
23
+ require 'axis-netcam'
24
+
25
+ c = AxisNetcam::Camera.new(:hostname => '192.168.2.25',
26
+ :username => 'root', :password => 'pass')
27
+ c.tilt(90)
28
+ c.zoom(500)
29
+ f = File.open('/tmp/test.jpg', 'wb')
30
+ f.bin
31
+ f.write(c.snapshot_jpeg)
32
+ f.close
33
+
34
+ For more information about using a Camera object, see the AxisNetcam::Camera RDocs.
35
+
36
+
37
+ ------
38
+
39
+ axis-netcam is free software; you can redistribute it and/or modify
40
+ it under the terms of the GNU Lesser General Public License as published by
41
+ the Free Software Foundation; either version 3 of the License, or
42
+ (at your option) any later version.
43
+
44
+ axis-netcam is distributed in the hope that it will be useful,
45
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
46
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47
+ GNU General Public License for more details.
48
+
49
+ You should have received a copy of the GNU Lesser General Public License
50
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+ include FileUtils
12
+ require File.join(File.dirname(__FILE__), 'lib', 'axis-netcam', 'version')
13
+
14
+ AUTHOR = "Matt Zukowski" # can also be an array of Authors
15
+ EMAIL = "matt@roughest.net"
16
+ DESCRIPTION = "Provides a Ruby interface for interacting with network cameras from Axis Communications."
17
+ GEM_NAME = "axis-netcam" # what ppl will type to install your gem
18
+ RUBYFORGE_PROJECT = "axis-netcam" # The unix name for your project
19
+ HOMEPATH = "http://axis-netcam.rubyforge.org"
20
+
21
+
22
+ NAME = "axis-netcam"
23
+ REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
+ VERS = ENV['VERSION'] || (AxisNetcam::VERSION::STRING + (REV ? ".#{REV}" : ""))
25
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
26
+ RDOC_OPTS = ['--quiet', '--title', "axis-netcam documentation",
27
+ "--opname", "index.html",
28
+ "--line-numbers",
29
+ "--main", "README.txt",
30
+ "--inline-source"]
31
+
32
+ class Hoe
33
+ def extra_deps
34
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
35
+ end
36
+ end
37
+
38
+ # Generate all the Rake tasks
39
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
40
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
41
+ p.author = AUTHOR
42
+ p.description = DESCRIPTION
43
+ p.email = EMAIL
44
+ p.summary = DESCRIPTION
45
+ p.url = HOMEPATH
46
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
47
+ p.test_globs = ["test/**/*_test.rb"]
48
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
49
+
50
+ # == Optional
51
+ #p.changes - A description of the release's latest changes.
52
+ #p.extra_deps - An array of rubygem dependencies.
53
+ #p.spec_extras - A hash of extra values to set in the gemspec.
54
+ end
@@ -0,0 +1,391 @@
1
+ require 'net/http'
2
+ require 'cgi'
3
+ require 'logger'
4
+
5
+ module AxisNetcam
6
+ # The AxisNetcam::Camera class represents an Axis network camera.
7
+ #
8
+ # To control a camera, first create an instance of a Camera object and then call
9
+ # its methods. For example:
10
+ #
11
+ # require 'axis-netcam/camera'
12
+ #
13
+ # c = AxisNetcam::Camera.new(:hostname => '192.168.2.25',
14
+ # :username => 'root', :password => 'pass')
15
+ #
16
+ # puts c.get_position[:tilt]
17
+ # c.tilt(90)
18
+ # puts c.camera_video_uri
19
+ #
20
+ # For a list of available methods, see the RDocs for included modules:
21
+ # AxisNetcam::Camera::PTZ :: for point-tilt-zoom control
22
+ # AxisNetcam::Camera::Users :: for user management
23
+ # AxisNetcam::Camera::Video :: for obtaining video/image data
24
+ # AxisNetcam::Camera::Info :: for diagnostic/status information
25
+ #
26
+ # Note that by default, AxisNetcam::Camera will log quite verbosely to stdout.
27
+ # See AxisNetcam::Camera#new for info on how to change this behaviour.
28
+ class Camera
29
+
30
+ # The HTTP network connection to the camera.
31
+ @@http = nil
32
+
33
+ attr_reader :hostname, :logger
34
+
35
+ # Create a new Camera object.
36
+ # Options must include a :hostname, :username, and :password.
37
+ # A Logger object can also be specified to the :logger option, otherwise
38
+ # logging will be done to STDOUT.
39
+ def initialize(options)
40
+ @hostname = options[:hostname] or raise(ArgumentError, "Must specify a hostname")
41
+ @username = options[:username] or raise(ArgumentError, "Must specify a username")
42
+ @password = options[:password] or raise(ArgumentError, "Must specify a password")
43
+ @log = options[:logger] || Logger.new(STDOUT)
44
+ end
45
+
46
+
47
+ # Functionality related to the camera's point, zoom, and tilt.
48
+ # Not all camera models support this.
49
+ module PTZ
50
+
51
+ # Returns the camera's current absolute position as a hash with :pan,
52
+ # :tilt, and :zoom elements.
53
+ #
54
+ # :tilt and :pan are specified in degrees from center (for example, -170
55
+ # to 170 for :pan, depending on your camera's physical capabilities), and
56
+ # zoom is an integer factor, with 0 being no zoom (widest possible
57
+ # viewing angle) and 10000 approaching a camera's maximum zoom. Again,
58
+ # this depends on your camera's capabilities.
59
+ def get_position
60
+ str = axis_action("com/ptz.cgi", {'query' => 'position'}).split
61
+ pan = str[0].split("=").last
62
+ tilt = str[1].split("=").last
63
+ zoom = str[2].split("=").last
64
+
65
+ {:pan => pan, :tilt => tilt, :zoom => zoom}
66
+ end
67
+
68
+ # Simultanously pans, tilts, and zooms the camera.
69
+ # The argument is a hash that can have any of 'pan', 'tilt', and 'zoom'
70
+ # elements, each specifying the desired value for the movement.
71
+ #
72
+ # Example:
73
+ #
74
+ # camera.ptz(:pan => 60, :zoom => 8000)
75
+ #
76
+ def ptz(ptz = {})
77
+ axis_action('com/ptz.cgi', ptz)
78
+ end
79
+
80
+ # Tilts the camera (up/down) to the given absolute position in degrees.
81
+ def tilt(d)
82
+ axis_action("com/ptz.cgi", {'tilt' => d})
83
+ end
84
+
85
+ # Pans the camera (left/right) to the given absolute position in degrees.
86
+ def pan(d)
87
+ axis_action("com/ptz.cgi", {'pan' => d})
88
+ end
89
+
90
+ # Zooms the camera (in/out) to the given zoom factor.
91
+ def zoom(n)
92
+ axis_action("com/ptz.cgi", {'zoom' => n})
93
+ end
94
+
95
+ # Zooms the camera (in/out) to the given zoom factor.
96
+ def center_on(x,y)
97
+ axis_action("com/ptz.cgi", {'center' => "#{x},#{y}"})
98
+ end
99
+
100
+ # Returns and array with the names of the preset positions saved in the camera.
101
+ def get_preset_positions
102
+ str = axis_action("com/ptz.cgi", {'query' => 'presetposall'})
103
+ positions = []
104
+ str.each do |line|
105
+ line =~ /presetposno\d+=(.*)/
106
+ positions << $~[1].strip if $~ && $~[1]
107
+ end
108
+ positions
109
+ end
110
+
111
+ def point_at_preset_name(preset_name)
112
+ axis_action("com/ptz.cgi", {'gotoserverpresetname' => preset_name})
113
+ end
114
+ end
115
+ include PTZ
116
+
117
+ # Functionality related to managing the camera's user and group lists.
118
+ module Users
119
+
120
+ # Adds a new user based on the given hash.
121
+ # The hash must have :username, :password, and :comment elements.
122
+ def add_user(user)
123
+ params = {
124
+ 'action' => 'add',
125
+ 'user' => user[:username],
126
+ 'pwd' => user[:password],
127
+ 'comment' => user[:comment],
128
+ 'grp' => 'users',
129
+ 'sgrp' => 'axview'
130
+ }
131
+ axis_action("admin/pwdgrp.cgi", params)
132
+ end
133
+
134
+ # Updates a user based on the given hash.
135
+ # username :: specifies the username of the user to update.
136
+ # attributes :: must be a hash with new values for :password and :comment.
137
+ def update_user(username, attributes)
138
+ params = {
139
+ 'action' => 'update',
140
+ 'user' => username,
141
+ 'pwd' => user[:password],
142
+ 'comment' => user[:comment],
143
+ 'grp' => 'users',
144
+ 'sgrp' => 'axview'
145
+ }
146
+ axis_action("admin/pwdgrp.cgi", params)
147
+ end
148
+
149
+ # Same as add_user, but updates the user account instead of creating it
150
+ # if it already exists.
151
+ def add_or_update_user(user)
152
+ if user_exists?(user[:username], user)
153
+ update_user(user[:username], user)
154
+ else
155
+ add_user(user)
156
+ end
157
+ end
158
+
159
+ # Deletes the user with the given username.
160
+ def remove_user(username)
161
+ params = {
162
+ 'action' => 'remove',
163
+ 'user' => username,
164
+ }
165
+ axis_action("admin/pwdgrp.cgi", params)
166
+ end
167
+
168
+ # Returns an array with the usernames of the users on the camera.
169
+ def users
170
+ str = axis_action("admin/pwdgrp.cgi", {'action' => 'get'})
171
+ return false unless str
172
+ usernames = []
173
+ str.split.collect do |u|
174
+ u =~ /.*?="(.*)"/
175
+ if $~
176
+ usernames += $~[1].split(',')
177
+ else
178
+ usernames += []
179
+ end
180
+ end
181
+ usernames.uniq!
182
+ end
183
+
184
+ # Checks if the user with the given username exists.
185
+ def user_exists?(username)
186
+ users.include? username
187
+ end
188
+ end
189
+ include Users
190
+
191
+
192
+ # Functionality related to the camera's video/image capabilities.
193
+ module Video
194
+ # Returns JPEG data with a snapshot of the current camera image.
195
+ # size :: optionally specifies the image size -- one of :full, :medium,
196
+ # or :thumbnail, or a string specifying the WxH dimensions like "640x480".
197
+ #
198
+ # To dump the JPEG data to a file, you can do something like this:
199
+ #
200
+ # # Instantiate c as a AxisNetcam::Camera object, and then...
201
+ # data = c.snapshot_jpeg
202
+ # f = File.open('/tmp/test.jpg', 'wb')
203
+ # f.binmode
204
+ # f.write(data)
205
+ # f.close
206
+ #
207
+ def snapshot_jpeg(size = :full)
208
+ case size
209
+ when :thumbnail
210
+ resolution = "160x120"
211
+ when :medium
212
+ resolution = "480x360"
213
+ when :full
214
+ resolution = "640x480"
215
+ else
216
+ resolution = size
217
+ end
218
+
219
+ axis_action("jpg/image.cgi",
220
+ {'resolution' => resolution, 'text' => '0'})
221
+ end
222
+
223
+ # Returns the URI for accessing the camera's streaming Motion JPEG video.
224
+ # size :: optionally specifies the image size -- one of :full, :medium,
225
+ # or :thumbnail, or a string specifying the WxH dimensions like "640x480".
226
+ def camera_video_uri(size = :full)
227
+ case size
228
+ when :thumbnail
229
+ resolution = "160x120"
230
+ when :medium
231
+ resolution = "480x360"
232
+ when :full
233
+ resolution = "640x480"
234
+ end
235
+ "http://#{hostname}/axis-cgi/mjpg/video.cgi?resolution=#{resolution}&text=0"
236
+ end
237
+ end
238
+ include Video
239
+
240
+ # Functionality related to obtaining information about the camera, such as its
241
+ # status, model number, etc.
242
+ module Info
243
+ # Returns the raw camera server report.
244
+ #
245
+ # The report is a string with info about the camera's status and parameters,
246
+ # and differs considerably from model to model.
247
+ #
248
+ # If you have the Easycache Rails plugin installed, report data will be
249
+ # cached unless the force_refresh argument is true. This is done to help
250
+ # improve performance, as the server_report method is often called by other
251
+ # methods to retrieve various camera info.
252
+ def server_report(force_refresh = false)
253
+ if Object.const_defined? "Easycache"
254
+ if force_refresh
255
+ Easycache.write("#{hostname}_server_report",
256
+ @report = axis_action("admin/serverreport.cgi"))
257
+ else
258
+ @report ||= Easycache.cache("#{hostname}_server_report") do
259
+ axis_action("admin/serverreport.cgi")
260
+ end
261
+ end
262
+ else
263
+ axis_action("admin/serverreport.cgi")
264
+ end
265
+ end
266
+
267
+ # Returns the camera's model name.
268
+ def model
269
+ begin
270
+ server_report =~ /prodshortname\s*=\s*"(.*?)"/i
271
+ $~[1]
272
+ rescue RemoteTimeout, RemoteError => e
273
+ @error = e
274
+ nil
275
+ end
276
+ end
277
+
278
+ # Returns a code describing the camera's current status.
279
+ # The codes are as follows:
280
+ #
281
+ # :ok :: Camera is responding as expected.
282
+ # :down :: Camera is not responding at all (connection is timing out).
283
+ # :error :: Camera responded with an error.
284
+ # :no_server_report :: Camera responded but for some reason did not return a server report.
285
+ #
286
+ # Calling this method updates the @status_message attribute with some more
287
+ # detailed information about the camera's status (for example, the full error message in
288
+ # case of an error).
289
+ def status_code
290
+ begin
291
+ if self.server_report(true).empty?
292
+ @status_message = "Server did not send report"
293
+ :no_server_report
294
+ else
295
+ @status_message = "OK"
296
+ :ok
297
+ end
298
+ rescue InvalidLogin => e
299
+ @status_message = "Invalid login"
300
+ :invalid_login
301
+ rescue RemoteTimeout => e
302
+ @status_message = "Timeout"
303
+ :down
304
+ rescue RemoteError => e
305
+ @status_message = "Error: #{e}"
306
+ :error
307
+ end
308
+ end
309
+
310
+ # Returns the result of the status message resulting from the last
311
+ # status_code call. If no status message is available, status_code
312
+ # will be automatically called first.
313
+ def status_message
314
+ if @status_message
315
+ @status_message
316
+ else
317
+ status_code
318
+ @status_message
319
+ end
320
+ end
321
+ end
322
+ include Info
323
+
324
+ private
325
+ # Executes an AXIS HTTP API call.
326
+ # script :: the remote cgi script to call
327
+ # params :: hash parameters to send to the script -- they will be URL-encoded for you
328
+ def axis_action(script, params = nil)
329
+ query = params.collect{|k,v| "#{k}=#{CGI.escape v.to_s}"}.join("&") if
330
+ params
331
+ body = false
332
+ cmd_uri = "/axis-cgi/#{script}?#{query}"
333
+ begin
334
+ # read/open timeouts don't seem to be working, so we gotta do it ourselves
335
+ Timeout.timeout(15) do
336
+ req = Net::HTTP::Get.new(cmd_uri)
337
+
338
+ # if @@http && @@http.active?
339
+ # @log.debug "AXIS REMOTE API reusing HTTP connection #{@@http}"
340
+ # http = @@http
341
+ # else
342
+ @log.debug "AXIS REMOTE API opening new HTTP connection to '#{hostname}'"
343
+ http = Net::HTTP.start(hostname)
344
+ http.read_timeout = 15 # wait 15 seconds for camera to respond, then give up
345
+ http.open_timeout = 15
346
+ # @@http = http
347
+ # end
348
+
349
+ req.basic_auth @username, @password
350
+
351
+ @log.info "AXIS REMOTE API CALL [#{hostname}]: #{cmd_uri}"
352
+ res = http.request(req)
353
+ @log.debug "AXIS REMOTE API CALL FINISHED"
354
+
355
+ # http.finish
356
+
357
+ body = res.body
358
+
359
+ if res.kind_of?(Net::HTTPClientError) || res.kind_of?(Net::HTTPServerError)
360
+ if res.kind_of?(Net::HTTPUnauthorized)
361
+ @log.error err = "AXIS CAMERA INVALID LOGIN [#{hostname}] for username '#{@username}'"
362
+ raise InvalidLogin, "Invalid login for username '#{@username}'"
363
+ else
364
+ @log.error err = "AXIS CAMERA ERROR [#{hostname}]: #{cmd_uri} -- #{body}"
365
+ raise RemoteError, body
366
+ end
367
+ end
368
+ end
369
+ if body =~ /Error: (.*?)<\/body>/
370
+ @log.error err = "AXIS CAMERA ERROR [#{hostname}]: #{cmd_uri} -- #{$~[1]}"
371
+ raise RemoteError, err
372
+ else
373
+ body
374
+ end
375
+ rescue Timeout::Error
376
+ @log.error err = "AXIS CAMERA #{hostname} TIMED OUT!"
377
+ raise RemoteTimeout, err
378
+ end
379
+ end
380
+
381
+ # Raised when a Camera is instantiated with incorrect username and/or password.
382
+ class InvalidLogin < Exception
383
+ end
384
+ # Raised when the camera responds with some error.
385
+ class RemoteError < Exception
386
+ end
387
+ # Raised when the remote camera does not respond within the timeout period.
388
+ class RemoteTimeout < Exception
389
+ end
390
+ end
391
+ end