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 +10 -0
- data/README.txt +19 -3
- data/lib/axis-netcam/camera.rb +220 -21
- data/lib/axis-netcam/version.rb +1 -1
- metadata +2 -2
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
|
-
<
|
15
|
-
|
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
|
50
|
+
For more information about using the Camera class, see the AxisNetcam::Camera RDocs.
|
35
51
|
|
36
52
|
|
37
53
|
------
|
data/lib/axis-netcam/camera.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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 = "
|
318
|
+
resolution = "704x480"
|
215
319
|
else
|
216
320
|
resolution = size
|
217
321
|
end
|
218
322
|
|
219
323
|
axis_action("jpg/image.cgi",
|
220
|
-
|
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
|
-
|
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
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
-
|
347
|
-
|
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
|
data/lib/axis-netcam/version.rb
CHANGED
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.
|
7
|
-
date: 2007-
|
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
|