Class: Ansible::KNX::KNXValue

Inherits:
Object
  • Object
show all
Includes:
AnsibleValue
Defined in:
lib/ansible/knx/knx_value.rb

Overview

a KNXValue is a device-dependant datapoint. It is initialized by a

DPT type name (e.g. “1.001” for binary switch) and is extended by the initializer with the corresponding DPT module (e.g. KNX::DPT1) so as to handle DPT1 frames. Each KNXValue is linked to zero or more group addresses, the first of which will be the “update” value

Constant Summary collapse

@@ids =

—— CLASS VARIABLES & METHODS

0
@@AllGroups =

a Hash containing all known group addresses

{}
@@transceiver =

the transceiver responsible for all things KNX

nil

Instance Attribute Summary collapse

Attributes included from AnsibleValue

#current_value, #last_update, #previous_value

Class Method Summary collapse

Instance Method Summary collapse

Methods included from AnsibleValue

[], #as_canonical_value, #get, insert, #matches?, #set, #to_protocol_value, #update

Methods included from AnsibleCallback

#add_callback, #fire_callback, #remove_callback

Constructor Details

#initialize(dpt, groups = [], flags = nil) ⇒ KNXValue

initialize a KNXValue

Arguments:

dpt

string representing the DPT (datapoint type) of the value e.g. “5.001” meaning DPT5 percentage value (8-bit unsigned)

groups

array of group addresses associated with this datapoint

flags

hash of symbol=>boolean flags regarding its behaviour e.g. => true the value can only respond to read requests on the KNX bus. default flags: READ and WRITE

c => Communication
r => Read
w => Write
t => Transmit
u => Update
i => read on Init


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/ansible/knx/knx_value.rb', line 98

def initialize(dpt, groups=[], flags=nil)
    
    # init DPT info
    if md = /(\d*)\.(\d*)/.match(dpt) then
        @dpt = dpt
        @dpt_mod = Ansible::KNX.module_eval("DPT#{md[1]}")
        raise "unknown/undeclared DPT module #{dpt}" unless @dpt_mod.is_a?Module
        @parserclass = @dpt_mod.module_eval("DPT#{md[1]}_Frame")
        raise "unknown/undeclared parser for DPT #{dpt}" unless @parserclass.ancestors.include?(DPTFrame)
        @dpt_basetype = @dpt_mod::Basetype
        raise "missing Basetype info for #{dpt}" unless @dpt_basetype.is_a?Hash
        @dpt_subtype = @dpt_mod::Subtypes[md[2]]
        raise "missing sybtype info for #{dpt}" unless @dpt_subtype.is_a?Hash
        # extend this object with DPT-specific module 
        self.extend(@dpt_mod)
        # print out some useful debug info                    
        puts "  dpt_basetype = #{@dpt_basetype},  dpt_subtype = #{@dpt_subtype}" if $DEBUG
    else
        raise "invalid datapoint type (#{dpt})"
    end
    
    # array of GroupAddress objects associated with this datapoint
    # only the first address is used in a  write operation (TODO: CHECKME)
    @groups = case groups
        when Fixnum then Array[group]
        when String then Array[str2addr(groups)] 
        when Array then groups
    end
    
    # store DPT info about these group addresses
    @groups.each { |grp|
        # sanity check: is this groupaddr already decaled as a different basetype?
        # FIXME: specs dont forbid it, only check required is datalength compatibility
        if @@AllGroups[grp] and (old_dpt = @@AllGroups[grp][:dpt_basetype]) and not (old_dpt.eql?(@dpt_basetype))                           
            raise "Group address #{addr2str(grp)} is already declared as DPT basetype #{old_dpt}!"
        end
        puts "adding groupaddr #{addr2str(grp,true) } (#{@dpt}: #{@dpt_subtype[:name]}), to global hash"
        @@AllGroups[grp] = {:basetype => @dpt_basetype, :subtype => @dpt_subtype}
    }
    
    if flags.nil?
        # default flags: READ and WRITE
        @flags = {:r => true,:w => true}
    else
        raise "flags parameter must be a Hash!" unless flags.is_a?Hash
        @flags = flags
    end

    # TODO: physical address: set only for remote nodes we are monitoring
    # when left to nil, it/ means a datapoint on this KNXTransceiver 
    @physaddr = nil
    
    # id of datapoint
    # initialized by class method KNXValue.id_generator
    @id = KNXValue.id_generator()
    
    @description = ''
    
    # store this KNXValue in the Ansible database
    AnsibleValue.insert(self)
