aca-device-modules 1.0.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.
@@ -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
+