axis-netcam 0.1.0 → 0.2.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 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