aca-device-modules 1.0.3 → 1.0.4

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.
@@ -14,6 +14,9 @@ class Epson::Projector::EscVp21
14
14
  # delimiter: ":"
15
15
  #})
16
16
 
17
+ self[:volume_min] = 0
18
+ self[:volume_max] = 255
19
+
17
20
  self[:power] = false
18
21
  self[:stable_state] = true # Stable by default (allows manual on and off)
19
22
 
@@ -85,6 +88,7 @@ class Epson::Projector::EscVp21
85
88
  logger.debug "-- epson LCD, requested to switch to: #{input}"
86
89
 
87
90
  self[:input] = input # for a responsive UI
91
+ self[:mute] = false
88
92
  end
89
93
 
90
94
 
@@ -178,6 +182,7 @@ class Epson::Projector::EscVp21
178
182
  end
179
183
  if !self[:stable_state] && self[:power_target] == self[:power]
180
184
  self[:stable_state] = true
185
+ self[:mute] = false if !self[:power]
181
186
  end
182
187
 
183
188
  when :MUTE
@@ -0,0 +1,243 @@
1
+ module Kramer; end
2
+ module Kramer::Switcher; end
3
+
4
+
5
+ # :title:Kramer video switches
6
+ #
7
+ # Status information avaliable:
8
+ # -----------------------------
9
+ #
10
+ # (built in)
11
+ # connected
12
+ #
13
+ # (module defined)
14
+ # video_inputs
15
+ # video_outputs
16
+ #
17
+ # video1 => input
18
+ # video2
19
+ # video3
20
+ #
21
+
22
+ #
23
+ # NOTE:: These devices should be marked as make and break!
24
+ #
25
+
26
+ class Kramer::Switcher::Protocol3000
27
+ include ::Orchestrator::Constants
28
+ include ::Orchestrator::Transcoder
29
+
30
+ def on_load
31
+ config({
32
+ tokenize: true,
33
+ delimiter: "\x0D\x0A",
34
+ encoding: "ASCII-8BIT"
35
+ })
36
+
37
+ on_update
38
+ end
39
+
40
+ def on_update
41
+ @device_id = setting(:kramer_id)
42
+ @destination = "#{@device_id}@" if @device_id
43
+
44
+ @login_level = setting(:kramer_login)
45
+ @password = setting(:kramer_password) if @login_level
46
+ end
47
+
48
+ def connected
49
+ #
50
+ # Get current state of the switcher
51
+ #
52
+ protocol_handshake
53
+ login
54
+ get_machine_info
55
+
56
+ @polling_timer = schedule.every('2m') do
57
+ logger.debug "-- Kramer Maintaining Connection"
58
+ do_send('MODEL?', {:priority => 0}) # Low priority poll to maintain connection
59
+ end
60
+ end
61
+
62
+ def disconnected
63
+ @polling_timer.cancel unless @polling_timer.nil?
64
+ @polling_timer = nil
65
+ end
66
+
67
+
68
+ #
69
+ # Starting at input 1, input 0 == disconnect
70
+ #
71
+ def switch(map, out = nil)
72
+ map = {map => out} if out
73
+ do_send(CMDS[:switch], build_switch_data(map))
74
+ end
75
+
76
+
77
+ def switch_video(map, out = nil)
78
+ map = {map => out} if out
79
+ do_send(CMDS[:switch_video], build_switch_data(map))
80
+ end
81
+
82
+
83
+ def switch_audio(map, out = nil)
84
+ map = {map => out} if out
85
+ do_send(CMDS[:switch_audio], build_switch_data(map))
86
+ end
87
+
88
+
89
+ def mute_video(out, state = true)
90
+ data = is_affirmative?(state) ? 1 : 0
91
+ do_send(CMDS[:video_mute], out, data)
92
+ end
93
+
94
+ def mute_audio(out, state = true)
95
+ data = is_affirmative?(state) ? 1 : 0
96
+ do_send(CMDS[:audio_mute], out, data)
97
+ end
98
+
99
+ def unmute_video(out)
100
+ mute_video out, false
101
+ end
102
+
103
+ def unmute_audio(out)
104
+ mute_audio out, false
105
+ end
106
+
107
+
108
+ def received(data, resolve, command)
109
+ logger.debug "Kramer sent #{data}"
110
+
111
+ # Extract and check the machine number if we've defined it
112
+ components = data.split('@')
113
+ if components.length > 1
114
+ machine = components[0]
115
+ if @device_id && machine != @device_id
116
+ return :ignore
117
+ end
118
+ end
119
+
120
+ data = components[-1].strip
121
+ components = data.split(/\s+|,/)
122
+
123
+ cmd = components[0]
124
+ args = components[1..-1]
125
+
126
+ if cmd == 'OK'
127
+ return :success
128
+ elsif cmd[0..2] == 'ERR' || args[0][0..2] == 'ERR'
129
+ if cmd[0..2] == 'ERR'
130
+ error = cmd[3..-1]
131
+ errfor = nil
132
+ else
133
+ error = args[0][3..-1]
134
+ errfor = " on #{cmd}"
135
+ end
136
+ logger.error "Kramer command error #{error}#{errfor}"
137
+ self[:last_error] = error
138
+ return :abort
139
+ end
140
+
141
+ case CMDS[cmd.to_sym]
142
+ when :info
143
+ self[:video_inputs] = args[1].to_i
144
+ self[:video_outputs] = args[3].to_i
145
+ when :route
146
+ inout = args[0].split(',')
147
+ layer = inout[0].to_i
148
+ dest = inout[1].to_i
149
+ src = inout[2].to_i
150
+ self[:"#{LAYERS[layer]}#{dest}"] = src
151
+ when :switch, :switch_audio, :switch_video
152
+ # return string like "in>out,in>out,in>out"
153
+
154
+ type = :av
155
+ type = :audio if CMDS[cmd] == :switch_audio
156
+ type = :video if CMDS[cmd] == :switch_video
157
+
158
+ mappings = args[0].split(',')
159
+ mappings.each do |map|
160
+ inout = map.split('>')
161
+ self[:"#{type}#{inout[1]}"] = inout[0].to_i
162
+ end
163
+ when :audio_mute
164
+ output, mute = args[0].split(',')
165
+ self[:"audio#{output}_muted"] = mute == '1'
166
+ when :video_mute
167
+ output, mute = args[0].split(',')
168
+ self[:"video#{output}_muted"] = mute == '1'
169
+ end
170
+
171
+ return :success
172
+ end
173
+
174
+
175
+ CMDS = {
176
+ info: :"INFO-IO?",
177
+ login: :"LOGIN",
178
+ route: :"ROUTE",
179
+ switch: :"AV",
180
+ switch_audio: :"AUD",
181
+ switch_video: :"VID",
182
+ audio_mute: :"MUTE",
183
+ video_mute: :"VMUTE"
184
+ }
185
+ CMDS.merge!(CMDS.invert)
186
+
187
+ LAYERS = {
188
+ 1 => :video,
189
+ 2 => :audio,
190
+ 2 => :data
191
+ }
192
+
193
+
194
+ private
195
+
196
+
197
+ def build_switch_data(map)
198
+ data = ''
199
+
200
+ map.each do |input, outputs|
201
+ outputs = [outputs] unless outputs.class == Array
202
+ input = input.to_s if input.class == Symbol
203
+ input = input.to_i if input.class == String
204
+ outputs.each do |output|
205
+ data << "#{input}>#{output},"
206
+ end
207
+ end
208
+
209
+ data.chop
210
+ end
211
+
212
+
213
+ def protocol_handshake
214
+ do_send('', {priority: 99})
215
+ end
216
+
217
+ def login
218
+ if @login_level
219
+ do_send(CMDS[:login], @password, {priority: 99})
220
+ end
221
+ end
222
+
223
+ def get_machine_info
224
+ do_send(CMDS[:info], {priority: 99})
225
+ end
226
+
227
+
228
+ def do_send(command, *args)
229
+ options = {}
230
+ if args[-1].is_a? Hash
231
+ options = args.pop
232
+ end
233
+
234
+ cmd = "##{@destination}#{command}"
235
+
236
+ if args.length > 0
237
+ cmd << " #{args.join(',')}"
238
+ end
239
+ cmd << "\r"
240
+
241
+ send(cmd, options)
242
+ end
243
+ end
@@ -0,0 +1,482 @@
1
+ module Nec; end
2
+ module Nec::Display; end
3
+
4
+ # :title:All NEC Control Module
5
+ #
6
+ # Controls all LCD displays as of 1/07/2011
7
+ # Status information avaliable:
8
+ # -----------------------------
9
+ #
10
+ # (built in)
11
+ # connected
12
+ #
13
+ # (module defined)
14
+ # power
15
+ # warming
16
+ #
17
+ # volume
18
+ # volume_min == 0
19
+ # volume_max
20
+ #
21
+ # brightness
22
+ # brightness_min == 0
23
+ # brightness_max
24
+ #
25
+ # contrast
26
+ # contrast_min = 0
27
+ # contrast_max
28
+ #
29
+ # audio_mute
30
+ #
31
+ # input (video input)
32
+ # audio (audio input)
33
+ #
34
+ #
35
+ class Nec::Display::All
36
+ include ::Orchestrator::Constants
37
+ include ::Orchestrator::Transcoder
38
+
39
+ #
40
+ # Called on module load complete
41
+ # Alternatively you can use initialize however will
42
+ # not have access to settings and this is called
43
+ # soon afterwards
44
+ #
45
+ def on_load
46
+ #
47
+ # Setup constants
48
+ #
49
+ self[:volume_min] = 0
50
+ self[:brightness_min] = 0
51
+ self[:contrast_min] = 0
52
+ #self[:error] = [] TODO!!
53
+ end
54
+
55
+ def response_delimiter # Function required if device could contacts us first
56
+ 0x0D
57
+ end
58
+
59
+ def connected
60
+ do_poll
61
+
62
+ @polling_timer = schedule.every('30s') do
63
+ logger.debug "-- Polling Display"
64
+ do_poll
65
+ end
66
+ end
67
+
68
+ def disconnected
69
+ #
70
+ # Disconnected may be called without calling connected
71
+ # Hence the check if timer is nil here
72
+ #
73
+ @polling_timer.cancel unless @polling_timer.nil?
74
+ @polling_timer = nil
75
+ end
76
+
77
+
78
+ #
79
+ # Power commands
80
+ #
81
+ def power(state)
82
+ message = "C203D6"
83
+
84
+ power? do |result|
85
+ if is_affirmative?(state)
86
+ if result == Off
87
+ message += "0001" # Power On
88
+ send_checksum(:command, message, {:name => :power})
89
+ self[:warming] = true
90
+ self[:power] = On
91
+ logger.debug "-- NEC LCD, requested to power on"
92
+
93
+ power_on_delay
94
+ mute_status(0)
95
+ volume_status(0)
96
+ end
97
+ else
98
+ if result == On
99
+ message += "0004" # Power Off
100
+ send_checksum(:command, message, {:name => :power})
101
+
102
+ self[:power] = Off
103
+ logger.debug "-- NEC LCD, requested to power off"
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def power?(options = {}, &block)
110
+ options[:emit] = block if block_given?
111
+ type = :command
112
+ message = "01D6"
113
+ send_checksum(type, message, options)
114
+ end
115
+
116
+
117
+ #
118
+ # Input selection
119
+ #
120
+ INPUTS = {
121
+ :vga => 1,
122
+ :rgbhv => 2,
123
+ :dvi => 3,
124
+ :hdmi_set => 4, # Set only?
125
+ :video1 => 5,
126
+ :video2 => 6,
127
+ :svideo => 7,
128
+
129
+ :tv => 10,
130
+ :dvd1 => 12,
131
+ :option => 13,
132
+ :dvd2 => 14,
133
+ :display_port => 15,
134
+
135
+ :hdmi => 17
136
+ }
137
+ def switch_to(input)
138
+ input = input.to_sym if input.class == String
139
+ #self[:target_input] = input
140
+
141
+ type = :set_parameter
142
+ message = OPERATION_CODE[:video_input]
143
+ message += INPUTS[input].to_s(16).upcase.rjust(4, '0') # Value of input as a hex string
144
+
145
+ send_checksum(type, message, {:name => :input})
146
+ brightness_status(60) # higher status than polling commands - lower than input switching
147
+ contrast_status(60)
148
+
149
+ logger.debug "-- NEC LCD, requested to switch to: #{input}"
150
+ end
151
+
152
+ AUDIO = {
153
+ :audio1 => 1,
154
+ :audio2 => 2,
155
+ :audio3 => 3,
156
+ :hdmi => 4,
157
+ :tv => 6,
158
+ :display_port => 7
159
+ }
160
+ def switch_audio(input)
161
+ input = input.to_sym if input.class == String
162
+ #self[:target_audio] = input
163
+
164
+ type = :set_parameter
165
+ message = OPERATION_CODE[:audio_input]
166
+ message += AUDIO[input].to_s(16).upcase.rjust(4, '0') # Value of input as a hex string
167
+
168
+ send_checksum(type, message, :name => :audio)
169
+ mute_status(60) # higher status than polling commands - lower than input switching
170
+ volume_status(60)
171
+
172
+ logger.debug "-- NEC LCD, requested to switch audio to: #{input}"
173
+ end
174
+
175
+
176
+ #
177
+ # Auto adjust
178
+ #
179
+ def auto_adjust
180
+ message = OPERATION_CODE[:auto_setup] #"001E" # Page + OP code
181
+ message += "0001" # Value of input as a hex string
182
+
183
+ send_checksum(:set_parameter, message, :delay_on_receive => 4.0)
184
+ end
185
+
186
+
187
+ #
188
+ # Value based set parameter
189
+ #
190
+ def brightness(val)
191
+ val = val.to_i if val.is_a? String
192
+ val = 100 if val > 100
193
+ val = 0 if val < 0
194
+
195
+ message = OPERATION_CODE[:brightness_status]
196
+ message += val.to_s(16).upcase.rjust(4, '0') # Value of input as a hex string
197
+
198
+ brightness_status
199
+ send_checksum(:set_parameter, message)
200
+ send_checksum(:command, '0C') # Save the settings
201
+ end
202
+
203
+ def contrast(val)
204
+ val = val.to_i if val.is_a? String
205
+ val = 100 if val > 100
206
+ val = 0 if val < 0
207
+
208
+ message = OPERATION_CODE[:contrast_status]
209
+ message += val.to_s(16).upcase.rjust(4, '0') # Value of input as a hex string
210
+
211
+ contrast_status
212
+ send_checksum(:set_parameter, message)
213
+ send_checksum(:command, '0C') # Save the settings
214
+ end
215
+
216
+ def volume(val)
217
+ val = val.to_i if val.is_a? String
218
+ val = 100 if val > 100
219
+ val = 0 if val < 0
220
+
221
+ message = OPERATION_CODE[:volume_status]
222
+ message += val.to_s(16).upcase.rjust(4, '0') # Value of input as a hex string
223
+
224
+ volume_status
225
+ send_checksum(:set_parameter, message)
226
+ send_checksum(:command, '0C') # Save the settings
227
+
228
+ self[:audio_mute] = false # audio is unmuted when the volume is set
229
+ end
230
+
231
+
232
+ def mute_audio
233
+ message = OPERATION_CODE[:mute_status]
234
+ message += "0001" # Value of input as a hex string
235
+
236
+ send_checksum(:set_parameter, message)
237
+
238
+ logger.debug "-- NEC LCD, requested to mute audio"
239
+ end
240
+ alias_method :mute, :mute_audio
241
+
242
+ def unmute_audio
243
+ message = OPERATION_CODE[:mute_status]
244
+ message += "0000" # Value of input as a hex string
245
+
246
+ send_checksum(:set_parameter, message)
247
+
248
+ logger.debug "-- NEC LCD, requested to unmute audio"
249
+ end
250
+ alias_method :unmute, :unmute_audio
251
+
252
+ #
253
+ # LCD Response code
254
+ #
255
+ def received(data, resolve, command)
256
+ #
257
+ # Check for valid response
258
+ #
259
+ if !check_checksum(data)
260
+ logger.debug "-- NEC LCD, checksum failed for command: #{command[:data]}"
261
+ logger.debug "-- NEC LCD, response was: #{data}"
262
+ return false
263
+ end
264
+
265
+ #data = array_to_str(data) # Convert bytes to a string (received like this)
266
+
267
+ case MSG_TYPE[data[4]] # Check the MSG_TYPE (B, D or F)
268
+ when :command_reply
269
+ #
270
+ # Power on and off
271
+ # 8..9 == "00" means no error
272
+ if data[10..15] == "C203D6" # Means power comamnd
273
+ if data[8..9] == "00"
274
+ power_on_delay(0) # wait until the screen has turned on before sending commands (0 == high priority)
275
+ else
276
+ logger.info "-- NEC LCD, command failed: #{command[:data]}"
277
+ logger.info "-- NEC LCD, response was: #{data}"
278
+ return false # command failed
279
+ end
280
+ elsif data[10..13] == "00D6" # Power status response
281
+ if data[10..11] == "00"
282
+ if data[23] == '1' # On == 1, Off == 4
283
+ self[:power] = On
284
+ else
285
+ self[:power] = Off
286
+ self[:warming] = false
287
+ end
288
+ #if self[:power_target].nil?
289
+ # self[:power_target] = self[:power]
290
+ #elsif self[:power_target] != self[:power]
291
+ # power(self[:power_target])
292
+ #end
293
+ else
294
+ logger.info "-- NEC LCD, command failed: #{command[:data]}"
295
+ logger.info "-- NEC LCD, response was: #{data}"
296
+ return false # command failed
297
+ end
298
+
299
+ end
300
+
301
+ when :get_parameter_reply, :set_parameter_reply
302
+ if data[8..9] == "00"
303
+ parse_response(data, command)
304
+ elsif data[8..9] == 'BE' # Wait response
305
+ send(command[:data]) # checksum already added
306
+ logger.debug "-- NEC LCD, response was a wait command"
307
+ else
308
+ logger.info "-- NEC LCD, get or set failed: #{command[:data]}"
309
+ logger.info "-- NEC LCD, response was: #{data}"
310
+ return false
311
+ end
312
+ end
313
+
314
+ return true # Command success
315
+ end
316
+
317
+
318
+ def do_poll
319
+ power?({:priority => 99}) do |result|
320
+ if result == On
321
+ power_on_delay
322
+ mute_status
323
+ volume_status
324
+ brightness_status
325
+ contrast_status
326
+ video_input
327
+ audio_input
328
+ end
329
+ end
330
+ end
331
+
332
+
333
+ private
334
+
335
+
336
+ def parse_response(data, command)
337
+
338
+ # 14..15 == type (we don't care)
339
+ max = data[16..19].to_i(16)
340
+ value = data[20..23].to_i(16)
341
+
342
+ case OPERATION_CODE[data[10..13]]
343
+ when :video_input
344
+ self[:input] = INPUTS.invert[value]
345
+ #self[:target_input] = self[:input] if self[:target_input].nil?
346
+ #switch_to(self[:target_input]) unless self[:input] == self[:target_input]
347
+
348
+ when :audio_input
349
+ self[:audio] = AUDIO.invert[value]
350
+ #self[:target_audio] = self[:audio] if self[:target_audio].nil?
351
+ #switch_audio(self[:target_audio]) unless self[:audio] == self[:target_audio]
352
+
353
+ when :volume_status
354
+ self[:volume_max] = max
355
+ if not self[:audio_mute]
356
+ self[:volume] = value
357
+ end
358
+
359
+ when :brightness_status
360
+ self[:brightness_max] = max
361
+ self[:brightness] = value
362
+
363
+ when :contrast_status
364
+ self[:contrast_max] = max
365
+ self[:contrast] = value
366
+
367
+ when :mute_status
368
+ self[:audio_mute] = value == 1
369
+ if(value == 1)
370
+ self[:volume] = 0
371
+ else
372
+ volume_status(0) # high priority
373
+ end
374
+
375
+ when :power_on_delay
376
+ if value > 0
377
+ self[:warming] = true
378
+ schedule.in("#{value}s") do # Prevent any commands being sent until the power on delay is complete
379
+ power_on_delay
380
+ end
381
+ else
382
+ schedule.in('3s') do # Reactive the interface once the display is online
383
+ self[:warming] = false # allow access to the display
384
+ end
385
+ end
386
+ when :auto_setup
387
+ # auto_setup
388
+ # nothing needed to do here (we are delaying the next command by 4 seconds)
389
+ else
390
+ logger.info "-- NEC LCD, unknown response: #{data[10..13]}"
391
+ logger.info "-- NEC LCD, for command: #{command[:data]}"
392
+ logger.info "-- NEC LCD, full response was: #{data}"
393
+ end
394
+ end
395
+
396
+
397
+ #
398
+ # Types of messages sent to and from the LCD
399
+ #
400
+ MSG_TYPE = {
401
+ :command => 'A',
402
+ 'B' => :command_reply,
403
+ :get_parameter => 'C',
404
+ 'D' => :get_parameter_reply,
405
+ :set_parameter => 'E',
406
+ 'F' => :set_parameter_reply
407
+ }
408
+
409
+
410
+ OPERATION_CODE = {
411
+ :video_input => '0060', '0060' => :video_input,
412
+ :audio_input => '022E', '022E' => :audio_input,
413
+ :volume_status => '0062', '0062' => :volume_status,
414
+ :mute_status => '008D', '008D' => :mute_status,
415
+ :power_on_delay => '02D8', '02D8' => :power_on_delay,
416
+ :contrast_status => '0012', '0012' => :contrast_status,
417
+ :brightness_status => '0010', '0010' => :brightness_status,
418
+ :auto_setup => '001E', '001E' => :auto_setup
419
+ }
420
+ #
421
+ # Automatically creates a callable function for each command
422
+ # http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html
423
+ # http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html
424
+ #
425
+ OPERATION_CODE.each_key do |command|
426
+ define_method command do |*args|
427
+ priority = 99
428
+ if args.length > 0
429
+ priority = args[0]
430
+ end
431
+ message = OPERATION_CODE[command]
432
+ send_checksum(:get_parameter, message, {:priority => priority}) # Status polling is a low priority
433
+ end
434
+ end
435
+
436
+
437
+ def check_checksum(data)
438
+ data = str_to_array(data)
439
+
440
+ check = 0
441
+ #
442
+ # Loop through the second to the second last element
443
+ # Delimiter is removed automatically
444
+ #
445
+ if data.length >= 2
446
+ data[1..-2].each do |byte|
447
+ check = check ^ byte
448
+ end
449
+ return check == data[-1] # Check the check sum equals the last element
450
+ else
451
+ return true
452
+ end
453
+ end
454
+
455
+
456
+ #
457
+ # Builds the command and creates the checksum
458
+ #
459
+ def send_checksum(type, command, options = {})
460
+ #
461
+ # build header + command and convert to a byte array
462
+ #
463
+ command = "" << 0x02 << command << 0x03
464
+ command = "0*0#{MSG_TYPE[type]}#{command.length.to_s(16).upcase.rjust(2, '0')}#{command}"
465
+ command = str_to_array(command)
466
+
467
+ #
468
+ # build checksum
469
+ #
470
+ check = 0
471
+ command.each do |byte|
472
+ check = check ^ byte
473
+ end
474
+
475
+ command << check # Add checksum
476
+ command << 0x0D # delimiter required by NEC displays
477
+ command.insert(0, 0x01) # insert SOH byte (not part of the checksum)
478
+
479
+ send(command, options)
480
+ end
481
+ end
482
+