class MQTT::Homie::Property

Attributes

datatype[R]
format[R]
node[R]
unit[R]
value[R]

Public Class Methods

new(node, id, name, datatype, value = nil, format: nil, retained: true, unit: nil, &block) click to toggle source
Calls superclass method MQTT::Homie::Base::new
# 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?
  raise ArgumentError, "format must be either rgb or hsv for colors" if datatype == :color && !%w{rgb hsv}.include?(format.to_s)
  raise ArgumentError, "an initial value cannot be provided for a non-retained property" if !value.nil? && !retained

  super(id, name)

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

Public Instance Methods

device() click to toggle source
# File lib/mqtt/homie/property.rb, line 35
def device
  node.device
end
format=(format) click to toggle source
# File lib/mqtt/homie/property.rb, line 69
def format=(format)
  if format != @format
    @format = format
    if @published
      device.init do
        mqtt.publish("#{topic}/$format", format.to_s, retain: true, qos: 1)
      end
    end
  end
end
mqtt() click to toggle source
# File lib/mqtt/homie/property.rb, line 129
def mqtt
  node.mqtt
end
publish() click to toggle source
# File lib/mqtt/homie/property.rb, line 133
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
range() click to toggle source
# File lib/mqtt/homie/property.rb, line 80
def range
  case datatype
  when :enum; format.split(',')
  when :integer; Range.new(*format.split(':').map(&:to_i))
  when :float; Range.new(*format.split(':').map(&:to_f))
  else; raise MethodNotImplemented
  end
end
retained?() click to toggle source
# File lib/mqtt/homie/property.rb, line 43
def retained?
  @retained
end
set(value) click to toggle source
# File lib/mqtt/homie/property.rb, line 89
def set(value)
  case datatype
  when :boolean
    return unless %w{true false}.include?(value)
    value = value == 'true'
  when :integer
    return unless value =~ /^-?\d+$/
    value = value.to_i
    return unless range.include?(value) if format
  when :float
    return unless value =~ /^-?(?:\d+|\d+\.|\.\d+|\d+\.\d+)(?:[eE]-?\d+)?$/
    value = value.to_f
    return unless range.include?(value) if format
  when :enum
    return unless range.include?(value)
  when :color
    return unless value =~ /^\d{1,3},\d{1,3},\d{1,3}$/
    value = value.split(',').map(&:to_i)
    if format == 'rgb'
      return if value.max > 255
    elsif format == '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?() click to toggle source
# File lib/mqtt/homie/property.rb, line 47
def settable?
  !!@block
end
subscribe() click to toggle source
# File lib/mqtt/homie/property.rb, line 150
def subscribe
  mqtt.subscribe("#{topic}/set") if settable?
end
topic() click to toggle source
# File lib/mqtt/homie/property.rb, line 39
def topic
  "#{node.topic}/#{id}"
end
unit=(unit) click to toggle source
# File lib/mqtt/homie/property.rb, line 58
def unit=(unit)
  if unit != @unit
    @unit = unit
    if @published
      device.init do
        mqtt.publish("#{topic}/$unit", unit.to_s, retain: true, qos: 1)
      end
    end
  end
end
unpublish() click to toggle source
# File lib/mqtt/homie/property.rb, line 154
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
value=(value) click to toggle source
# File lib/mqtt/homie/property.rb, line 51
def value=(value)
  if @value != value
    @value = value if retained?
    publish_value if @published
  end
end

Private Instance Methods

publish_value() click to toggle source
# File lib/mqtt/homie/property.rb, line 170
def publish_value
  serialized = value
  serialized = serialized&.iso8601 if %i[datetime duration].include?(datatype)
  serialized = serialized.to_s

  node.device.logger&.debug("publishing #{serialized.inspect} to #{topic}")
  mqtt.publish(topic, serialized, retain: retained?, qos: 1)
end