axis-netcam 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.txt CHANGED
@@ -1,3 +1,13 @@
1
+ === 0.2.0 :: 2007-08-13
2
+
3
+ * Added methods for obtaining camera parameters and for calibrating the
4
+ camera's position.
5
+ * The library can now be installed directly as a Rails plugin (instead
6
+ of installing as a RubyGem). See README for instructions.
7
+ * Added method for fetching the camera's PTZ limits.
8
+ * snapshot_jpeg method now takes an optional hash of parameters to send
9
+ with the snapshot request.
10
+
1
11
  === 0.1.0 :: 2007-07-26
2
12
 
3
13
  * First public release.
data/README.txt CHANGED
@@ -1,5 +1,10 @@
1
1
  = AxisNetcam
2
2
 
3
+ <b>AxisNetcam provides a Ruby interface for interacting with network cameras
4
+ from Axis Communications.</b>
5
+
6
+ === Copyright & Contact Info
7
+
3
8
  <i>Copyright 2007 Urbacon Ltd.</i>
4
9
 
5
10
  For info and downloads please see:
@@ -9,10 +14,21 @@ For info and downloads please see:
9
14
  You can contact the author at:
10
15
 
11
16
  matt at roughest dot net
17
+
18
+ === Installation
19
+
20
+ As a RubyGem[http://rubygems.org/read/chapter/3]):
21
+
22
+ gem install axis-netcam
12
23
 
24
+ As a plugin in a Rails application (this will install as an svn external,
25
+ so your installation will be linked to the newest, bleeding-edge version
26
+ of AxisNetcam):
13
27
 
14
- <b>AxisNetcam provides a Ruby interface for interacting with network cameras
15
- from Axis Communications.</b>
28
+ cd <your Rails application's root directory>
29
+ ruby script/plugin install -x http://axis-netcam.rubyforge.org/svn/trunk/lib/axis-netcam
30
+
31
+ === Usage
16
32
 
17
33
  Note that only a subset of the full Axis API is currently implemented, but the most
18
34
  useful functionality is in place.
@@ -31,7 +47,7 @@ Example usage:
31
47
  f.write(c.snapshot_jpeg)
32
48
  f.close
33
49
 
34
- For more information about using a Camera object, see the AxisNetcam::Camera RDocs.
50
+ For more information about using the Camera class, see the AxisNetcam::Camera RDocs.
35
51
 
36
52
 
37
53
  ------
@@ -1,3 +1,22 @@
1
+ #--
2
+ # This file is part of axis-netcam.
3
+ #
4
+ # Copyright (2007) Matt Zukowski <matt at roughest dot net>.
5
+ #
6
+ # axis-netcam is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published by
8
+ # the Free Software Foundation; either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # axis-netcam is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+ #--
19
+
1
20
  require 'net/http'
2
21
  require 'cgi'
3
22
  require 'logger'
@@ -28,7 +47,7 @@ module AxisNetcam
28
47
  class Camera
29
48
 
30
49
  # The HTTP network connection to the camera.
31
- @@http = nil
50
+ @http = nil
32
51
 
33
52
  attr_reader :hostname, :logger
34
53
 
@@ -57,10 +76,11 @@ module AxisNetcam
57
76
  # viewing angle) and 10000 approaching a camera's maximum zoom. Again,
58
77
  # this depends on your camera's capabilities.
59
78
  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
79
+ raw = axis_action("com/ptz.cgi", {'query' => 'position'})
80
+ str = raw.split
81
+ pan = str[0].split("=").last.to_f
82
+ tilt = str[1].split("=").last.to_f
83
+ zoom = str[2].split("=").last.to_i
64
84
 
65
85
  {:pan => pan, :tilt => tilt, :zoom => zoom}
66
86
  end
@@ -108,9 +128,89 @@ module AxisNetcam
108
128
  positions
109
129
  end
110
130
 
131
+ # Returns a hash with info about the camera's point-tilt-zoom limits.
132
+ #
133
+ # The returned hash will look something like:
134
+ #
135
+ # {'MinPan' => "-169",
136
+ # 'MaxPan' => "169",
137
+ # 'MinTilt' => "-90",
138
+ # 'MaxTilt' => "10",
139
+ # 'MinZoom' => "1",
140
+ # 'MaxZoom' => "19999"}
141
+ #
142
+ # The pan and tilt limit values assume that your camera's image is not
143
+ # rotated. If you want to use these values in <tt>ptz</tt> calls
144
+ # and your image is configured to be rotated, then you should also
145
+ # specify <tt>:imagerotation => 0</tt> as one of your parameters
146
+ # to <tt>ptz</tt>. For example:
147
+ #
148
+ # limits = c.get_ptz_limits
149
+ # c.ptz(:tilt => limits['MaxTilt'], :imagerotation => 0)
150
+ #
151
+ # Alternatively, you can specify a +rotation+ argument. This will
152
+ # automatically adjust the returned pan and tilt values to match
153
+ # the given rotation. You can also specify :auto instead of providing
154
+ # a numeric value, in which case the system will try to fetch
155
+ # the rotation value for you (but be careful, because this can slow
156
+ # things down, since the camera must be queried first).
157
+ def get_ptz_limits(rotation = nil)
158
+ l = get_parameters("PTZ.Limit.L1")
159
+ return l unless rotation
160
+
161
+ rotation = get_current_image_rotation if rotation == :auto
162
+
163
+ # TODO: this only works for the 0, 90, 180, 270 rotations but not arbitrary values
164
+ case rotation
165
+ when 90
166
+ l['MinPan'], l['MaxPan'], l['MinTilt'], l['MaxTilt'] =
167
+ l['MinTilt'], l['MaxTilt'], l['MinPan'], l['MaxPan']
168
+ when 180
169
+ l['MinPan'], l['MaxPan'], l['MinTilt'], l['MaxTilt'] =
170
+ l['MinPan'], l['MaxPan'], l['MaxTilt']*-1, l['MinTilt']*-1
171
+ when 270
172
+ # FIXME: This transformation appears to be busted :(
173
+ l['MinPan'], l['MaxPan'], l['MinTilt'], l['MaxTilt'] =
174
+ l['MinTilt'], l['MaxTilt'], l['MinPan']*-1, l['MaxPan']*-1
175
+ end
176
+
177
+ l
178
+ end
179
+
180
+ # Points the camera at the given preset.
111
181
  def point_at_preset_name(preset_name)
112
182
  axis_action("com/ptz.cgi", {'gotoserverpresetname' => preset_name})
113
183
  end
184
+
185
+ # Tries to 'calibrate' the camera by rotating to all extremes.
186
+ #
187
+ # This may be useful when the camera is shifted by some external
188
+ # force and looses its place. Running the calibration should reset
189
+ # things so that the camera's absolute point and tilt co-ordinates are
190
+ # consistent relative to the camera's base.
191
+ def calibrate
192
+ @log.info("Starting camera calibration...")
193
+
194
+ pos = get_position
195
+ limits = get_ptz_limits(:auto)
196
+
197
+ zoom(limits['MinZoom'])
198
+ (1..2).each do # do it twice, just to be safe...
199
+ sleep(3)
200
+ ptz(:pan => limits['MinPan'], :tilt => limits['MinTilt'])
201
+ sleep(7)
202
+ ptz(:pan => limits['MinPan'], :tilt => limits['MaxTilt'])
203
+ sleep(7)
204
+ ptz(:pan => limits['MaxPan'], :tilt => limits['MaxTilt'])
205
+ sleep(7)
206
+ ptz(:pan => limits['MaxPan'], :tilt => limits['MinTilt'])
207
+ sleep(7)
208
+ end
209
+
210
+ ptz(pos)
211
+
212
+ @log.info("Finished camera calibration.")
213
+ end
114
214
  end
115
215
  include PTZ
116
216
 
@@ -194,6 +294,7 @@ module AxisNetcam
194
294
  # Returns JPEG data with a snapshot of the current camera image.
195
295
  # size :: optionally specifies the image size -- one of :full, :medium,
196
296
  # or :thumbnail, or a string specifying the WxH dimensions like "640x480".
297
+ # options :: hash of additional options (if any) to send with the image request
197
298
  #
198
299
  # To dump the JPEG data to a file, you can do something like this:
199
300
  #
@@ -204,20 +305,23 @@ module AxisNetcam
204
305
  # f.write(data)
205
306
  # f.close
206
307
  #
207
- def snapshot_jpeg(size = :full)
308
+ # The options hash can include parameters such as 'compression', 'rotation', 'date', etc.
309
+ # See http://www.axis.com/techsup/cam_servers/dev/cam_http_api_2.htm#api_blocks_image_video_jpeg_snapshot
310
+ # for more information.
311
+ def snapshot_jpeg(size = :full, options = {})
208
312
  case size
209
313
  when :thumbnail
210
314
  resolution = "160x120"
211
315
  when :medium
212
316
  resolution = "480x360"
213
317
  when :full
214
- resolution = "640x480"
318
+ resolution = "704x480"
215
319
  else
216
320
  resolution = size
217
321
  end
218
322
 
219
323
  axis_action("jpg/image.cgi",
220
- {'resolution' => resolution, 'text' => '0'})
324
+ options.merge('resolution' => resolution))
221
325
  end
