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.
- checksums.yaml +4 -4
- data/lib/aca-device-modules/version.rb +1 -1
- data/modules/biamp/nexia.rb +45 -26
- data/modules/bss/blu100.rb +49 -31
- data/modules/chiyu/cyt.rb +183 -0
- data/modules/epson/projector/esc_vp21.rb +5 -0
- data/modules/kramer/switcher/protocol3000.rb +243 -0
- data/modules/nec/display/all.rb +482 -0
- data/modules/nec/projector/np_series.rb +648 -0
- data/modules/panasonic/projector/tcp.rb +250 -0
- data/modules/screen_technics/connect.rb +73 -0
- data/modules/sony/display/{gdx_and_fwd.rb → id_talk.rb} +38 -7
- data/modules/sony/projector/pj_talk.rb +300 -0
- data/modules/transmitsms/api.rb +52 -0
- data/modules/vaddio/camera/clear_view_ptz_telnet.rb +3 -3
- metadata +11 -4
- data/modules/panasonic/projector/pj_link.rb +0 -266
@@ -0,0 +1,250 @@
|
|
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::Tcp
|
11
|
+
include ::Orchestrator::Constants
|
12
|
+
include ::Orchestrator::Transcoder
|
13
|
+
|
14
|
+
def on_load
|
15
|
+
# Response time is slow
|
16
|
+
defaults({
|
17
|
+
timeout: 2000,
|
18
|
+
delay_on_receive: 1000
|
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
|
+
|
31
|
+
# Meta data for inquiring interfaces
|
32
|
+
self[:type] = :projector
|
33
|
+
|
34
|
+
# The projector drops the connection when there is no activity
|
35
|
+
schedule.every('60s') do
|
36
|
+
power?({:priority => 0})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_update
|
41
|
+
end
|
42
|
+
|
43
|
+
def connected
|
44
|
+
end
|
45
|
+
|
46
|
+
def disconnected
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
COMMANDS = {
|
51
|
+
power_on: :PON,
|
52
|
+
power_off: :POF,
|
53
|
+
power_query: :QPW,
|
54
|
+
freeze: :OFZ,
|
55
|
+
input: :IIS,
|
56
|
+
mute: :OSH,
|
57
|
+
lamp: :"Q$S"
|
58
|
+
}
|
59
|
+
COMMANDS.merge!(COMMANDS.invert)
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
#
|
64
|
+
# Power commands
|
65
|
+
#
|
66
|
+
def power(state, opt = nil)
|
67
|
+
self[:stable_state] = false
|
68
|
+
if is_affirmative?(state)
|
69
|
+
self[:power_target] = On
|
70
|
+
do_send(:power_on, {:retries => 10, :name => :power, delay_on_receive: 8000})
|
71
|
+
logger.debug "-- panasonic Proj, requested to power on"
|
72
|
+
do_send(:lamp)
|
73
|
+
else
|
74
|
+
self[:power_target] = Off
|
75
|
+
do_send(:power_off, {:retries => 10, :name => :power, delay_on_receive: 8000})
|
76
|
+
logger.debug "-- panasonic Proj, requested to power off"
|
77
|
+
do_send(:lamp)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def power?(options = {}, &block)
|
82
|
+
options[:emit] = block if block_given?
|
83
|
+
do_send(:lamp, options)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
#
|
89
|
+
# Input selection
|
90
|
+
#
|
91
|
+
INPUTS = {
|
92
|
+
:hdmi => :HD1,
|
93
|
+
:hdmi2 => :HD2,
|
94
|
+
:vga => :RG1,
|
95
|
+
:vga2 => :RG2,
|
96
|
+
:miracast => :MC1
|
97
|
+
}
|
98
|
+
INPUTS.merge!(INPUTS.invert)
|
99
|
+
|
100
|
+
|
101
|
+
def switch_to(input)
|
102
|
+
input = input.to_sym
|
103
|
+
return unless INPUTS.has_key? input
|
104
|
+
|
105
|
+
# Projector doesn't automatically unmute
|
106
|
+
unmute if self[:mute]
|
107
|
+
|
108
|
+
do_send(:input, INPUTS[input], {:retries => 10, delay_on_receive: 2000})
|
109
|
+
logger.debug "-- panasonic LCD, requested to switch to: #{input}"
|
110
|
+
|
111
|
+
self[:input] = input # for a responsive UI
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
#
|
116
|
+
# Mute Audio and Video
|
117
|
+
#
|
118
|
+
def mute(val = true)
|
119
|
+
actual = val ? 1 : 0
|
120
|
+
logger.debug "-- panasonic Proj, requested to mute"
|
121
|
+
do_send(:mute, actual) # Audio + Video
|
122
|
+
end
|
123
|
+
|
124
|
+
def unmute
|
125
|
+
logger.debug "-- panasonic Proj, requested to unmute"
|
126
|
+
do_send(:mute, 0)
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
ERRORS = {
|
131
|
+
:ERR1 => '1: Undefined control command'.freeze,
|
132
|
+
:ERR2 => '2: Out of parameter range'.freeze,
|
133
|
+
:ERR3 => '3: Busy state or no-acceptable period'.freeze,
|
134
|
+
:ERR4 => '4: Timeout or no-acceptable period'.freeze,
|
135
|
+
:ERR5 => '5: Wrong data length'.freeze,
|
136
|
+
:ERRA => 'A: Password mismatch'.freeze,
|
137
|
+
:ER401 => '401: Command cannot be executed'.freeze,
|
138
|
+
:ER402 => '402: Invalid parameter is sent'.freeze
|
139
|
+
}
|
140
|
+
|
141
|
+
|
142
|
+
def received(data, resolve, command) # Data is default received as a string
|
143
|
+
logger.debug "panasonic Proj sent: #{data}"
|
144
|
+
|
145
|
+
# This is the ready response
|
146
|
+
if data[0] == ' '
|
147
|
+
@mode = data[1]
|
148
|
+
if @mode == '1'
|
149
|
+
@pass = "#{setting(:username) || 'admin1'}:#{setting(:password) || 'panasonic'}:#{data.strip.split(/\s+/)[-1]}"
|
150
|
+
@pass = Digest::MD5.hexdigest(@pass)
|
151
|
+
end
|
152
|
+
|
153
|
+
else
|
154
|
+
data = data[2..-1]
|
155
|
+
|
156
|
+
# Error Response
|
157
|
+
if data[0] == 'E'
|
158
|
+
error = data.to_sym
|
159
|
+
self[:last_error] = ERRORS[error]
|
160
|
+
|
161
|
+
# Check for busy or timeout
|
162
|
+
if error == :ERR3 || error == :ERR4
|
163
|
+
logger.warn "Panasonic Proj busy: #{self[:last_error]}"
|
164
|
+
return :retry
|
165
|
+
else
|
166
|
+
logger.error "Panasonic Proj error: #{self[:last_error]}"
|
167
|
+
return :abort
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
resp = data.split(':')
|
172
|
+
cmd = COMMANDS[resp[0].to_sym]
|
173
|
+
val = resp[1]
|
174
|
+
|
175
|
+
case cmd
|
176
|
+
when :power_on
|
177
|
+
self[:power] = true
|
178
|
+
when :power_off
|
179
|
+
self[:power] = false
|
180
|
+
when :power_query
|
181
|
+
self[:power] = val.to_i == 1
|
182
|
+
when :freeze
|
183
|
+
self[:frozen] = val.to_i == 1
|
184
|
+
when :input
|
185
|
+
self[:input] = INPUTS[val.to_sym]
|
186
|
+
when :mute
|
187
|
+
self[:mute] = val.to_i == 1
|
188
|
+
else
|
189
|
+
if command && command[:name] == :lamp
|
190
|
+
ival = resp[0].to_i
|
191
|
+
self[:power] = ival == 1 || ival == 2
|
192
|
+
self[:warming] = ival == 1
|
193
|
+
self[:cooling] = ival == 3
|
194
|
+
|
195
|
+
if (self[:warming] || self[:cooling]) && !@check_scheduled && !self[:stable_state]
|
196
|
+
@check_scheduled = true
|
197
|
+
schedule.in('13s') do
|
198
|
+
@check_scheduled = false
|
199
|
+
logger.debug "-- checking panasonic state"
|
200
|
+
power?({:priority => 0}) do
|
201
|
+
state = self[:power]
|
202
|
+
if state != self[:power_target]
|
203
|
+
if self[:power_target] || !self[:cooling]
|
204
|
+
power(self[:power_target])
|
205
|
+
end
|
206
|
+
elsif self[:power_target] && self[:cooling]
|
207
|
+
power(self[:power_target])
|
208
|
+
else
|
209
|
+
self[:stable_state] = true
|
210
|
+
switch_to(self[:input]) if self[:power_target] == On && !self[:input].nil?
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
:success
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
protected
|
224
|
+
|
225
|
+
|
226
|
+
def do_send(command, param = nil, options = {})
|
227
|
+
if param.is_a? Hash
|
228
|
+
options = param
|
229
|
+
param = nil
|
230
|
+
end
|
231
|
+
|
232
|
+
# Default to the command name if name isn't set
|
233
|
+
options[:name] = command unless options[:name]
|
234
|
+
|
235
|
+
if param.nil?
|
236
|
+
pj = "#{COMMANDS[command]}"
|
237
|
+
else
|
238
|
+
pj = "#{COMMANDS[command]}:#{param}"
|
239
|
+
end
|
240
|
+
|
241
|
+
if @mode == '0'
|
242
|
+
send("00#{pj}\r", options)
|
243
|
+
else
|
244
|
+
send("#{@pass}00#{pj}\r", options)
|
245
|
+
end
|
246
|
+
|
247
|
+
nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ScreenTechnics; end
|
2
|
+
|
3
|
+
|
4
|
+
class ScreenTechnics::Connect
|
5
|
+
include ::Orchestrator::Constants
|
6
|
+
|
7
|
+
|
8
|
+
def on_load
|
9
|
+
defaults({
|
10
|
+
delay: 2000,
|
11
|
+
keepalive: false,
|
12
|
+
inactivity_timeout: 1.5, # seconds before closing the connection if no response
|
13
|
+
connect_timeout: 2 # max seconds for the initial connection to the device
|
14
|
+
})
|
15
|
+
|
16
|
+
self[:state] = :up
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_update
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected
|
23
|
+
end
|
24
|
+
|
25
|
+
def state(new_state, index = 1)
|
26
|
+
if is_affirmative?(new_state)
|
27
|
+
down(index)
|
28
|
+
else
|
29
|
+
up(index)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def down(index = 1)
|
34
|
+
stop(index)
|
35
|
+
do_send({
|
36
|
+
state: :down,
|
37
|
+
body: "Down#{index}=Down",
|
38
|
+
name: :"position#{index}",
|
39
|
+
index: index
|
40
|
+
})
|
41
|
+
end
|
42
|
+
|
43
|
+
def up(index = 1)
|
44
|
+
stop(index)
|
45
|
+
do_send({
|
46
|
+
state: :up,
|
47
|
+
body: "Up#{index}=Up",
|
48
|
+
name: :"position#{index}",
|
49
|
+
index: index
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop(index = 1)
|
54
|
+
do_send({
|
55
|
+
body: "Stop#{index}=Stop",
|
56
|
+
name: :"stop#{index}",
|
57
|
+
priority: 99
|
58
|
+
})
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
|
65
|
+
def do_send(options)
|
66
|
+
state = options.delete(:state)
|
67
|
+
index = options.delete(:index)
|
68
|
+
post('/ADirectControl.html', options) do
|
69
|
+
self[:"screen#{index}"] = state if state
|
70
|
+
:success
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -5,7 +5,7 @@ module Sony::Display; end
|
|
5
5
|
#
|
6
6
|
# Port: 53484
|
7
7
|
#
|
8
|
-
class Sony::Display::
|
8
|
+
class Sony::Display::IdTalk
|
9
9
|
include ::Orchestrator::Constants
|
10
10
|
include ::Orchestrator::Transcoder
|
11
11
|
|
@@ -16,9 +16,19 @@ class Sony::Display::GdxAndFwd
|
|
16
16
|
self[:contrast_max] = 0x64
|
17
17
|
self[:volume_min] = 0x00
|
18
18
|
self[:volume_max] = 0x64
|
19
|
+
|
19
20
|
self[:power] = false
|
20
21
|
self[:type] = :lcd
|
21
22
|
|
23
|
+
config({
|
24
|
+
tokenize: proc {
|
25
|
+
::UV::AbstractTokenizer.new({
|
26
|
+
indicator: "\x02\x10", # Header
|
27
|
+
callback: method(:check_complete)
|
28
|
+
})
|
29
|
+
}
|
30
|
+
})
|
31
|
+
|
22
32
|
on_update
|
23
33
|
end
|
24
34
|
|
@@ -164,15 +174,14 @@ class Sony::Display::GdxAndFwd
|
|
164
174
|
}
|
165
175
|
|
166
176
|
|
167
|
-
def received(
|
177
|
+
def received(byte_str, resolve, command) # Data is default received as a string
|
168
178
|
logger.debug "sony display sent: 0x#{byte_to_hex(data)}"
|
169
179
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
resp = str_to_array(data[10..-1])
|
180
|
+
data = str_to_array(byte_str)
|
181
|
+
idt_command = data[5..6]
|
182
|
+
resp = data[8..-1]
|
174
183
|
|
175
|
-
if
|
184
|
+
if data[4] == 0x01
|
176
185
|
# resp is now equal to the unit control codes
|
177
186
|
|
178
187
|
type = RESP[resp[1]]
|
@@ -216,6 +225,28 @@ class Sony::Display::GdxAndFwd
|
|
216
225
|
|
217
226
|
|
218
227
|
protected
|
228
|
+
|
229
|
+
|
230
|
+
# Called by the Abstract Tokenizer to confirm we have the
|
231
|
+
# whole message.
|
232
|
+
def check_complete(byte_str)
|
233
|
+
bytes = str_to_array(byte_str)
|
234
|
+
|
235
|
+
# Min message length is 8 bytes
|
236
|
+
return false if bytes.length < 8
|
237
|
+
|
238
|
+
# Check we have the data
|
239
|
+
data = bytes[8..-1]
|
240
|
+
if data.length == bytes[7]
|
241
|
+
return true
|
242
|
+
elsif data.length > bytes[7]
|
243
|
+
# Let the tokeniser know we only want the following number of bytes
|
244
|
+
return 7 + bytes[7]
|
245
|
+
end
|
246
|
+
|
247
|
+
# Still waiting on data
|
248
|
+
return false
|
249
|
+
end
|
219
250
|
|
220
251
|
|
221
252
|
def do_poll(*args)
|
@@ -0,0 +1,300 @@
|
|
1
|
+
module Sony; end
|
2
|
+
module Sony::Projector; end
|
3
|
+
|
4
|
+
|
5
|
+
#
|
6
|
+
# Port: 53484
|
7
|
+
#
|
8
|
+
class Sony::Projector::PjTalk
|
9
|
+
include ::Orchestrator::Constants
|
10
|
+
include ::Orchestrator::Transcoder
|
11
|
+
|
12
|
+
def on_load
|
13
|
+
self[:brightness_min] = 0x00
|
14
|
+
self[:brightness_max] = 0x64
|
15
|
+
self[:contrast_min] = 0x00
|
16
|
+
self[:contrast_max] = 0x64
|
17
|
+
|
18
|
+
self[:power] = false
|
19
|
+
self[:type] = :projector
|
20
|
+
|
21
|
+
on_update
|
22
|
+
config({
|
23
|
+
tokenize: proc {
|
24
|
+
::UV::AbstractTokenizer.new({
|
25
|
+
indicator: "\x02\x0a", # Header
|
26
|
+
callback: method(:check_complete)
|
27
|
+
})
|
28
|
+
}
|
29
|
+
})
|
30
|
+
|
31
|
+
defaults({
|
32
|
+
delay_on_receive: 200
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
def on_update
|
37
|
+
# Default community value is SONY - can be changed in displays settings
|
38
|
+
@community = str_to_array(setting(:community) || 'SONY')
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def connected
|
43
|
+
@polling_timer = schedule.every('60s', method(:do_poll))
|
44
|
+
end
|
45
|
+
|
46
|
+
def disconnected
|
47
|
+
@polling_timer.cancel unless @polling_timer.nil?
|
48
|
+
@polling_timer = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
#
|
53
|
+
# Power commands
|
54
|
+
#
|
55
|
+
def power(state)
|
56
|
+
if is_affirmative?(state)
|
57
|
+
do_send(:set, :power_on, name: :power, delay_on_receive: 3000)
|
58
|
+
logger.debug "-- sony display requested to power on"
|
59
|
+
else
|
60
|
+
do_send(:set, :power_off, name: :power, delay_on_receive: 3000)
|
61
|
+
logger.debug "-- sony display requested to power off"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Request status update
|
65
|
+
power?
|
66
|
+
end
|
67
|
+
|
68
|
+
def power?(options = {}, &block)
|
69
|
+
options[:emit] = block if block_given?
|
70
|
+
options[:priority] = 0
|
71
|
+
do_send(:get, :power_status, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
#
|
77
|
+
# Input selection
|
78
|
+
#
|
79
|
+
INPUTS = {
|
80
|
+
:vga => [0x00, 0x03],
|
81
|
+
:dvi => [0x00, 0x04],
|
82
|
+
:hdmi => [0x00, 0x05]
|
83
|
+
}
|
84
|
+
INPUTS.merge!(INPUTS.invert)
|
85
|
+
|
86
|
+
|
87
|
+
def switch_to(input)
|
88
|
+
input = input.to_sym
|
89
|
+
return unless INPUTS.has_key? input
|
90
|
+
|
91
|
+
do_send(:set, :input, INPUTS[input], delay_on_receive: 500)
|
92
|
+
logger.debug "-- sony projector, requested to switch to: #{input}"
|
93
|
+
|
94
|
+
input?
|
95
|
+
end
|
96
|
+
|
97
|
+
def input?
|
98
|
+
do_send(:get, :input, {:priority => 0})
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
#
|
103
|
+
# Mute Audio and Video
|
104
|
+
#
|
105
|
+
def mute(val = true)
|
106
|
+
logger.debug "-- sony projector, requested to mute"
|
107
|
+
|
108
|
+
actual = is_affirmative?(val) ? [0x00, 0x01] : [0x00, 0x00]
|
109
|
+
do_send(:set, :mute, actual, delay_on_receive: 500)
|
110
|
+
end
|
111
|
+
|
112
|
+
def unmute
|
113
|
+
logger.debug "-- sony projector, requested to unmute"
|
114
|
+
mute(false)
|
115
|
+
end
|
116
|
+
|
117
|
+
def mute?
|
118
|
+
do_send(:get, :mute, {:priority => 0})
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
#
|
123
|
+
# Automatically creates a callable function for each command
|
124
|
+
# http://blog.jayfields.com/2007/10/ruby-defining-class-methods.html
|
125
|
+
# http://blog.jayfields.com/2008/02/ruby-dynamically-define-method.html
|
126
|
+
#
|
127
|
+
[:contrast, :brightness, :color, :hue, :sharpness].each do |command|
|
128
|
+
# Query command
|
129
|
+
define_method :"#{command}?" do
|
130
|
+
do_send(:get, command, {:priority => 0})
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set value command
|
134
|
+
define_method command do |level|
|
135
|
+
level = in_range(level, 0x64)
|
136
|
+
do_send(:set, command, [0x00, level])
|
137
|
+
__send__(:"#{command}?")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
ERRORS = {
|
143
|
+
0x00 => 'No Error'.freeze,
|
144
|
+
0x01 => 'Lamp Error'.freeze,
|
145
|
+
0x02 => 'Fan Error'.freeze,
|
146
|
+
0x04 => 'Cover Error'.freeze,
|
147
|
+
0x08 => 'Temperature Error'.freeze,
|
148
|
+
0x10 => 'D5V Error'.freeze,
|
149
|
+
0x20 => 'Power Error'.freeze,
|
150
|
+
0x40 => 'Warning Error'.freeze
|
151
|
+
}
|
152
|
+
|
153
|
+
|
154
|
+
def received(byte_str, resolve, command) # Data is default received as a string
|
155
|
+
# Remove community string (useless)
|
156
|
+
logger.debug "sony proj sent: 0x#{byte_to_hex(byte_str[4..-1])}"
|
157
|
+
|
158
|
+
data = str_to_array(byte_str)
|
159
|
+
pjt_command = data[5..6]
|
160
|
+
pjt_length = data[7]
|
161
|
+
pjt_data = data[8..-1]
|
162
|
+
|
163
|
+
if data[4] == 0x01
|
164
|
+
case COMMANDS[pjt_command]
|
165
|
+
when :power_on
|
166
|
+
self[:power] = On
|
167
|
+
when :power_off
|
168
|
+
self[:power] = Off
|
169
|
+
else
|
170
|
+
# Same switch however now we know there is data
|
171
|
+
if pjt_length > 0
|
172
|
+
case COMMANDS[pjt_command]
|
173
|
+
when :power_status
|
174
|
+
case pjt_data[-1]
|
175
|
+
when 0, 8
|
176
|
+
self[:warming] = self[:cooling] = self[:power] = false
|
177
|
+
when 1, 2
|
178
|
+
self[:cooling] = false
|
179
|
+
self[:warming] = self[:power] = true
|
180
|
+
when 3
|
181
|
+
self[:power] = true
|
182
|
+
self[:warming] = self[:cooling] = false
|
183
|
+
when 4, 5, 6, 7
|
184
|
+
self[:cooling] = true
|
185
|
+
self[:warming] = self[:power] = false
|
186
|
+
end
|
187
|
+
|
188
|
+
if self[:warming] || self[:cooling]
|
189
|
+
schedule.in '10s' do
|
190
|
+
power?
|
191
|
+
end
|
192
|
+
end
|
193
|
+
when :mute
|
194
|
+
self[:mute] = pjt_data[-1] == 1
|
195
|
+
when :input
|
196
|
+
self[:input] = INPUTS[pjt_data]
|
197
|
+
when :contrast, :brightness, :color, :hue, :sharpness
|
198
|
+
self[COMMANDS[pjt_command]] = pjt_data[-1]
|
199
|
+
when :error_status
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
else
|
205
|
+
# Command failed..
|
206
|
+
self[:last_error] = pjt_data
|
207
|
+
logger.debug "Command #{pjt_command} failed with Major 0x#{pjt_data[0].to_s(16)} and Minor 0x#{pjt_data[1].to_s(16)}"
|
208
|
+
return :abort
|
209
|
+
end
|
210
|
+
|
211
|
+
:success
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
protected
|
216
|
+
|
217
|
+
|
218
|
+
# Called by the Abstract Tokenizer to confirm we have the
|
219
|
+
# whole message.
|
220
|
+
def check_complete(byte_str)
|
221
|
+
bytes = str_to_array(byte_str)
|
222
|
+
|
223
|
+
# Min message length is 8 bytes
|
224
|
+
return false if bytes.length < 8
|
225
|
+
|
226
|
+
# Check we have the data
|
227
|
+
data = bytes[8..-1]
|
228
|
+
if data.length == bytes[7]
|
229
|
+
return true
|
230
|
+
elsif data.length > bytes[7]
|
231
|
+
# Let the tokeniser know we only want the following number of bytes
|
232
|
+
return 7 + bytes[7]
|
233
|
+
end
|
234
|
+
|
235
|
+
# Still waiting on data
|
236
|
+
return false
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
def do_poll(*args)
|
241
|
+
power?({:priority => 0}) do
|
242
|
+
if self[:power]
|
243
|
+
input?
|
244
|
+
mute?
|
245
|
+
do_send(:get, :error_status, {:priority => 0})
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Constants as per manual page 13
|
251
|
+
# version, category
|
252
|
+
PjTalk_Header = [0x02, 0x0a]
|
253
|
+
|
254
|
+
|
255
|
+
# request, category, command
|
256
|
+
COMMANDS = {
|
257
|
+
power_on: [0x17, 0x2E],
|
258
|
+
power_off: [0x17, 0x2F],
|
259
|
+
input: [0x00, 0x01],
|
260
|
+
mute: [0x00, 0x30],
|
261
|
+
|
262
|
+
error_status: [0x01, 0x01],
|
263
|
+
power_status: [0x01, 0x02],
|
264
|
+
|
265
|
+
contrast: [0x00, 0x10],
|
266
|
+
brightness: [0x00, 0x11],
|
267
|
+
color: [0x00, 0x12],
|
268
|
+
hue: [0x00, 0x13],
|
269
|
+
sharpness: [0x00, 0x14],
|
270
|
+
}
|
271
|
+
COMMANDS.merge!(COMMANDS.invert)
|
272
|
+
|
273
|
+
|
274
|
+
def do_send(getset, command, param = nil, options = {})
|
275
|
+
# Check for missing params
|
276
|
+
if param.is_a? Hash
|
277
|
+
options = param
|
278
|
+
param = nil
|
279
|
+
end
|
280
|
+
|
281
|
+
reqres = getset == :get ? [0x01] : [0x00]
|
282
|
+
|
283
|
+
# Control + Mode
|
284
|
+
if param.nil?
|
285
|
+
options[:name] = command if options[:name].nil?
|
286
|
+
cmd = COMMANDS[command] + [0x00]
|
287
|
+
else
|
288
|
+
options[:name] = :"#{command}_req" if options[:name].nil?
|
289
|
+
if !param.is_a?(Array)
|
290
|
+
param = [param]
|
291
|
+
end
|
292
|
+
cmd = COMMANDS[command] + [param.length] + param
|
293
|
+
end
|
294
|
+
|
295
|
+
# Build the IDTalk header # set request every time?
|
296
|
+
pjt_cmd = PjTalk_Header + @community + reqres + cmd
|
297
|
+
|
298
|
+
send(pjt_cmd, options)
|
299
|
+
end
|
300
|
+
end
|