Class: MQTT::Homie::Property

Inherits:
Base
  • Object
show all
Defined in:
lib/mqtt/homie/property.rb

Constant Summary

Constants inherited from Base

Base::REGEX

Instance Attribute Summary collapse

Attributes inherited from Base

#id, #name

Instance Method Summary collapse

Constructor Details

#initialize(node, id, name, datatype, value = nil, format: nil, retained: true, unit: nil, &block) ⇒ Property

Returns a new instance of Property.

Raises:

  • (ArgumentError)


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/mqtt/homie/property.rb', line 8

def initialize(node, id, name, datatype, value = nil, format: nil, retained: true, unit: nil, &block)
  raise ArgumentError, "Invalid Homie datatype" unless %i[string integer float boolean enum color datetime
                                                          duration].include?(datatype)
  raise ArgumentError, "retained must be boolean" unless [true, false].include?(retained)

  format = format.join(",") if format.is_a?(Array) && datatype == :enum
  if %i[integer float].include?(datatype) && format.is_a?(Range)
    raise ArgumentError "only inclusive ranges are supported" if format.exclude_end?

    format = "#{format.begin}:#{format.end}"
  end
  raise ArgumentError, "format must be nil or a string" unless format.nil? || format.is_a?(String)
  raise ArgumentError, "unit must be nil or a string" unless unit.nil? || unit.is_a?(String)
  raise ArgumentError, "format is required for enums" if datatype == :enum && format.nil?
  raise ArgumentError, "format is required for colors" if datatype == :color && format.nil?
  if datatype == :color && !%w[rgb hsv].include?(format.to_s)
    raise ArgumentError, "format must be either rgb or hsv for colors"
  end
  if !value.nil? && !retained
    raise ArgumentError, "an initial value cannot be provided for a non-retained property"
  end

  super(id, name)

  @node = node
  @datatype = datatype
  @format = format
  @retained = retained
  @unit = unit
  @value = value
  @published = false
  @block = block
end

Instance Attribute Details

#datatypeObject (readonly)

Returns the value of attribute datatype.



6
7
8
# File 'lib/mqtt/homie/property.rb', line 6

def datatype
  @datatype
end

#formatObject

Returns the value of attribute format.



6
7
8
# File 'lib/mqtt/homie/property.rb', line 6

def format
  @format
end

#nodeObject (readonly)

Returns the value of attribute node.



6
7
8
# File 'lib/mqtt/homie/property.rb', line 6

def node
  @node
end

#unitObject

Returns the value of attribute unit.



6
7
8
# File 'lib/mqtt/homie/property.rb', line 6

def unit
  @unit
end

#valueObject

Returns the value of attribute value.



6
7
8
# File 'lib/mqtt/homie/property.rb', line 6

def value
  @value
end

Instance Method Details

#deviceObject



60
61
62
# File 'lib/mqtt/homie/property.rb', line 60

def device
  node.device
end

#full_nameObject



56
57
58
# File 'lib/mqtt/homie/property.rb', line 56

def full_name
  "#{node.full_name} #{name}"
end

#inspectObject



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/mqtt/homie/property.rb', line 42

def inspect
  result = +"#<MQTT::Homie::Property #{topic} name=#{full_name.inspect}, datatype=#{datatype.inspect}"
  result << ", format=#{format.inspect}" if format
  result << ", unit=#{unit.inspect}" if unit
  result << ", settable=true" if settable?
  result << if retained?
              ", value=#{value.inspect}"
            else
              ", retained=false"
            end
  result << ">"
  result.freeze
end

#mqttObject



161
162
163
# File 'lib/mqtt/homie/property.rb', line 161

def mqtt
  node.mqtt
end

#publishObject



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/mqtt/homie/property.rb', line 165

def publish
  return if @published

  mqtt.batch_publish do
    mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
    mqtt.publish("#{topic}/$datatype", datatype.to_s, retain: true, qos: 1)
    mqtt.publish("#{topic}/$format", format, retain: true, qos: 1) if format
    mqtt.publish("#{topic}/$settable", "true", retain: true, qos: 1) if settable?
    mqtt.publish("#{topic}/$retained", "false", retain: true, qos: 1) unless retained?
    mqtt.publish("#{topic}/$unit", unit, retain: true, qos: 1) if unit
    publish_value unless value.nil?
    subscribe
  end

  @published = true
end

#rangeObject



105
106
107
108
109
110
111
112
113
114
# File 'lib/mqtt/homie/property.rb', line 105

def range
  return nil unless format

  case datatype
  when :enum then format.split(",")
  when :integer then Range.new(*format.split(":").map(&:to_i))
  when :float then Range.new(*format.split(":").map(&:to_f))
  else; raise MethodNotImplemented
  end
end

#retained?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/mqtt/homie/property.rb', line 68

def retained?
  @retained
end

#set(value) ⇒ Object



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
159
# File 'lib/mqtt/homie/property.rb', line 116

def set(value)
  case datatype
  when :boolean
    return unless %w[true false].include?(value)

    value = value == "true"
  when :integer
    return unless /^-?\d+$/.match?(value)

    value = value.to_i
    return if format && !range.include?(value)
  when :float
    return unless /^-?(?:\d+|\d+\.|\.\d+|\d+\.\d+)(?:[eE]-?\d+)?$/.match?(value)

    value = value.to_f
    return if format && !range.include?(value)
  when :enum
    return unless range.include?(value)
  when :color
    return unless /^\d{1,3},\d{1,3},\d{1,3}$/.match?(value)

    value = value.split(",").map(&:to_i)
    case format
    when "rgb"
      return if value.max > 255
    when "hsv"
      return if value.first > 360 || value[1..2].max > 100
    end
  when :datetime
    begin
      value = Time.parse(value)
    rescue ArgumentError
      return
    end
  when :duration
    begin
      value = ActiveSupport::Duration.parse(value)
    rescue ActiveSupport::Duration::ISO8601Parser::ParsingError
      return
    end
  end

  @block.arity == 2 ? @block.call(value, self) : @block.call(value)
end

#settable?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/mqtt/homie/property.rb', line 72

def settable?
  !!@block
end

#subscribeObject



182
183
184
# File 'lib/mqtt/homie/property.rb', line 182

def subscribe
  mqtt.subscribe("#{topic}/set") if settable?
end

#topicObject



64
65
66
# File 'lib/mqtt/homie/property.rb', line 64

def topic
  "#{node.topic}/#{id}"
end

#unpublishObject



186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/mqtt/homie/property.rb', line 186

def unpublish
  return unless @published

  @published = false

  mqtt.publish("#{topic}/$name", retain: true, qos: 0)
  mqtt.publish("#{topic}/$datatype", retain: true, qos: 0)
  mqtt.publish("#{topic}/$format", retain: true, qos: 0) if format
  mqtt.publish("#{topic}/$settable", retain: true, qos: 0) if settable?
  mqtt.publish("#{topic}/$retained", retain: true, qos: 0) unless retained?
  mqtt.publish("#{topic}/$unit", retain: true, qos: 0) if unit
  mqtt.unsubscribe("#{topic}/set") if settable?
  mqtt.publish(topic, retain: retained?, qos: 0) if !value.nil? && retained?
end