222
326
 
223
327
  # Returns the URI for accessing the camera's streaming Motion JPEG video.
@@ -234,12 +338,91 @@ module AxisNetcam
234
338
  end
235
339
  "http://#{hostname}/axis-cgi/mjpg/video.cgi?resolution=#{resolution}&text=0"
236
340
  end
341
+
342
+ # Returns the current image rotation setting.
343
+ def get_current_image_rotation
344
+ v = get_parameters("Image.I0.Appearance.Rotation")
345
+ if v && v["Image.I0.Appearance.Rotation"]
346
+ v["Image.I0.Appearance.Rotation"]
347
+ else
348
+ nil
349
+ end
350
+ end
237
351
  end
238
352
  include Video
239
353
 
240
354
  # Functionality related to obtaining information about the camera, such as its
241
355
  # status, model number, etc.
242
356
  module Info
357
+ # Returns a hash enumerating the camera's various parameters.
358
+ # The +group+ parameter limits the returned values to the given group.
359
+ # Note that if given, the group is removed from the parameter names.
360
+ #
361
+ # For example:
362
+ #
363
+ # c.get_parameters("PTZ.Limit")
364
+ #
365
+ # Returns:
366
+ #
367
+ # {"L1.MaxFocus"=>9999, "L1.MaxFieldAngle"=>50, "L1.MaxTilt"=>10,
368
+ # "L1.MinFieldAngle"=>1, "L1.MaxPan"=>169, "L1.MaxIris"=>9999,
369
+ # "L1.MaxZoom"=>19999, "L1.MinFocus"=>1, "L1.MinPan"=>-169,
370
+ # "L1.MinIris"=>1, "L1.MinZoom"=>1, "L1.MinTilt"=>-90}
371
+ #
372
+ # But the following:
373
+ #
374
+ # c.get_parameters("PTZ.Limit.L1")
375
+ #
376
+ # Returns:
377
+ #
378
+ # {"MaxIris"=>9999, "MaxZoom"=>19999, "MaxTilt"=>10, "MaxFocus"=>9999,
379
+ # "MaxPan"=>169, "MinFieldAngle"=>1, "MinTilt"=>-90, "MinPan"=>-169,
380
+ # "MinIris"=>1, "MinZoom"=>1, "MinFocus"=>1, "MaxFieldAngle"=>50}
381
+ #
382
+ def get_parameters(group = nil)
383
+ params = {
384
+ 'action' => 'list',
385
+ 'responseformat' => 'rfc'
386
+ }
387
+ params['group'] = group if group
388
+
389
+ response = axis_action("admin/param.cgi", params)
390
+
391
+ if response =~ /Error -1 getting param in group '.*?'!/
392
+ raise RemoteError, "There is no parameter group '#{group}' on this camera."
393
+ end
394
+
395
+ values = {}
396
+ response.each do |line|
397
+ k,v = line.split("=")
398
+ k.strip!
399
+
400
+ if v.nil?
401
+ v = nil
402
+ else
403
+ case v.strip
404
+ when /^true$/
405
+ v = true
406
+ when /^false$/
407
+ v = false
408
+ when /^[-]?[0-9]+$/
409
+ v = v.to_i
410
+ when /^[-]?[0-9]+\.?[0-9]+$/
411
+ v = v.to_f
412
+ else
413
+ v = v.strip
414
+ end
415
+ end
416
+
417
+ key = k.gsub(group ? "root.#{group}." : "root.", "")
418
+
419
+ values[key] = v
420
+ end
421
+
422
+ values
423
+ end
424
+
425
+
243
426
  # Returns the raw camera server report.
