axis-netcam 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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