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,125 @@
1
+ # encoding: US-ASCII
2
+
3
+ module Biamp; end
4
+
5
+ # TELNET port 23
6
+
7
+ class Biamp::Nexia
8
+ include ::Orchestrator::Constants
9
+ include ::Orchestrator::Transcoder
10
+
11
+ def on_load
12
+ self[:fader_min] = -36 # specifically for tonsley
13
+ self[:fader_max] = 12
14
+
15
+ # max +12
16
+ # min -100
17
+
18
+ config({
19
+ tokenize: true,
20
+ delimiter: /\xFF\xFE\x01|\r\n/
21
+ })
22
+ end
23
+
24
+ def on_unload
25
+ end
26
+
27
+ def on_update
28
+ end
29
+
30
+
31
+ def connected
32
+ send("\xFF\xFE\x01") # Echo off
33
+ do_send('GETD', 0, 'DEVID')
34
+
35
+ @polling_timer = schedule.every('60s') do
36
+ do_send('GETD', 0, 'DEVID')
37
+ end
38
+ end
39
+
40
+ def disconnected
41
+ @polling_timer.cancel unless @polling_timer.nil?
42
+ @polling_timer = nil
43
+ end
44
+
45
+
46
+ def preset(number)
47
+ #
48
+ # Recall Device 0 Preset number 1001
49
+ # Device Number will always be 0 for Preset strings
50
+ # 1001 == minimum preset number
51
+ #
52
+ do_send('RECALL', 0, 'PRESET', number)
53
+ end
54
+
55
+ def fader(fader_id, level)
56
+ # value range: -100 ~ 12
57
+ do_send('SETD', self[:device_id], 'FDRLVL', fader_id, 1, level)
58
+ end
59
+
60
+ def mute(fader_id)
61
+ do_send('SETD', self[:device_id], 'FDRMUTE', fader_id, 1, 1)
62
+ end
63
+
64
+ def unmute(fader_id)
65
+ do_send('SETD', self[:device_id], 'FDRMUTE', fader_id, 1, 0)
66
+ end
67
+
68
+ def query_fader(fader_id)
69
+ send("GET #{self[:device_id]} FDRLVL #{fader_id} 1 \n") do |data|
70
+ if data == "-ERR"
71
+ :abort
72
+ else
73
+ self[:"fader_#{fader_id}"] = data.to_i
74
+ :success
75
+ end
76
+ end
77
+ end
78
+
79
+ def query_mute(fader_id)
80
+ send("GET #{self[:device_id]} FDRMUTE #{fader_id} 1 \n") do |data|
81
+ if data == "-ERR"
82
+ :abort
83
+ else
84
+ self[:"fader_#{fader_id}_mute"] = data.to_i == 1
85
+ :success
86
+ end
87
+ end
88
+ end
89
+
90
+
91
+ def received(data, resolve, command)
92
+ data = data.split(' ')
93
+
94
+ if data.length == 1
95
+ if data[-1] == "-ERR"
96
+ logger.debug "Nexia Invalid Command sent #{command[:data]}" if !!command
97
+ return :abort
98
+ end
99
+ return :success # data[-1] == "+OK" || data == "" # Echo off
100
+ end
101
+
102
+ unless data[2].nil?
103
+ case data[2].to_sym
104
+ when :FDRLVL
105
+ self[:"fader_#{data[3]}"] = data[-2].to_i
106
+ when :FDRMUTE
107
+ self[:"fader_#{data[3]}_mute"] = data[-2] == "1"
108
+ when :DEVID
109
+ self[:device_id] = data[-2].to_i
110
+ end
111
+ end
112
+
113
+ return :success
114
+ end
115
+
116
+
117
+
118
+ private
119
+
120
+
121
+ def do_send(*args)
122
+ send("#{args.join(' ')}\n")
123
+ end
124
+ end
125
+
@@ -0,0 +1,265 @@
1
+ module Bss; end
2
+
3
+ # TCP port 1023
4
+
5
+ class Bss::Blu100
6
+ include ::Orchestrator::Constants
7
+ include ::Orchestrator::Transcoder
8
+
9
+ def on_load
10
+ defaults({
11
+ :wait => false
12
+ })
13
+ config({
14
+ tokenize: true,
15
+ delimiter: "\x03",
16
+ indicator: "\x02"
17
+ })
18
+ end
19
+
20
+ def on_unload
21
+ end
22
+
23
+ def on_update
24
+ end
25
+
26
+ def connected
27
+ subscribe_percent(1, 60000)
28
+ @polling_timer = schedule.every('150s') do # Every 2.5 min
29
+ subscribe_percent(1, 60000) # Request the level of Hybrid I/O Card A
30
+ end # This works to maintain the connection
31
+ end
32
+
33
+ def disconnected
34
+ #
35
+ # Disconnected may be called without calling connected
36
+ # Hence the check if timer is nil here
37
+ #
38
+ @polling_timer.cancel unless @polling_timer.nil?
39
+ @polling_timer = nil
40
+ end
41
+
42
+
43
+ OPERATION_CODE = {
44
+ :set_state => 0x88,
45
+ :subscribe_state => 0x89,
46
+ :unsubscribe_state => 0x8A,
47
+ :venue_preset => 0x8B,
48
+ :param_preset => 0x8C,
49
+ :set_percent => 0x8D,
50
+ :subscribe_percent => 0x8E,
51
+ :unsubscribe_percent => 0x8F,
52
+ :set_relative_percent => 0x90
53
+ }
54
+ OPERATION_CODE.merge!(OPERATION_CODE.invert)
55
+
56
+
57
+ def preset(number)
58
+ number = number.to_i
59
+
60
+ do_send([OPERATION_CODE[:venue_preset]] + number_to_data(number))
61
+ end
62
+
63
+
64
+ #
65
+ # Level controls
66
+ #
67
+ def fader(fader, percent)
68
+ percent = percent.to_i
69
+ percent = 6553600 if percent > 6553600
70
+ percent = 0 if percent < 0
71
+
72
+ percent = number_to_data(percent)
73
+
74
+ do_send([OPERATION_CODE[:set_percent]] + NODE + VIRTUAL + number_to_object(fader.to_i) + CONTROLS[:gain] + percent)
75
+ subscribe_percent(fader)
76
+ end
77
+
78
+ def mute(fader)
79
+ do_send([OPERATION_CODE[:set_state]] + NODE + VIRTUAL + number_to_object(fader.to_i) + CONTROLS[:mute] + number_to_data(1))
80
+ subscribe_state(fader)
81
+ end
82
+
83
+ def unmute(fader)
84
+ do_send([OPERATION_CODE[:set_state]] + NODE + VIRTUAL + number_to_object(fader.to_i) + CONTROLS[:mute] + number_to_data(0))
85
+ subscribe_state(fader)
86
+ end
87
+
88
+
89
+ def query_fader(fader_id)
90
+ subscribe_percent(fader_id)
91
+ end
92
+
93
+ def query_mute(fader_id)
94
+ subscribe_state(fader_id)
95
+ end
96
+
97
+
98
+ #
99
+ # Percent controls for relative values
100
+ #
101
+ def subscribe_percent(fader, rate = 0, control = CONTROLS[:gain]) # rate must be 0 for non meter controls
102
+ fader = number_to_object(fader.to_i)
103
+ rate = number_to_data(rate.to_i)
104
+
105
+ do_send([OPERATION_CODE[:subscribe_percent]] + NODE + VIRTUAL + fader + control + rate)
106
+ end
107
+
108
+ def unsubscribe_percent(fader, control = CONTROLS[:gain]) # rate must be 0 for non meter controls
109
+ fader = number_to_object(fader.to_i)
110
+ rate = number_to_data(0)
111
+
112
+ do_send([OPERATION_CODE[:unsubscribe_percent]] + NODE + VIRTUAL + fader + control + rate)
113
+ end
114
+
115
+ #
116
+ # State controls are for discrete values
117
+ #
118
+ def subscribe_state(fader, rate = 0, control = CONTROLS[:mute]) # 1000 == every second
119
+ fader = number_to_object(fader.to_i)
120
+ rate = number_to_data(rate.to_i)
121
+
122
+ do_send([OPERATION_CODE[:subscribe_state]] + NODE + VIRTUAL + fader + control + rate)
123
+ end
124
+
125
+ def unsubscribe_state(fader, control = CONTROLS[:mute]) # 1000 == every second
126
+ fader = number_to_object(fader.to_i)
127
+ rate = number_to_data(0)
128
+
129
+ do_send([OPERATION_CODE[:unsubscribe_state]] + NODE + VIRTUAL + fader + control + rate)
130
+ end
131
+
132
+
133
+ def received(data, resolve, command)
134
+ #
135
+ # Grab the message body
136
+ #
137
+ data = data.split("\x02")
138
+ data = str_to_array(data[-1])
139
+
140
+ #
141
+ # Unescape any control characters
142
+ #
143
+ message = []
144
+ found = false
145
+ data.each do |byte|
146
+ if found
147
+ found = false
148
+ message << (byte - 0x80)
149
+ elsif RESERVED_CHARS.include? byte
150
+ found = true
151
+ else
152
+ message << byte
153
+ end
154
+ end
155
+
156
+ #
157
+ # Process the response
158
+ #
159
+ if check_checksum(message)
160
+ logger.debug "Blu100 sent 0x#{byte_to_hex(array_to_str(message))}"
161
+
162
+ data = byte_to_hex(array_to_str(message[-4..-1])).to_i(16)
163
+ type = message[0] # Always sent
164
+
165
+ node = message[1..2]
166
+ vi = message[3]
167
+ obj = message[4..6]
168
+ cntrl = message[7..8]
169
+
170
+ case OPERATION_CODE[type]
171
+ when :set_state # This is the mute response
172
+ obj = byte_to_hex(array_to_str(obj)).to_i(16)
173
+ if CONTROLS[cntrl] == :mute
174
+ self[:"fader_#{obj}_mute"] = data == 1
175
+ else
176
+ self[:"fader_#{obj}"] = data
177
+ end
178
+ when :subscribe_state
179
+ when :unsubscribe_state
180
+ when :venue_preset
181
+ when :param_preset
182
+ when :set_percent # This is the fader response
183
+ obj = byte_to_hex(array_to_str(obj)).to_i(16)
184
+ self[:"fader_#{obj}"] = data
185
+ when :subscribe_percent
186
+ when :unsubscribe_percent
187
+ when :set_relative_percent
188
+ end
189
+
190
+ return :success
191
+ else
192
+ logger.warn "Blu100 Checksum error: 0x#{byte_to_hex(array_to_str(message))}"
193
+ return :failed
194
+ end
195
+ end
196
+
197
+
198
+ protected
199
+
200
+
201
+ def number_to_data(num)
202
+ str_to_array(hex_to_byte(num.to_s(16).upcase.rjust(8, '0')))
203
+ end
204
+
205
+ def number_to_object(num)
206
+ str_to_array(hex_to_byte(num.to_s(16).upcase.rjust(6, '0')))
207
+ end
208
+
209
+
210
+ RESERVED_CHARS = [0x02, 0x03, 0x06, 0x15, 0x1B]
211
+ NODE = [0,0] # node we are connected to
212
+ VIRTUAL = [3] # virtual device is always 3 for audio devices
213
+
214
+ CONTROLS = {
215
+ :gain => [0,0],
216
+ :mute => [0,1]
217
+ }
218
+ CONTROLS.merge!(CONTROLS.invert)
219
+
220
+
221
+ def check_checksum(data)
222
+ #
223
+ # Loop through the second to the second last element
224
+ # Delimiter is removed automatically
225
+ #
226
+ check = 0
227
+ data[0..-2].each do |byte|
228
+ check = check ^ byte
229
+ end
230
+ return check == data.pop # Check the check sum equals the last element
231
+ end
232
+
233
+
234
+
235
+ def do_send(command, options = {})
236
+ #
237
+ # build checksum
238
+ #
239
+ check = 0
240
+ command.each do |byte|
241
+ check = check ^ byte
242
+ end
243
+ command = command + [check]
244
+
245
+ #
246
+ # Substitute reserved characters
247
+ #
248
+ substituted = []
249
+ command.each do |byte|
250
+ if RESERVED_CHARS.include? byte
251
+ substituted << 0x1B << (byte + 0x80)
252
+ else
253
+ substituted << byte
254
+ end
255
+ end
256
+
257
+ #
258
+ # Add the control characters
259
+ #
260
+ substituted.unshift 0x02
261
+ substituted << 0x03
262
+
263
+ send(substituted, options)
264
+ end
265
+ end
@@ -0,0 +1,256 @@
1
+ module Clipsal; end
2
+
3
+
4
+ #
5
+ # Common Headers
6
+ # 0x03 == point - point -multipoint, low pri
7
+ # 0x05 == point - multipoint, low pri
8
+ # 0x06 == point - point, low pri
9
+ #
10
+ # 11xxx110 (x == reserved)
11
+ # -- Priority, 11 == high, 00 == low
12
+ # --- Destination, 011 = P-P-M, 101 = P-M, 110 = P-P
13
+ #
14
+ #
15
+ # Commands are formatted as: \ + Header + 00 + Data + checksum + <cr>
16
+ #
17
+ # Turn group on \ + 05 (MP header) + 38 (lighting) + 00 + 79 (group on) + XX (group number) + checksum + <cr>
18
+ # Turn group off \ + 05 (MP header) + 38 (lighting) + 00 + 01 (group off) + XX (group number) + checksum + <cr>
19
+ # Ramp a group \ + 05 (MP header) + 38 (lighting) + 00 + 79 (group on) + XX (group number) + checksum + <cr>
20
+ #
21
+
22
+
23
+
24
+ class Clipsal::CBus
25
+ include ::Orchestrator::Constants
26
+ include ::Orchestrator::Transcoder
27
+
28
+ def on_load
29
+ defaults({
30
+ :wait => false
31
+ })
32
+
33
+ config({
34
+ tokenize: true,
35
+ delimiter: "\x0D"
36
+ })
37
+ end
38
+
39
+ def on_unload
40
+ end
41
+
42
+ def on_update
43
+
44
+ end
45
+
46
+
47
+ def connected
48
+ send("|||\r", :wait => false) # Ensure we are in smart mode
49
+ @polling_timer = schedule.every('60s') do
50
+ logger.debug "-- Polling CBUS"
51
+ send("|||\r", :wait => false) # Ensure we are in smart mode
52
+ end
53
+ end
54
+
55
+ def disconnected
56
+ @polling_timer.cancel unless @polling_timer.nil?
57
+ @polling_timer = nil
58
+ end
59
+
60
+
61
+ def lighting(group, state, application = 0x38)
62
+ group = group & 0xFF
63
+ application = application & 0xFF
64
+
65
+ command = [0x05, application, 0x00]
66
+ if [On, 1, :on, 'on'].include?(state)
67
+ state = On
68
+ command << 0x79 # Group on
69
+ else
70
+ state = Off
71
+ command << 0x01 # Group off
72
+ end
73
+ command << group
74
+
75
+ self["lighting_group_#{group}"] = state
76
+
77
+ do_send(command)
78
+ end
79
+
80
+
81
+ def lighting_ramp(group, level, rate = 0b0001, application = 0x38)
82
+
83
+ #
84
+ # rates:
85
+ # => 0 == instant
86
+ # => 1 == 4sec
87
+ # => 2 == 8sec etc
88
+ #
89
+ rate = ((rate & 0x0F) << 3) | 0b010 # The command is structured as: 0b0 xxxx 010 where xxxx == rate
90
+ group = group & 0xFF
91
+ level = level & 0xFF
92
+ application = application & 0xFF
93
+
94
+ lighting_term_ramp(group)
95
+ command = [0x05, application, 0x00, rate, group, level]
96
+
97
+ do_send(command)
98
+ end
99
+
100
+
101
+
102
+ def blinds(group, action, application = 0x38)
103
+ group = group & 0xFF
104
+ application = application & 0xFF
105
+
106
+ command = [0x05, application, 0x00]
107
+ if is_affirmative?(action)
108
+ action = Down
109
+ command += [0x1A, group, 0x00]
110
+ else
111
+ command += [0x02, group]
112
+
113
+ if is_negatory?(action)
114
+ action = Up
115
+ command << 0xFF
116
+ else
117
+ # Stop (need to confirm this)
118
+ command << 0x05
119
+ end
120
+ end
121
+
122
+ do_send(command)
123
+ end
124
+
125
+
126
+ def trigger(group, action)
127
+ action = action.to_i
128
+ group = group.to_i
129
+
130
+ group = group & 0xFF
131
+ action = action & 0xFF
132
+ command = [0x05, 0xCA, 0x00, 0x02, group, action]
133
+
134
+ self["trigger_group_#{group}"] = action
135
+
136
+ do_send(command)
137
+ end
138
+
139
+
140
+ def trigger_kill(group)
141
+ group = group.to_i
142
+
143
+ group = group & 0xFF
144
+ command = [0x05, 0xCA, 0x00, 0x01, group]
145
+ do_send(command)
146
+ end
147
+
148
+
149
+ def received(data, resolve, command)
150
+ # Debug here will sometimes have the \n char
151
+ # This is removed by the hex_to_byte function
152
+ logger.debug "CBus sent #{data}"
153
+
154
+ data = str_to_array(hex_to_byte(data))
155
+
156
+ if !check_checksum(data)
157
+ logger.debug "CBus checksum failed"
158
+ return :failed
159
+ end
160
+
161
+ # We are only looking at Point -> MultiPoint commands
162
+ return if data[0] != 0x05
163
+ # 0x03 == Point -> Point -> MultiPoint
164
+ # 0x06 == Point -> Point
165
+
166
+ application = data[2] # The application being referenced
167
+ commands = data[4..-2] # Remove the header + checksum
168
+
169
+ while commands.length > 0
170
+ current = commands.shift
171
+
172
+ case application
173
+ when 0xCA # Trigger group
174
+ case current
175
+ when 0x02 # Trigger Event (ex: 0504CA00 020101 29)
176
+ self["trigger_group_#{commands.shift}"] = commands.shift # Action selector
177
+ when 0x01 # Trigger Min
178
+ self["trigger_group_#{commands.shift}"] = 0
179
+ when 0x79 # Trigger Max
180
+ self["trigger_group_#{commands.shift}"] = 0xFF
181
+ when 0x09 # Indicator Kill (ex: 0504CA00 0901 23)
182
+ commands.shift # Group (turns off indicators of all scenes triggered by this group)
183
+ else
184
+ break # We don't know what data is here
185
+ end
186
+ when 0x30..0x5F # Lighting group
187
+ case current
188
+ when 0x01 # Group off (ex: 05043800 0101 0102 0103 0104 7905 33)
189
+ self["lighting_group_#{commands.shift}"] = Off
190
+ when 0x79 # Group on (ex: 05013800 7905 44)
191
+ self["lighting_group_#{commands.shift}"] = On
192
+ when 0x02 # Blinds up or stop
193
+ group = commands.shift
194
+ value = commands.shift
195
+ if value == 0xFF
196
+ self["blinds_group_#{group}"] = Up
197
+ elsif value == 0x05 # Value needs confirmation
198
+ self["blinds_group_#{group}"] = :stopped
199
+ end
200
+ when 0x1A # Blinds down
201
+ group = commands.shift
202
+ value = commands.shift
203
+ self["blinds_group_#{group}"] = Down if value == 0x00
204
+ when 0x09 # Terminate Ramp
205
+ commands.shift # Group address
206
+ else
207
+ if (current & 0b10000101) == 0 # Ramp to level (ex: 05013800 0205FF BC)
208
+ commands.shift(2) # Group address, level
209
+ else
210
+ break # We don't know what data is here
211
+ end
212
+ end
213
+ else
214
+ break # We haven't programmed this application
215
+ end
216
+ end
217
+
218
+ return :success
219
+ end
220
+
221
+
222
+ protected
223
+
224
+
225
+ def lighting_term_ramp(group)
226
+ command = [0x05, 0x38, 0x00, 0x09, group]
227
+ do_send(command)
228
+ end
229
+
230
+
231
+ def checksum(data)
232
+ check = 0
233
+ data.each do |byte|
234
+ check += byte
235
+ end
236
+ check = check % 0x100
237
+ check = ((check ^ 0xFF) + 1) & 0xFF
238
+ return check
239
+ end
240
+
241
+ def check_checksum(data)
242
+ check = 0
243
+ data.each do |byte|
244
+ check += byte
245
+ end
246
+ return (check % 0x100) == 0x00
247
+ end
248
+
249
+
250
+ def do_send(command, options = {})
251
+ string = byte_to_hex(array_to_str(command << checksum(command))).upcase
252
+ send("\\#{string}\r", options)
253
+ #logger.debug "CBus module sent #{string}"
254
+ end
255
+ end
256
+