end

Instance Attribute Details

#descriptionObject

Returns the value of attribute description.



79
80
81
# File 'lib/ansible/knx/knx_value.rb', line 79

def description
  @description
end

#dpt_basetypeObject (readonly)

Returns the value of attribute dpt_basetype.



78
79
80
# File 'lib/ansible/knx/knx_value.rb', line 78

def dpt_basetype
  @dpt_basetype
end

#dpt_subtypeObject (readonly)

Returns the value of attribute dpt_subtype.



78
79
80
# File 'lib/ansible/knx/knx_value.rb', line 78

def dpt_subtype
  @dpt_subtype
end

#flagsObject (readonly)

set flag: knxvalue.flags = true get flag: knxvalue.flags (evaluates to true, meaning the read flag is set)



76
77
78
# File 'lib/ansible/knx/knx_value.rb', line 76

def flags
  @flags
end

#groupsObject

Returns the value of attribute groups.



77
78
79
# File 'lib/ansible/knx/knx_value.rb', line 77

def groups
  @groups
end

#idObject (readonly)

Returns the value of attribute id.



78
79
80
# File 'lib/ansible/knx/knx_value.rb', line 78

def id
  @id
end

Class Method Details

.id_generatorObject



50
51
52
53
# File 'lib/ansible/knx/knx_value.rb', line 50

def KNXValue.id_generator
    @@ids = @@ids + 1
    return @@ids
end

.transceiverObject



60
# File 'lib/ansible/knx/knx_value.rb', line 60

def KNXValue.transceiver; return @@transceiver; end

.transceiver=(other) ⇒ Object



61
62
63
# File 'lib/ansible/knx/knx_value.rb', line 61

def KNXValue.transceiver=(other); 
    @@transceiver = other if other.is_a? Ansible::KNX::KNX_Transceiver
end

Instance Method Details

#==(other) ⇒ Object

equality checking



70
71
72
# File 'lib/ansible/knx/knx_value.rb', line 70

def == (other)
    return (other.is_a?(KNXValue) and (@id == other.id))
end

#explain(frame) ⇒ Object

return a human-readable representation of a DPT frame



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/ansible/knx/knx_value.rb', line 293

def explain(frame)
    raise "explain() expects a DPTFrame, got a #{frame.class}" unless frame.is_a?DPTFrame
    fielddata = []
    # iterate over all available DPT fields
    frame.field_names.each { |fieldname|
        # skip padding fields
        next if /pad/.match(fieldname)
        field = frame.send(fieldname)
        fval = field.value
        # get value encoding hashmap, if any
        vhash = getparam(:enc, field)
        # get value units
        units = getparam(:unit, field) 
        fval = as_canonical_value()
        # add field value, according to encoding hashtable
        fielddata << "#{(vhash.is_a?Hash) ? vhash[fval] : fval} #{units}"
    } 
    return fielddata.join(', ')
end

#getparam(param, field = nil) ⇒ Object

get a DPT parameter, trying to locate it in the following order:

1) in the DPTFrame field definition 
2) in the DPT subtype definition
3) in the DPT basetype definition


317
318
319
320
321
# File 'lib/ansible/knx/knx_value.rb', line 317

def getparam(param, field=nil)
    return ((field and field.get_parameter(param)) or 
            (@dpt_subtype and @dpt_subtype[param]) or 
            (@dpt_basetype and @dpt_basetype[param]))
end

#group_primary=(grpaddr) ⇒ Object

set primary group address



233
234
235
# File 'lib/ansible/knx/knx_value.rb', line 233

def group_primary=(grpaddr)
    @groups.unshift(grpaddr)
end

#read_only?Boolean

is this KNX datapoint read only?

Returns:

  • (Boolean)


161
162
163
# File 'lib/ansible/knx/knx_value.rb', line 161

def read_only?
    return((defined? @flags) and (@flags[:r] and not @flags[:w]))
end

#read_valueObject

read value from eibd’s group cache, or issue a read request, hoping that someone will respond with the last known status for this value



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ansible/knx/knx_value.rb', line 173

