aca-device-modules 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,346 @@
1
+ module Panasonic; end
2
+ module Panasonic::Camera; end
3
+
4
+
5
+ class Panasonic::Camera::He50
6
+ include ::Orchestrator::Constants
7
+ include ::Orchestrator::Transcoder
8
+
9
+ def on_load
10
+ on_update
11
+ end
12
+
13
+ def on_update
14
+ defaults({
15
+ delay: 130, # As per the manual page 8
16
+ keepalive: false,
17
+ inactivity_timeout: 1.5, # seconds before closing the connection if no response
18
+ connect_timeout: 2 # max seconds for the initial connection to the device
19
+ })
20
+
21
+ self[:pan_max] = 0xD2F5
22
+ self[:pan_min] = 0x2D08
23
+ self[:pan_center] = 0x7FFF
24
+ self[:tilt_max] = 0x8E38
25
+ self[:tilt_min] = 0x5556
26
+ self[:tilt_center] = 0x7FFF
27
+
28
+ self[:joy_left] = 0x01
29
+ self[:joy_right] = 0x99
30
+ self[:joy_center] = 0x50
31
+
32
+ self[:zoom_max] = 0xFFF
33
+ self[:zoom_min] = 0x555
34
+
35
+ self[:focus_max] = 0xFFF
36
+ self[:focus_min] = 0x555
37
+
38
+ self[:iris_max] = 0xFFF
39
+ self[:iris_min] = 0x555
40
+
41
+ # {near: {zoom: val, pan: val, tilt: val}}
42
+ @presets = setting(:presets) || {}
43
+ self[:presets] = @presets.keys
44
+ end
45
+
46
+ def connected
47
+ schedule.every('60s', method(:do_poll))
48
+ do_poll
49
+ end
50
+
51
+
52
+ RESP = {
53
+ power: 'p',
54
+ installation: 'iNS',
55
+
56
+ pantilt: 'aPC',
57
+ joystick: 'pTS',
58
+ limit: 'lC',
59
+
60
+ zoom: /axz|gz/,
61
+ manual_zoom: 'zS',
62
+ link_zoom: 'sWZ',
63
+
64
+ focus: /axf|gf/,
65
+ manual_focus: 'fS',
66
+ auto_focus: 'd1',
67
+
68
+ iris: /axi|gi/,
69
+ auto_iris: 'd3'
70
+ }
71
+ LIMITS = {
72
+ up: 1,
73
+ down: 2,
74
+ left: 3,
75
+ right: 4
76
+ }
77
+ LIMITS.merge!(LIMITS.invert)
78
+
79
+
80
+ # Responds with:
81
+ # 0 == standby
82
+ # 1 == power on
83
+ # 3 == powering on
84
+ def power(state = nil, &blk)
85
+ state = (is_affirmative?(state) ? 1 : 0) unless state.nil?
86
+
87
+ options = {}
88
+ options[:emit] = blk if blk
89
+ options[:delay] = 6000 if state
90
+
91
+ logger.debug "Camera requested power #{state}"
92
+
93
+ req('O', state, :power, options) do |data, resolve|
94
+ val = extract(:power, data, resolve)
95
+ if val
96
+ self[:power] = val.to_i > 0
97
+ :success
98
+ end
99
+ end
100
+ end
101
+
102
+ def installation(pos = nil)
103
+ pos = (pos.to_sym == :desk ? 0 : 1) if pos
104
+
105
+ req('INS', pos, :installation) do |data, resolve|
106
+ val = extract(:installation, data, resolve)
107
+ if val
108
+ self[:installation] = val == '0' ? :desk : :ceiling
109
+ :success
110
+ end
111
+ end
112
+ end
113
+
114
+ def pantilt(pan = nil, tilt = nil)
115
+ unless pan.nil?
116
+ pan = in_range(pan.to_i, self[:pan_max], self[:pan_min]).to_s(16).upcase.rjust(4, '0')
117
+ tilt = in_range(tilt.to_i, self[:tilt_max], self[:tilt_min]).to_s(16).upcase.rjust(4, '0')
118
+ end
119
+
120
+ req('APC', "#{pan}#{tilt}", :pantilt) do |data, resolve|
121
+ val = extract(:pantilt, data, resolve)
122
+ if val
123
+ comp = []
124
+ val.scan(/.{4}/) { |com| comp << com.to_i(16) }
125
+ self[:pan] = comp[0]
126
+ self[:tilt] = comp[1]
127
+ :success
128
+ end
129
+ end
130
+ end
131
+
132
+ # Recall a preset from the database
133
+ def preset(name)
134
+ values = @presets[name.to_sym]
135
+ if values
136
+ pantilt(values[:pan], values[:tilt])
137
+ zoom(values[:zoom])
138
+ true
139
+ else
140
+ false
141
+ end
142
+ end
143
+
144
+ def joystick(pan_speed, tilt_speed)
145
+ left_max = self[:joy_left]
146
+ right_max = self[:joy_right]
147
+ pan_speed = in_range(pan_speed.to_i, right_max, left_max).to_s(16).upcase.rjust(2, '0')
148
+ tilt_speed = in_range(tilt_speed.to_i, right_max, left_max).to_s(16).upcase.rjust(2, '0')
149
+
150
+ is_centered = false
151
+ if pan_speed == '50' && tilt_speed == '50'
152
+ is_centered = true
153
+ end
154
+
155
+ options = {}
156
+ options[:retries] = is_centered ? 1 : 0
157
+
158
+ logger.debug("Sending camera: #{pan_speed}#{tilt_speed}");
159
+
160
+ req('PTS', "#{pan_speed}#{tilt_speed}", :joystick, options) do |data, resolve|
161
+ val = extract(:joystick, data, resolve)
162
+ if val
163
+ comp = []
164
+ val.scan(/.{2}/) { |com| comp << com.to_i(16) }
165
+ self[:joy_pan] = comp[0]
166
+ self[:joy_tilt] = comp[1]
167
+ :success
168
+ end
169
+ end
170
+ end
171
+
172
+ def limit(direction, state = nil)
173
+ dir = LIMITS[direction.to_sym]
174
+ state = (is_affirmative?(set) ? 1 : 0) unless state.nil?
175
+
176
+ req('LC', "#{dir}#{state}", :limit) do |data, resolve|
177
+ val = extract(:limit, data, resolve)
178
+ if val
179
+ self[:"limit_#{LIMITS[val[0].to_i]}"] = val[1] == '1'
180
+ :success
181
+ end
182
+ end
183
+ end
184
+
185
+
186
+ def zoom(pos = nil)
187
+ cmd = 'AXZ'
188
+ if pos
189
+ pos = in_range(pos.to_i, self[:zoom_max], self[:zoom_min]).to_s(16).upcase.rjust(3, '0')
190
+ else
191
+ cmd = 'GZ'
192
+ end
193
+
194
+ req(cmd, pos, :zoom) do |data, resolve|
195
+ val = extract(:zoom, data, resolve)
196
+ if val
197
+ self[:zoom] = val.to_i(16)
198
+ :success
199
+ end
200
+ end
201
+ end
202
+
203
+ def manual_zoom(speed)
204
+ speed = in_range(speed.to_i, self[:joy_right], self[:joy_left]).to_s(16).upcase.rjust(3, '0')
205
+
206
+ req('Z', speed, :manual_zoom) do |data, resolve|
207
+ val = extract(:manual_zoom, data, resolve)
208
+ if val
209
+ self[:manual_zoom] = val.to_i(16)
210
+ :success
211
+ end
212
+ end
213
+ end
214
+
215
+ def link_zoom(state = nil) # Link pantilt speed to zoom
216
+ state = (is_affirmative?(state) ? 1 : 0) unless state.nil?
217
+
218
+ req('SWZ', state, :link_zoom) do |data, resolve|
219
+ val = extract(:link_zoom, data, resolve)
220
+ if val
221
+ self[:link_zoom] = val == '1'
222
+ :success
223
+ end
224
+ end
225
+ end
226
+
227
+
228
+ def focus(pos = nil)
229
+ cmd = 'AXF'
230
+ if pos
231
+ pos = in_range(pos.to_i, self[:focus_max], self[:focus_min]).to_s(16).upcase.rjust(3, '0')
232
+ else
233
+ cmd = 'GF'
234
+ end
235
+
236
+ req(cmd, pos, :focus) do |data, resolve|
237
+ val = extract(:focus, data, resolve)
238
+ if val
239
+ self[:focus] = val.to_i(16)
240
+ :success
241
+ end
242
+ end
243
+ end
244
+
245
+ def manual_focus(speed)
246
+ speed = in_range(speed.to_i, self[:joy_right], self[:joy_left]).to_s(16).upcase.rjust(2, '0')
247
+
248
+ req('F', speed, :manual_focus) do |data, resolve|
249
+ val = extract(:manual_focus, data, resolve)
250
+ if val
251
+ self[:manual_focus] = val.to_i(16)
252
+ :success
253
+ end
254
+ end
255
+ end
256
+
257
+ def auto_focus(state = nil)
258
+ state = (is_affirmative?(state) ? 1 : 0) unless state.nil?
259
+
260
+ req('D1', state, :auto_focus) do |data, resolve|
261
+ val = extract(:auto_focus, data, resolve)
262
+ if val
263
+ self[:auto_focus] = val == '1'
264
+ :success
265
+ end
266
+ end
267
+ end
268
+
269
+
270
+ def iris(level = nil)
271
+ cmd = 'AXI'
272
+ if level
273
+ level = in_range(level.to_i, self[:iris_max], self[:iris_min]).to_s(16).upcase.rjust(3, '0')
274
+ else
275
+ cmd = 'GI'
276
+ end
277
+
278
+ req(cmd, level, :iris) do |data, resolve|
279
+ val = extract(:iris, data, resolve)
280
+ if val
281
+ self[:iris] = val[0..2].to_i(16)
282
+ if val.length == 4
283
+ self[:auto_iris] = val == '1'
284
+ end
285
+ :success
286
+ end
287
+ end
288
+ end
289
+
290
+ def auto_iris(state = nil)
291
+ state = (is_affirmative?(state) ? 1 : 0) unless state.nil?
292
+
293
+ req('D3', state, :auto_iris) do |data, resolve|
294
+ val = extract(:auto_iris, data, resolve)
295
+ if val
296
+ self[:auto_iris] = val == '1'
297
+ :success
298
+ end
299
+ end
300
+ end
301
+
302
+
303
+ protected
304
+
305
+
306
+ def req(cmd, data, name, options = {}, &blk)
307
+ if data.nil? || (data.respond_to?(:empty?) && data.empty?)
308
+ options[:delay] = 0
309
+ options[:priority] = 0 # Actual commands have a higher priority
310
+ else
311
+ options[:name] = name
312
+ end
313
+ request_string = "/cgi-bin/aw_ptz?cmd=%23#{cmd}#{data}&res=1"
314
+ get(request_string, options, &blk)
315
+
316
+ logger.debug "requesting #{name}: #{request_string}"
317
+ end
318
+
319
+ def extract(name, data, resp)
320
+ logger.debug "received #{data} for command #{name}"
321
+
322
+ body = data[:body]
323
+ if body[0] == 'e'
324
+ notify_error(body, 'invalid command sent', data)
325
+ resp.call(:failed)
326
+ nil
327
+ else
328
+ body.sub(RESP[name], '')
329
+ end
330
+ end
331
+
332
+ def do_poll(*args)
333
+ power do
334
+ if self[:power] # only request status if online
335
+ pantilt
336
+ zoom
337
+ end
338
+ end
339
+ end
340
+
341
+ def notify_error(err, msg, cmd)
342
+ cmd = cmd[:request]
343
+ logger.warn "Camera error response: #{err} - #{msg} for #{cmd[:path]} #{cmd[:query]}"
344
+ end
345
+ end
346
+
@@ -0,0 +1,266 @@
1
+ module Panasonic; end
2
+ module Panasonic::Projector; end
3
+
4
+
5
+ require 'digest/md5'
6
+
7
+ #
8
+ # Port: 1024
9
+ #
10
+ class Panasonic::Projector::PjLink
11
+ include ::Orchestrator::Constants
12
+ include ::Orchestrator::Transcoder
13
+
14
+ def on_load
15
+ # PJLink is slow
16
+ defaults({
17
+ timeout: 4000,
18
+ delay_on_receive: 400
19
+ })
20
+
21
+ config({
22
+ tokenize: true,
23
+ delimiter: "\r",
24
+ wait_ready: 'NTCONTROL'
25
+ })
26
+
27
+ @check_scheduled = false
28
+ self[:power] = false
29
+ self[:stable_state] = true # Stable by default (allows manual on and off)
30
+ self[:input_stable] = true
31
+
32
+ # Meta data for inquiring interfaces
33
+ self[:type] = :projector
34
+ end
35
+
36
+ def on_update
37
+ end
38
+
39
+ def connected
40
+ @polling_timer = schedule.every('60s', method(:do_poll))
41
+ end
42
+
43
+ def disconnected
44
+ self[:power] = false
45
+
46
+ @polling_timer.cancel unless @polling_timer.nil?
47
+ @polling_timer = nil
48
+ end
49
+
50
+
51
+
52
+ #
53
+ # Power commands
54
+ #
55
+ def power(state, opt = nil)
56
+ self[:stable_state] = false
57
+ if is_affirmative?(state)
58
+ self[:power_target] = On
59
+ do_send(:POWR, 1, {:retries => 10, :name => :power})
60
+ logger.debug "-- panasonic Proj, requested to power on"
61
+ do_send('POWR', '?', :name => :power_state)
62
+ else
63
+ self[:power_target] = Off
64
+ do_send(:POWR, 0, {:retries => 10, :name => :power})
65
+ logger.debug "-- panasonic Proj, requested to power off"
66
+ do_send('POWR', '?', :name => :power_state)
67
+ end
68
+ end
69
+
70
+ def power?(options = {}, &block)
71
+ options[:emit] = block if block_given?
72
+ options[:name] = :power_state
73
+ do_send(:POWR, options)
74
+ end
75
+
76
+
77
+
78
+ #
79
+ # Input selection
80
+ #
81
+ INPUTS = {
82
+ :hdmi => 31,
83
+ :hdmi2 => 32,
84
+ :digital => 33,
85
+ :miracast => 52
86
+ }
87
+ INPUTS.merge!(INPUTS.invert)
88
+
89
+
90
+ def switch_to(input)
91
+ input = input.to_sym
92
+ return unless INPUTS.has_key? input
93
+
94
+ do_send(:INPT, INPUTS[input], {:retries => 10, :name => :inpt_source})
95
+ do_send('INPT', '?', {:name => :inpt_query})
96
+ logger.debug "-- panasonic LCD, requested to switch to: #{input}"
97
+
98
+ self[:input] = input # for a responsive UI
99
+ self[:input_stable] = false
100
+ end
101
+
102
+
103
+ #
104
+ # Mute Audio and Video
105
+ #
106
+ def mute
107
+ logger.debug "-- panasonic Proj, requested to mute"
108
+ do_send(:AVMT, 31, {:name => :video_mute}) # Audio + Video
109
+ end
110
+
111
+ def unmute
112
+ logger.debug "-- panasonic Proj, requested to unmute"
113
+ do_send(:AVMT, 30, {:name => :video_mute})
114
+ end
115
+
116
+
117
+ ERRORS = {
118
+ :ERR1 => '1: Undefined control command'.freeze,
119
+ :ERR2 => '2: Out of parameter range'.freeze,
120
+ :ERR3 => '3: Busy state or no-acceptable period'.freeze,
121
+ :ERR4 => '4: Timeout or no-acceptable period'.freeze,
122
+ :ERR5 => '5: Wrong data length'.freeze,
123
+ :ERRA => 'A: Password mismatch'.freeze
124
+ }
125
+
126
+
127
+ def received(data, resolve, command) # Data is default received as a string
128
+ logger.debug "panasonic Proj sent: #{data}"
129
+
130
+ # This is the ready response
131
+ if data[0] == ' '
132
+ @mode = data[1]
133
+ if @mode == '1'
134
+ @pass = "#{setting(:username) || 'admin1'}:#{setting(:password) || 'panasonic'}:#{data.strip.split(/\s+/)[-1]}"
135
+ @pass = Digest::MD5.hexdigest(@hash)
136
+ end
137
+
138
+ :success
139
+
140
+ # Error Response
141
+ elsif data[0] == 'E'
142
+ error = data.to_sym
143
+ #self[:last_error] = ERRORS[error] (for error status query)
144
+
145
+ # Check for busy or timeout
146
+ if error == :ERR3 || error == :ERR4
147
+ logger.warn "Panasonic Proj busy: #{self[:last_error]}"
148
+ :retry
149
+ else
150
+ logger.error "Panasonic Proj error: #{self[:last_error]}"
151
+ :abort
152
+ end
153
+
154
+ # Success Response
155
+ else
156
+ data = data[2..-1].split('=')
157
+
158
+ if data[1] = 'OK'
159
+ return :success
160
+ else
161
+ type = data[0][2..-1].to_sym
162
+ response = data[1].to_i
163
+ resolve.call(:success)
164
+
165
+ case type
166
+ when :POWR
167
+ self[:power] = response >= 1 && response != 2
168
+ self[:warming] = response == 3
169
+ self[:cooling] = response == 2
170
+ if response >= 2 && !@check_scheduled && !self[:stable_state]
171
+ @check_scheduled = true
172
+ schedule.in('20s') do
173
+ @check_scheduled = false
174
+ logger.debug "-- checking panasonic state"
175
+ power?({:priority => 0}) do
176
+ state = self[:power]
177
+ if state != self[:power_target]
178
+ if self[:power_target] || !self[:cooling]
179
+ power(self[:power_target])
180
+ end
181
+ elsif self[:power_target] && self[:cooling]
182
+ power(self[:power_target])
183
+ else
184
+ self[:stable_state] = true
185
+ switch_to(self[:input]) if self[:power_target] == On && !self[:input].nil?
186
+ end
187
+ end
188
+ end
189
+ end
190
+ when :INPT
191
+ if INPUTS[response].present?
192
+ self[:input] = INPUTS[response] if self[:input].nil?
193
+ self[:actual_input] = INPUTS[response]
194
+ if self[:input] == self[:actual_input]
195
+ self[:input_stable] = true
196
+ elsif self[:input_stable] == false
197
+ schedule.in('5s') do
198
+ logger.debug "-- forcing panasonic input"
199
+ switch_to(self[:input]) if self[:input_stable] == false
200
+ end
201
+ end
202
+ end
203
+ when :AVMT
204
+ self[:mute] = response == 31 # 10 == video mute off, 11 == video mute, 20 == audio mute off, 21 == audio mute, 30 == AV mute off
205
+ when :LAMP
206
+ self[:lamp] = data[1][0..-2].to_i
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+
213
+
214
+ protected
215
+
216
+
217
+ def do_poll(*args)
218
+ power?({:priority => 0}) do
219
+ if self[:power]
220
+ if self[:stable_state] == false && self[:power_target] == Off
221
+ power(Off)
222
+ else
223
+ self[:stable_state] = true
224
+ do_send(:INPT, {
225
+ :name => :inpt_query,
226
+ :priority => 0
227
+ })
228
+ do_send(:AVMT, {
229
+ :name => :mute_query,
230
+ :priority => 0
231
+ })
232
+ do_send(:LAMP, {
233
+ :name => :lamp_query,
234
+ :priority => 0
235
+ })
236
+ end
237
+ elsif self[:stable_state] == false
238
+ if self[:power_target] == On
239
+ power(On)
240
+ else
241
+ self[:stable_state] = true
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ def do_send(command, param = nil, options = {})
248
+ if param.is_a? Hash
249
+ options = param
250
+ param = nil
251
+ end
252
+
253
+ if param.nil?
254
+ pj = "#{command} ?"
255
+ else
256
+ pj = "#{command} #{param}"
257
+ end
258
+
259
+ if @mode == '0'
260
+ send("00#{pj}\r", options)
261
+ else
262
+ send("#{@pass}00#{pj}\r", options)
263
+ end
264
+ end
265
+ end
266
+