ansible4ozw 0.0.1

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.
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