ansible4ozw 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/lib/ansible.rb +47 -0
  2. data/lib/ansible/ansible_callback.rb +142 -0
  3. data/lib/ansible/ansible_device.rb +68 -0
  4. data/lib/ansible/ansible_value.rb +179 -0
  5. data/lib/ansible/config.rb +92 -0
  6. data/lib/ansible/devices/ansible_dimmer.rb +80 -0
  7. data/lib/ansible/devices/ansible_switch.rb +66 -0
  8. data/lib/ansible/knx/EIBConnection.rb +2371 -0
  9. data/lib/ansible/knx/dpt/canonical_1bit.rb +54 -0
  10. data/lib/ansible/knx/dpt/dpt1.rb +224 -0
  11. data/lib/ansible/knx/dpt/dpt10.rb +85 -0
  12. data/lib/ansible/knx/dpt/dpt11.rb +72 -0
  13. data/lib/ansible/knx/dpt/dpt12.rb +61 -0
  14. data/lib/ansible/knx/dpt/dpt13.rb +100 -0
  15. data/lib/ansible/knx/dpt/dpt14.rb +87 -0
  16. data/lib/ansible/knx/dpt/dpt15.rb +72 -0
  17. data/lib/ansible/knx/dpt/dpt16.rb +67 -0
  18. data/lib/ansible/knx/dpt/dpt17.rb +65 -0
  19. data/lib/ansible/knx/dpt/dpt18.rb +66 -0
  20. data/lib/ansible/knx/dpt/dpt19.rb +100 -0
  21. data/lib/ansible/knx/dpt/dpt2.rb +156 -0
  22. data/lib/ansible/knx/dpt/dpt3.rb +104 -0
  23. data/lib/ansible/knx/dpt/dpt4.rb +75 -0
  24. data/lib/ansible/knx/dpt/dpt5.rb +124 -0
  25. data/lib/ansible/knx/dpt/dpt6.rb +73 -0
  26. data/lib/ansible/knx/dpt/dpt7.rb +146 -0
  27. data/lib/ansible/knx/dpt/dpt8.rb +118 -0
  28. data/lib/ansible/knx/dpt/dpt9.rb +204 -0
  29. data/lib/ansible/knx/dpt/tests/test_dpt10.rb +45 -0
  30. data/lib/ansible/knx/dpt/tests/test_dpt9.rb +60 -0
  31. data/lib/ansible/knx/hexdump.rb +113 -0
  32. data/lib/ansible/knx/knx_dpt.rb +58 -0
  33. data/lib/ansible/knx/knx_dpt_scalar.rb +62 -0
  34. data/lib/ansible/knx/knx_eistypes.rb +76 -0
  35. data/lib/ansible/knx/knx_protocol.rb +99 -0
  36. data/lib/ansible/knx/knx_scene.rb +48 -0
  37. data/lib/ansible/knx/knx_tools.rb +76 -0
  38. data/lib/ansible/knx/knx_transceiver.rb +237 -0
  39. data/lib/ansible/knx/knx_value.rb +327 -0
  40. data/lib/ansible/openzwave/ozw_constants.rb +11 -0
  41. data/lib/ansible/openzwave/ozw_headers.rb +80 -0
  42. data/lib/ansible/openzwave/ozw_remote_manager.rb +7615 -0
  43. data/lib/ansible/openzwave/ozw_types.rb +406 -0
  44. data/lib/ansible/orbiter_proxy.rb +12 -0
  45. data/lib/ansible/transceiver.rb +63 -0
  46. data/lib/ansible/zwave/types/valuetype_bool.rb +74 -0
  47. data/lib/ansible/zwave/types/valuetype_button.rb +63 -0
  48. data/lib/ansible/zwave/types/valuetype_byte.rb +78 -0
  49. data/lib/ansible/zwave/types/valuetype_decimal.rb +64 -0
  50. data/lib/ansible/zwave/types/valuetype_int.rb +63 -0
  51. data/lib/ansible/zwave/types/valuetype_list.rb +64 -0
  52. data/lib/ansible/zwave/types/valuetype_short.rb +63 -0
  53. data/lib/ansible/zwave/types/valuetype_string.rb +61 -0
  54. data/lib/ansible/zwave/zwave_command_classes.rb +113 -0
  55. data/lib/ansible/zwave/zwave_node.rb +5 -0
  56. data/lib/ansible/zwave/zwave_protocol.rb +52 -0
  57. data/lib/ansible/zwave/zwave_transceiver.rb +435 -0
  58. data/lib/ansible/zwave/zwave_value.rb +193 -0
  59. metadata +108 -0