def read_value()
    if (not @groups.nil?) and (group = @groups[0]) then
        if (data = @@transceiver.read_eibd_cache(group)) then
            fire_callback(:onReadCacheHit)
            # update the value
            update(@parserclass.read(data.pack('c*')))
        else
            # value not found in cache, maybe some other
            # device on the bus will respond...
            fire_callback(:onReadCacheMiss)
            nil
        end
    else
        return false
    end
end

#to_apdu(frame, apci_code = 0x40) ⇒ Object

create apdu for this KNXValue APDU types are:

0x00 => Read
0x40 => Response (default)
0x80 => Write


256
257
258
259
260
261
262
263
264
265
# File 'lib/ansible/knx/knx_value.rb', line 256

def to_apdu(frame, apci_code = 0x40)
    apdu = if @dpt_mod::Basetype[:bitlength] <= 6 then
        #[0, apci_code | @current_value]
        [0, apci_code | frame.data]
    else
        #[0, apci_code] + @current_value.to_a
        [0, apci_code] + frame.to_binary_s.unpack('C*') 
    end
    return apdu
end

#to_sObject

human-readable representation of the value. Uses all field info from its DPT included module, if available.



282
283
284
285
286
287
288
289
290
# File 'lib/ansible/knx/knx_value.rb', line 282

def to_s
    dpt_name = (@dpt_subtype.nil?) ? '' : @dpt_subtype[:name] 
    dpt_info = "KNXValue[#{@dpt} #{dpt_name}]"
    # add field values explanation, if any
    vstr = (defined?(@current_value) ? explain(@current_value) : '(value undefined)')
    # return @dpt: values.explained
    gaddrs = @groups.collect{|ga| addr2str(ga, true)}.join(', ')
    return [@description, gaddrs, dpt_info].compact.join(' ') + " : #{vstr}" 
end

#update_from_frame(rawframe) ⇒ Object

update internal state from raw KNX frame



268
269
270
271
272
273
274
275
276
277
278
# File 'lib/ansible/knx/knx_value.rb', line 268

def update_from_frame(rawframe)
    data = if @dpt_mod::Basetype[:bitlength] <= 6 then
        # bindata always expects a binary string
        [rawframe.apci_data].pack('c')
    else
        rawframe.data
    end
    foo = @parserclass.read(data)
    puts "update_from_frame: #{foo.class} = #{foo.inspect}"
    update(foo)
end

#validate_rangesObject

make sure all frame fields are valid (within min,max range) see Ansible::AnsibleValue.update



245
246
247
248
249
# File 'lib/ansible/knx/knx_value.rb', line 245

def validate_ranges
    if defined? @current_value then  
        @current_value.validate_ranges
    end
end

#write_only?Boolean

is this KNX datapoint write only?

Returns:

  • (Boolean)


166
167
168
# File 'lib/ansible/knx/knx_value.rb', line 166

def write_only?
    return((defined? @flags) and (@flags[:w] and not @flags[:r]))
end

#write_value(new_val) ⇒ Object

write value to the bus argument: new_val according to DPT

if :basic, then new_val is plain data value   
else (:composite) new_val is a hash of 
    field_name => field_value pairs
all values get mapped using to_protocol_value() in AnsibleValue::update()

return true if successful, false otherwise



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/ansible/knx/knx_value.rb', line 198

def write_value(new_val)
    #write value to primary group address
    dest, telegram = nil, nil
    if @groups.length > 0 then 
        dest = @groups[0]
    else
        raise "#{self}: primary group address not set!!!"
    end 
    case @dpt_mod::Basetype[:valuetype]
    when :basic then
        # basic value: single 'data' field
        telegram = @parserclass.new(:data => new_val)
    when :composite then
        if new_val.is_a?Hash then
            telegram = @parserclass.new(new_val)
        else
            if @parserclass.methods.include?(:assign) then
                puts "FIXME"
            else
                raise "#{self} has a composite DPT, set() expects a hash!"
            end
        end
    end
    #
    puts "#{self}: Writing new value (#{new_val}) to #{addr2str(dest, true)}"
    #
    if (@@transceiver.send_apdu_raw(dest, to_apdu(telegram, 0x80)) > -1) then
        update(telegram)
        return(true)
    else
        return(false)
    end
end