244
427
  #
245
428
  # The report is a string with info about the camera's status and parameters,
@@ -266,13 +449,7 @@ module AxisNetcam
266
449
 
267
450
  # Returns the camera's model name.
268
451
  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
452
+ extract_value_pairs_from_server_report(['prodshortname'])['prodshortname']
276
453
  end
277
454
 
278
455
  # Returns a code describing the camera's current status.
@@ -306,6 +483,7 @@ module AxisNetcam
306
483
  :error
307
484
  end
308
485
  end
486
+ alias status status_code
309
487
 
310
488
  # Returns the result of the status message resulting from the last
311
489
  # status_code call. If no status message is available, status_code
@@ -321,6 +499,11 @@ module AxisNetcam
321
499
  end
322
500
  include Info
323
501
 
502
+ def to_s
503
+ s = super
504
+ s.gsub(">", %{ @hostname=#{hostname.inspect}>})
505
+ end
506
+
324
507
  private
325
508
  # Executes an AXIS HTTP API call.
326
509
  # script :: the remote cgi script to call
@@ -335,16 +518,16 @@ module AxisNetcam
335
518
  Timeout.timeout(15) do
336
519
  req = Net::HTTP::Get.new(cmd_uri)
337
520
 
338
- # if @@http && @@http.active?
339
- # @log.debug "AXIS REMOTE API reusing HTTP connection #{@@http}"
340
- # http = @@http
341
- # else
521
+ if @http && @http.active?
522
+ @log.debug "AXIS REMOTE API reusing HTTP connection #{@http}"
523
+ http = @http
524
+ else
342
525
  @log.debug "AXIS REMOTE API opening new HTTP connection to '#{hostname}'"
343
526
  http = Net::HTTP.start(hostname)
344
527
  http.read_timeout = 15 # wait 15 seconds for camera to respond, then give up
345
528
  http.open_timeout = 15
346
- # @@http = http
347
- # end
529
+ @http = http
530
+ end
348
531
 
349
532
  req.basic_auth @username, @password
350
533
 
@@ -377,6 +560,22 @@ module AxisNetcam
377
560
  raise RemoteTimeout, err
378
561
  end
379
562
  end
563
+
564
+ def extract_value_pairs_from_server_report(keys)
565
+ values = {}
566
+ report = server_report
567
+ begin
568
+ keys.each do |k|
569
+ report =~ Regexp.new(%{#{k}\s*=\s*"(.*?)"}, "i")
570
+ values[k] = $~[1] if $~ && $~[1]
571
+ end
572
+ rescue RemoteTimeout, RemoteError => e
573
+ @error = e
574
+ nil
575
+ end
576
+
577
+ values
578
+ end
380
579
 
381
580
  # Raised when a Camera is instantiated with incorrect username and/or password.
382
581
  class InvalidLogin < Exception
@@ -1,7 +1,7 @@
1
1
  module AxisNetcam #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 1
4
+ MINOR = 2
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: axis-netcam
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.0
7
- date: 2007-07-26 00:00:00 -04:00
6
+ version: 0.2.0
7
+ date: 2007-08-13 00:00:00 -04:00
8
8
  summary: Provides a Ruby interface for interacting with network cameras from Axis Communications.
9
9
  require_paths:
10
10
  - lib