@@ -0,0 +1,48 @@
1
+ =begin
2
+ Project Ansible - An extensible home automation scripting framework
3
+ ----------------------------------------------------
4
+ Copyright (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
5
+
6
+ SOFTWARE NOTICE AND LICENSE
7
+
8
+ Project Ansible is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU Lesser General Public License as published
10
+ by the Free Software Foundation, either version 3 of the License,
11
+ or (at your option) any later version.
12
+
13
+ Project Ansible is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public License
19
+ along with Project Ansible. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ for more information on the LGPL, see:
22
+ http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
23
+ =end
24
+
25
+ module Ansible
26
+
27
+ module KNX
28
+
29
+ # KNX Scene control module
30
+ module Scene
31
+
32
+ @scene_values = {}
33
+
34
+ def store(scene_id)
35
+ @scene_values[scene_id] = @current_value
36
+ end
37
+
38
+ def recall(scene_id)
39
+ if val = @scene_values[scene_id] then
40
+ set(scene_id)
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,76 @@
1
+ =begin
2
+ Project Ansible - An extensible home automation scripting framework
3
+ ----------------------------------------------------
4
+ Copyright (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
5
+
6
+ SOFTWARE NOTICE AND LICENSE
7
+
8
+ Project Ansible is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU Lesser General Public License as published
10
+ by the Free Software Foundation, either version 3 of the License,
11
+ or (at your option) any later version.
12
+
13
+ Project Ansible is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public License
19
+ along with Project Ansible. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ for more information on the LGPL, see:
22
+ http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
23
+ =end
24
+
25
+ # various KNX tool functions
26
+
27
+ # addr2str: Convert an integer to an EIB address string, in the form "1/2/3" or "1.2.3"
28
+ def addr2str(a, ga=false)
29
+ str=""
30
+ if a.is_a?Array then
31
+ #~ a = a[0]*256+a[1]
32
+ a = a.pack('c*').unpack('n')[0]
33
+ end
34
+ if (ga)
35
+ str = "#{(a >> 11) & 0xf}/#{(a >> 8) & 0x7}/#{a & 0xff}"
36
+ else
37
+ str = "#{a >> 12}.#{(a >> 8) & 0xf}.#{a & 0xff}"
38
+ end
39
+ return(str)
40
+ end
41
+
42
+ # str2addr: Parse an address string into an unsigned 16-bit integer
43
+ def str2addr(s)
44
+ if m = s.match(/(\d*)\/(\d*)\/(\d*)/)
45
+ a, b, c = m[1].to_i, m[2].to_i, m[3].to_i
46
+ return ((a & 0x01f) << 11) | ((b & 0x07) << 8) | ((c & 0xff))
47
+ end
48
+ if m = s.match(/(\d*)\/(\d*)/)
49
+ a,b = m[1].to_i, m[2].to_i
50
+ return ((a & 0x01f) << 11) | ((b & 0x7FF))
51
+ end
52
+ if s.start_with?("0x")
53
+ return (s.to_i & 0xffff)
54
+ end
55
+ end
56
+
57
+ def decode_framedata(data)
58
+ case data
59
+ when Fixnum then "0x"+data.to_s(16).upcase
60
+ when Array then data.collect{|b| "0x"+b.to_s(16).upcase}
61
+ when String then data.unpack('C*').collect{|b| "0x"+b.to_s(16).upcase}
62
+ else data
63
+ end
64
+ end
65
+
66
+ def frame_inspect(frame)
67
+ return "#{Time.now.strftime('%a %d/%m/%Y %H:%M:%S %Z')}: #{Ansible::KNX::APCICODES[frame.apci]}" +
68
+ " prio=#{Ansible::KNX::PRIOCLASSES[frame.prio_class]}" + " len=#{frame.datalength}" +
69
+ " from #{addr2str(frame.src_addr)} to #{addr2str(frame.dst_addr, frame.daf)}" +
70
+ " data=" + decode_framedata(frame.datalength > 1 ? frame.data : frame.apci_data).inspect
71
+ end
72
+
73
+ #ga = 17*256 + 200
74
+ #~ ga = [17, 200]
75
+ #~ puts(addr2str(ga))
76
+ #~ dest= str2addr("1/2/0")
@@ -0,0 +1,237 @@
1
+ =begin
2
+ Project Ansible - An extensible home automation scripting framework
3
+ ----------------------------------------------------
4
+ Copyright (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
5
+
6
+ SOFTWARE NOTICE AND LICENSE
7
+
8
+ Project Ansible is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU Lesser General Public License as published
10
+ by the Free Software Foundation, either version 3 of the License,
11
+ or (at your option) any later version.
12
+
13
+ Project Ansible is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public License
19
+ along with Project Ansible. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ for more information on the LGPL, see:
22
+ http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
23
+ =end
24
+
25
+ require 'cgi'
26
+
27
+ require 'config'
28
+ require 'transceiver'
29
+ require 'EIBConnection'
30
+ require 'ansible_callback'
31
+ require 'knx_protocol'
32
+ require 'knx_value'
33
+ require 'knx_tools'
34
+
35
+ module Ansible
36
+
37
+ module KNX
38
+
39
+ #
40
+ # The KNX Transceiver is an object responsible for i/o with the KNX bus.
41
+ # It does so using eibd, part of BCU-SDK the open-source libary for KNX.
42
+ class KNX_Transceiver < Transceiver
43
+ include AnsibleCallback
44
+
45
+ # a special exception to break the knx tranceiver loop
46
+ class NormalExit < Exception; end
47
+
48
+ attr_reader :stomp
49
+
50
+ # initialize a KNXTranceiver
51
+ #
52
+ # *params:
53
+ # [connURL] an eibd connection URL. see eibd --help for acceptable values
54
+ def initialize(connURL=KNX_URL)
55
+ raise "Already initialized!" unless Ansible::KNX::KNXValue.transceiver.nil?
56
+ @connURL = connURL
57
+ @monitor_conn_ok, @send_conn_ok = false, false
58
+ @send_mutex = Mutex.new()
59
+ @knxbuf = EIBBuffer.new()
60
+ #
61
+ super()
62
+ # store reference to ourselves to the classes that use us
63
+ Ansible::KNX::KNXValue.transceiver = self
64
+ # register default handler for KNX frames
65
+ add_callback(:onKNXtelegram) { | sender, cb, frame |
66
+ puts(frame_inspect(frame)) if $DEBUG
67
+ case frame.apci.value
68
+ when 0 then # A_GroupValue_Read
69
+ puts "read request for knx address #{addr2str(frame.dst_addr, frame.daf)}"
70
+ AnsibleValue[:groups => [frame.dst_addr]].each { |v|
71
+ unless v.current_value.nil? then
72
+ puts "==> responding with value #{v}"
73
+ send_apdu_raw(frame.dst_addr, v.to_apdu(0x40))
74
+ end
75
+ }
76
+ when 1 then # A_GroupValue_Response
77
+ puts "response frame by #{addr2str(frame.src_addr)} for knx address #{addr2str(frame.dst_addr, frame.daf)}"
78
+ AnsibleValue[:groups => [frame.dst_addr]].each { |v|
79
+ v.update_from_frame(frame)
80
+ puts "synchronized knx value #{v} from frame #{frame.inspect}" if $DEBUG
81
+ }
82
+ when 2 then # A_GroupValue_Write
83
+ AnsibleValue[:groups => [frame.dst_addr]].each { |v|
84
+ v.update_from_frame(frame)
85
+ puts "updated knx value #{v} from frame #{frame.inspect}" if $DEBUG
86
+ }
87
+ end
88
+ }
89
+ end
90
+
91
+ # initialize eibd connection
92
+ def init_eibd(conn_symbol, conn_ok_symbol)
93
+ unless instance_variable_get(conn_ok_symbol)
94
+ begin
95
+ puts("KNX: init #{conn_symbol} to #{@connURL}")
96
+ conn = EIBConnection.new()
97
+ conn.EIBSocketURL(@connURL)
98
+ instance_variable_set(conn_symbol, conn)
99
+ instance_variable_set(conn_ok_symbol, true)
100
+ return(conn)
101
+ rescue Errno::ECONNRESET => e
102
+ conn.EIBClose
103
+ instance_variable_set(conn_ok_symbol, false)
104
+ puts "init_eibd: Disconnected, retrying in 10 seconds..."
105
+ sleep(10)
106
+ end
107
+ end
108
+ end
109
+
110
+ # get handle to eibd monitor connection
111
+ def eibd_connection(conn_symbol, conn_ok_symbol)
112
+ if instance_variable_get(conn_ok_symbol) then
113
+ return(instance_variable_get(conn_symbol))
114
+ else
115
+ init_eibd(conn_symbol, conn_ok_symbol)
116
+ end
117
+ end
118
+
119
+ # get handle to KNX monitoring connection, reconnecting if necessary
120
+ def monitor_conn; return(eibd_connection(:@monitor_conn, :@monitor_conn_ok)); end
121
+
122
+ # get handle to KNX sending connection, reconnecting if necessary
123
+ def send_conn; return(eibd_connection(:@send_conn, :@send_conn_ok)); end
124
+
125
+ # the main KNX transceiver thread
126
+ def run()
127
+ puts("KNX Transceiver thread is running!")
128
+ @stomp = nil
129
+ begin
130
+ #### part 1: connect to STOMP broker
131
+ @stomp = OnStomp.connect(STOMP_URL)
132
+ #### part 2: subscribe to command channel, listen for messages and pass them to KNX
133
+ # @stomp.subscribe KNX_COMMAND_TOPIC do |msg|
134
+ # dest = msg.headers['dest_addr'].to_i
135
+ # #TODO: check address limits
136
+ # apdu = Marshal.load(CGI.unescape(msg.body))
137
+ # send_apdu_raw(dest, apdu)
138
+ # end
139
+ ##### part 3: monitor KNX bus, post all activity to /knx/monitor
140
+ vbm = monitor_conn.EIBOpenVBusmonitor()
141
+ loop do
142
+ len = monitor_conn.EIBGetBusmonitorPacket(@knxbuf)
143
+ #puts "knxbuffer=="+@knxbuf.buffer.inspect
144
+ frame = L_DATA_Frame.read(@knxbuf.buffer.pack('c*'))
145
+ #puts "frame:\n\t"
146
+ headers = {}
147
+ frame.field_names.each { |fieldname|
148
+ field = frame.send(fieldname)
149
+ #puts "\t#{fieldname} == #{field.value}"
150
+ headers[fieldname] = CGI.escape(field.value.to_s)
151
+ }
152
+ @stomp.send(KNX_MONITOR_TOPIC, "KNX Activity", headers)
153
+ #puts Ansible::KNX::APCICODES[frame.apci] + " packet from " +
154
+ # addr2str(frame.src_addr) + " to " + addr2str(frame.dst_addr, frame.daf) +
155
+ # " priority=" + Ansible::KNX::PRIOCLASSES[frame.prio_class]
156
+ fire_callback(:onKNXtelegram, frame.dst_addr, frame)
157
+ #
158
+ end
159
+ rescue Errno::ECONNRESET => e
160
+ @monitor_conn_ok = false
161
+ puts("EIBD disconnected! retrying in 10 seconds..")
162
+ sleep(10)
163
+ retry
164
+ rescue NormalExit => e
165
+ puts("KNX transceiver terminating gracefully...")
166
+ rescue Exception => e
167
+ puts("Exception in KNX server thread: #{e}")
168
+ puts("backtrace:\n " << e.backtrace.join("\n "))
169
+ sleep(3)
170
+ retry
171
+ # ensure
172
+ #puts "Closing EIB connection..."
173
+ #@monitor_conn.EIBClose() if @monitor_conn
174
+ #puts "Closing STOMP connection..."
175
+ #@stomp.disconnect if @stomp
176
+ end
177
+ end #def run()
178
+
179
+ # send a raw APDU to the KNX bus.
180
+ #
181
+ # * Arguments:
182
+ # [dest] destination (16-bit integer)
183
+ # [apdu] raw APDU (binary string)
184
+ def send_apdu_raw(dest, apdu)
185
+ @send_mutex.synchronize {
186
+ raise 'apdu must be a byte array!' unless apdu.is_a?Array
187
+ puts("KNX transceiver: sending to group address #{dest}, #{apdu.inspect}") if $DEBUG
188
+ if (send_conn.EIBOpenT_Group(dest, 1) == -1) then
189
+ raise("KNX client: error setting socket mode")
190
+ end
191
+ send_conn.EIBSendAPDU(apdu)
192
+ send_conn.EIBReset()
193
+ }
194
+ end
195
+
196
+ # (Try to) read a groupaddr from eibd cache.
197
+ #
198
+ # return it if found, otherwise query the bus. In the latter case,
199
+ # the main receiver thread (in run()) will act on the response.
200
+ #
201
+ # * Arguments:
202
+ # [ga] Fixnum: group address (0-65535)
203
+ # [cache_only] boolean: when true, do not query the bus
204
+ def read_eibd_cache(ga, cache_only=false)
205
+ src = EIBAddr.new()
206
+ buf = EIBBuffer.new()
207
+ result = nil
208
+ @send_mutex.synchronize {
209
+ # query eibd for a cached value
210
+ if (send_conn.EIB_Cache_Read_Sync(ga, src, buf, 0) == -1) then
211
+ # value not found in cache
212
+ puts "groupaddress #{addr2str(ga, true)} not found in cache."
213
+ unless cache_only then
214
+ puts ".. requesting value on bus .."
215
+ if (send_conn.EIBOpenT_Group(ga, 1) == -1) then
216
+ raise("KNX client: error setting socket mode")
217
+ end
218
+ # send a read request to the bus
219
+ send_conn.EIBSendAPDU([0,0x00])
220
+ end
221
+ send_conn.EIBReset()
222
+ else
223
+ send_conn.EIBReset()
224
+ # value found in cache..
225
+ puts "found in cache, last sender was #{addr2str(src.data)}"
226
+ result = buf.buffer
227
+ end
228
+ }
229
+ return result
230
+ end
231
+
232
+ end #class
233
+
234
+ end #module KNX
235
+
236
+ end #module Ansible
237
+
@@ -0,0 +1,327 @@
1
+ =begin
2
+ Project Ansible - An extensible home automation scripting framework
3
+ ----------------------------------------------------
4
+ Copyright (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
5
+
6
+ SOFTWARE NOTICE AND LICENSE
7
+
8
+ Project Ansible is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU Lesser General Public License as published
10
+ by the Free Software Foundation, either version 3 of the License,
11
+ or (at your option) any later version.
12
+
13
+ Project Ansible is distributed in the hope that it will be useful,
14
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ GNU Lesser General Public License for more details.
17
+
18
+ You should have received a copy of the GNU Lesser General Public License
19
+ along with Project Ansible. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ for more information on the LGPL, see:
22
+ http://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License
23
+ =end
24
+
25
+ require 'ansible_value'
26
+
27
+ require 'knx_transceiver'
28
+ require 'knx_protocol'
29
+ require 'knx_tools'
30
+ require 'knx_dpt'
31
+
32
+ module Ansible
33
+
34
+ module KNX
35
+
36
+ # a KNXValue is a device-dependant datapoint. It is initialized by a
37
+ # DPT type name (e.g. "1.001" for binary switch) and is extended
38
+ # by the initializer with the corresponding DPT module (e.g. KNX::DPT1)
39
+ # so as to handle DPT1 frames.
40
+ # Each KNXValue is linked to zero or more group addresses, the first
41
+ # of which will be the "update" value
42
+ class KNXValue
43
+
44
+ include AnsibleValue
45
+
46
+ #
47
+ # ------ CLASS VARIABLES & METHODS
48
+ #
49
+ @@ids = 0
50
+ def KNXValue.id_generator
51
+ @@ids = @@ids + 1
52
+ return @@ids
53
+ end
54
+
55
+ # a Hash containing all known group addresses
56
+ @@AllGroups = {}
57
+
58
+ # the transceiver responsible for all things KNX
59
+ @@transceiver = nil
60
+ def KNXValue.transceiver; return @@transceiver; end
61
+ def KNXValue.transceiver=(other);
62
+ @@transceiver = other if other.is_a? Ansible::KNX::KNX_Transceiver
63
+ end
64
+
65
+ #
66
+ # ----- INSTANCE VARIABLES & METHODS
67
+ #
68
+
69
+ # equality checking
70
+ def == (other)
71
+ return (other.is_a?(KNXValue) and (@id == other.id))
72
+ end
73
+
74
+ # set flag: knxvalue.flags[:r] = true
75
+ # get flag: knxvalue.flags[:r] (evaluates to true, meaning the read flag is set)
76
+ attr_reader :flags
77
+ attr_reader :groups
78
+ attr_reader :dpt_basetype, :dpt_subtype, :id
79
+ attr_accessor :description
80
+
81
+ # initialize a KNXValue
82
+ # ===Arguments:
83
+ # [dpt]
84
+ # string representing the DPT (datapoint type) of the value
85
+ # e.g. "5.001" meaning DPT5 percentage value (8-bit unsigned)
86
+ # [groups]
87
+ # array of group addresses associated with this datapoint
88
+ # [flags]
89
+ # hash of symbol=>boolean flags regarding its behaviour
90
+ # e.g. {:r => true} the value can only respond to read requests on the KNX bus.
91
+ # default flags: READ and WRITE
92
+ # c => Communication
93
+ # r => Read
94
+ # w => Write
95
+ # t => Transmit
96
+ # u => Update
97
+ # i => read on Init
98
+ def initialize(dpt, groups=[], flags=nil)
99
+
100
+ # init DPT info
101
+ if md = /(\d*)\.(\d*)/.match(dpt) then
102
+ @dpt = dpt
103
+ @dpt_mod = Ansible::KNX.module_eval("DPT#{md[1]}")
104
+ raise "unknown/undeclared DPT module #{dpt}" unless @dpt_mod.is_a?Module
105
+ @parserclass = @dpt_mod.module_eval("DPT#{md[1]}_Frame")
106
+ raise "unknown/undeclared parser for DPT #{dpt}" unless @parserclass.ancestors.include?(DPTFrame)
107
+ @dpt_basetype = @dpt_mod::Basetype
108
+ raise "missing Basetype info for #{dpt}" unless @dpt_basetype.is_a?Hash
109
+ @dpt_subtype = @dpt_mod::Subtypes[md[2]]
110
+ raise "missing sybtype info for #{dpt}" unless @dpt_subtype.is_a?Hash
111
+ # extend this object with DPT-specific module
112
+ self.extend(@dpt_mod)
113
+ # print out some useful debug info
114
+ puts " dpt_basetype = #{@dpt_basetype}, dpt_subtype = #{@dpt_subtype}" if $DEBUG
115
+ else
116
+ raise "invalid datapoint type (#{dpt})"
117
+ end
118
+
119
+ # array of GroupAddress objects associated with this datapoint
120
+ # only the first address is used in a write operation (TODO: CHECKME)
121
+ @groups = case groups
122
+ when Fixnum then Array[group]
123
+ when String then Array[str2addr(groups)]
124
+ when Array then groups
125
+ end
126
+
127
+ # store DPT info about these group addresses
128
+ @groups.each { |grp|
129
+ # sanity check: is this groupaddr already decaled as a different basetype?
130
+ # FIXME: specs dont forbid it, only check required is datalength compatibility
131
+ if @@AllGroups[grp] and (old_dpt = @@AllGroups[grp][:dpt_basetype]) and not (old_dpt.eql?(@dpt_basetype))
132
+ raise "Group address #{addr2str(grp)} is already declared as DPT basetype #{old_dpt}!"
133
+ end
134
+ puts "adding groupaddr #{addr2str(grp,true) } (#{@dpt}: #{@dpt_subtype[:name]}), to global hash"
135
+ @@AllGroups[grp] = {:basetype => @dpt_basetype, :subtype => @dpt_subtype}
136
+ }
137
+
138
+ if flags.nil?
139
+ # default flags: READ and WRITE
140
+ @flags = {:r => true,:w => true}
141
+ else
142
+ raise "flags parameter must be a Hash!" unless flags.is_a?Hash
143
+ @flags = flags
144
+ end
145
+
146
+ # TODO: physical address: set only for remote nodes we are monitoring
147
+ # when left to nil, it/ means a datapoint on this KNXTransceiver
148
+ @physaddr = nil
149
+
150
+ # id of datapoint
151
+ # initialized by class method KNXValue.id_generator
152
+ @id = KNXValue.id_generator()
153
+
154
+ @description = ''
155
+
156
+ # store this KNXValue in the Ansible database
157
+ AnsibleValue.insert(self)
158
+ end
159
+
160
+ # is this KNX datapoint read only?
161
+ def read_only?
162
+ return((defined? @flags) and (@flags[:r] and not @flags[:w]))
163
+ end
164
+
165
+ # is this KNX datapoint write only?
166
+ def write_only?
167
+ return((defined? @flags) and (@flags[:w] and not @flags[:r]))
168
+ end
169
+
170
+ # read value from eibd's group cache, or issue a read request,
171
+ # hoping that someone will respond with the last known status
172
+ # for this value
173
+ def read_value()
174
+ if (not @groups.nil?) and (group = @groups[0]) then
175
+ if (data = @@transceiver.read_eibd_cache(group)) then
176
+ fire_callback(:onReadCacheHit)
177
+ # update the value
178
+ update(@parserclass.read(data.pack('c*')))
179
+ else
180
+ # value not found in cache, maybe some other
181
+ # device on the bus will respond...
182
+ fire_callback(:onReadCacheMiss)
183
+ nil
184
+ end
185
+ else
186
+ return false
187
+ end
188
+ end
189
+
190
+ #
191
+ # write value to the bus
192
+ # argument: new_val according to DPT
193
+ # if :basic, then new_val is plain data value
194
+ # else (:composite) new_val is a hash of
195
+ # field_name => field_value pairs
196
+ # all values get mapped using to_protocol_value() in AnsibleValue::update()
197
+ # return true if successful, false otherwise
198
+ def write_value(new_val)
199
+ #write value to primary group address
200
+ dest, telegram = nil, nil
201
+ if @groups.length > 0 then
202
+ dest = @groups[0]
203
+ else
204
+ raise "#{self}: primary group address not set!!!"
205
+ end
206
+ case @dpt_mod::Basetype[:valuetype]
207
+ when :basic then
208
+ # basic value: single 'data' field
209
+ telegram = @parserclass.new(:data => new_val)
210
+ when :composite then
211
+ if new_val.is_a?Hash then
212
+ telegram = @parserclass.new(new_val)
213
+ else
214
+ if @parserclass.methods.include?(:assign) then
215
+ puts "FIXME"
216
+ else
217
+ raise "#{self} has a composite DPT, set() expects a hash!"
218
+ end
219
+ end
220
+ end
221
+ #
222
+ puts "#{self}: Writing new value (#{new_val}) to #{addr2str(dest, true)}"
223
+ #
224
+ if (@@transceiver.send_apdu_raw(dest, to_apdu(telegram, 0x80)) > -1) then
225
+ update(telegram)
226
+ return(true)
227
+ else
228
+ return(false)
229
+ end
230
+ end
231
+
232
+ # set primary group address
233
+ def group_primary=(grpaddr)
234
+ @groups.unshift(grpaddr)
235
+ end
236
+
237
+ # assign a new array of group addresses for this datapoint
238
+ def groups=(other)
239
+ raise "KNXValue.groups= requires an array of at least one group addresses" unless (other.is_a?Array) and (other.length > 0)
240
+ @groups.replace(other)
241
+ end
242
+
243
+ # make sure all frame fields are valid (within min,max range)
244
+ # see Ansible::AnsibleValue.update
245
+ def validate_ranges
246
+ if defined? @current_value then
247
+ @current_value.validate_ranges
248
+ end
249
+ end
250
+
251
+ # create apdu for this KNXValue
252
+ # APDU types are:
253
+ # 0x00 => Read
254
+ # 0x40 => Response (default)
255
+ # 0x80 => Write
256
+ def to_apdu(frame, apci_code = 0x40)
257
+ apdu = if @dpt_mod::Basetype[:bitlength] <= 6 then
258
+ #[0, apci_code | @current_value]
259
+ [0, apci_code | frame.data]
260
+ else
261
+ #[0, apci_code] + @current_value.to_a
262
+ [0, apci_code] + frame.to_binary_s.unpack('C*')
263
+ end
264
+ return apdu
265
+ end
266
+
267
+ # update internal state from raw KNX frame
268
+ def update_from_frame(rawframe)
269
+ data = if @dpt_mod::Basetype[:bitlength] <= 6 then
270
+ # bindata always expects a binary string
271
+ [rawframe.apci_data].pack('c')
272
+ else
273
+ rawframe.data
274
+ end
275
+ foo = @parserclass.read(data)
276
+ puts "update_from_frame: #{foo.class} = #{foo.inspect}"
277
+ update(foo)
278
+ end
279
+
280
+ # human-readable representation of the value. Uses all field
281
+ # info from its DPT included module, if available.
282
+ def to_s
283
+ dpt_name = (@dpt_subtype.nil?) ? '' : @dpt_subtype[:name]
284
+ dpt_info = "KNXValue[#{@dpt} #{dpt_name}]"
285
+ # add field values explanation, if any
286
+ vstr = (defined?(@current_value) ? explain(@current_value) : '(value undefined)')
287
+ # return @dpt: values.explained
288
+ gaddrs = @groups.collect{|ga| addr2str(ga, true)}.join(', ')
289
+ return [@description, gaddrs, dpt_info].compact.join(' ') + " : #{vstr}"
290
+ end
291
+
292
+ # return a human-readable representation of a DPT frame
293
+ def explain(frame)
294
+ raise "explain() expects a DPTFrame, got a #{frame.class}" unless frame.is_a?DPTFrame
295
+ fielddata = []
296
+ # iterate over all available DPT fields
297
+ frame.field_names.each { |fieldname|
298
+ # skip padding fields
299
+ next if /pad/.match(fieldname)
300
+ field = frame.send(fieldname)
301
+ fval = field.value
302
+ # get value encoding hashmap, if any
303
+ vhash = getparam(:enc, field)
304
+ # get value units
305
+ units = getparam(:unit, field)
306
+ fval = as_canonical_value()
307
+ # add field value, according to encoding hashtable
308
+ fielddata << "#{(vhash.is_a?Hash) ? vhash[fval] : fval} #{units}"
309
+ }
310
+ return fielddata.join(', ')
311
+ end
312
+
313
+ # get a DPT parameter, trying to locate it in the following order:
314
+ # 1) in the DPTFrame field definition
315
+ # 2) in the DPT subtype definition
316
+ # 3) in the DPT basetype definition
317
+ def getparam(param, field=nil)
318
+ return ((field and field.get_parameter(param)) or
319
+ (@dpt_subtype and @dpt_subtype[param]) or
320
+ (@dpt_basetype and @dpt_basetype[param]))
321
+ end
322
+
323
+ end #class KNXValue
324
+
325
+ end #module KNX
326
+
327
+ end #module Ansible