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


96
97
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
# File 'lib/ansible/knx/knx_value.rb', line 96

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.



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

def description
  @description
end

#dpt_basetypeObject (readonly)

Returns the value of attribute dpt_basetype.



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

def dpt_basetype
  @dpt_basetype
end

#dpt_subtypeObject (readonly)

Returns the value of attribute dpt_subtype.



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

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)



74
75
76
# File 'lib/ansible/knx/knx_value.rb', line 74

def flags
  @flags
end

#groupsObject

Returns the value of attribute groups.



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

def groups
  @groups
end

#idObject (readonly)

Returns the value of attribute id.



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

def id
  @id
end

Class Method Details

.id_generatorObject



48
49
50
51
# File 'lib/ansible/knx/knx_value.rb', line 48

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

.transceiverObject



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

def KNXValue.transceiver; return @@transceiver; end

.transceiver=(other) ⇒ Object



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

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

Instance Method Details

#==(other) ⇒ Object

equality checking



68
69
70
# File 'lib/ansible/knx/knx_value.rb', line 68

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

#explain(frame) ⇒ Object

return a human-readable representation of a DPT frame



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

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


315
316
317
318
319
# File 'lib/ansible/knx/knx_value.rb', line 315

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



231
232
233
# File 'lib/ansible/knx/knx_value.rb', line 231

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

#read_only?Boolean

is this KNX datapoint read only?

Returns:

  • (Boolean)


159
160
161
# File 'lib/ansible/knx/knx_value.rb', line 159

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



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

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


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

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.



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

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



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

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



243
244
245
246
247
# File 'lib/ansible/knx/knx_value.rb', line 243

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)


164
165
166
# File 'lib/ansible/knx/knx_value.rb', line 164

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



196
197
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
# File 'lib/ansible/knx/knx_value.rb', line 196

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