homie-mqtt 1.4.5 → 1.5.0
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.
- checksums.yaml +4 -4
- data/lib/homie-mqtt.rb +5 -5
- data/lib/mqtt/homie/base.rb +8 -7
- data/lib/mqtt/homie/device.rb +25 -15
- data/lib/mqtt/homie/node.rb +13 -0
- data/lib/mqtt/homie/property.rb +68 -35
- data/lib/mqtt/homie/version.rb +1 -1
- metadata +47 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8552d2aca44043208c3b3cd06fff9d5376515103d7de423926713441acecd6e0
|
4
|
+
data.tar.gz: a27b5fe2fe50e2975708c308ed9a0f9b5b7d2469667741fe092210adcda8582a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b10c773d61781b4fb2e5595056e1b5e41373ec579bcf0a152f0c5dc763c3973ad09914964caaaf9c619b079e6cf15f4dc923122caf214f571741b8b6934a8317
|
7
|
+
data.tar.gz: dd8069070124dea54d81f3fc0095b9cce1d28d77ed655d7162182c0a11cdfecbb93cc40c97ea3778b3ebd35508cf960fdfcdd2594cac87f3621fdf11e64845c0
|
data/lib/homie-mqtt.rb
CHANGED
@@ -4,13 +4,13 @@ module MQTT
|
|
4
4
|
module Homie
|
5
5
|
class << self
|
6
6
|
def escape_id(id)
|
7
|
-
id.downcase.gsub(/[^a-z0-9\-]/,
|
7
|
+
id.downcase.gsub(/[^a-z0-9\-]/, "-").sub(/^[^a-z0-9]+/, "")
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
13
|
+
require "mqtt/homie/base"
|
14
|
+
require "mqtt/homie/device"
|
15
|
+
require "mqtt/homie/node"
|
16
|
+
require "mqtt/homie/property"
|
data/lib/mqtt/homie/base.rb
CHANGED
@@ -9,18 +9,19 @@ module MQTT
|
|
9
9
|
|
10
10
|
def initialize(id, name)
|
11
11
|
raise ArgumentError, "Invalid Homie ID '#{id}'" unless id.is_a?(String) && id =~ Regexp.new("^#{REGEX}$")
|
12
|
+
|
12
13
|
@id = id
|
13
14
|
@name = name
|
14
15
|
end
|
15
16
|
|
16
17
|
def name=(value)
|
17
|
-
if name
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
return if name == value
|
19
|
+
|
20
|
+
name = value
|
21
|
+
return unless @published
|
22
|
+
|
23
|
+
device.init do
|
24
|
+
mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
data/lib/mqtt/homie/device.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "mqtt"
|
4
|
+
require "ruby2_keywords"
|
5
5
|
|
6
6
|
module MQTT
|
7
7
|
module Homie
|
8
8
|
class Device < Base
|
9
|
+
# the Homie spec version
|
10
|
+
VERSION = "4.0.0"
|
11
|
+
|
9
12
|
attr_reader :root_topic, :state, :mqtt
|
10
|
-
attr_accessor :logger
|
11
|
-
attr_accessor :out_of_band_topic_proc
|
13
|
+
attr_accessor :logger, :out_of_band_topic_proc
|
12
14
|
|
13
15
|
def initialize(id, name, root_topic: nil, mqtt: nil, clear_topics: true, &block)
|
14
16
|
super(id, name)
|
15
|
-
@root_topic =
|
17
|
+
@root_topic = root_topic || "homie"
|
16
18
|
@state = :init
|
17
19
|
@nodes = {}
|
18
20
|
@published = false
|
@@ -23,9 +25,7 @@ module MQTT
|
|
23
25
|
|
24
26
|
@mqtt.on_reconnect do
|
25
27
|
each do |node|
|
26
|
-
node.each
|
27
|
-
property.subscribe
|
28
|
-
end
|
28
|
+
node.each(&:subscribe)
|
29
29
|
end
|
30
30
|
mqtt.publish("#{topic}/$state", :init, retain: true, qos: 1)
|
31
31
|
mqtt.publish("#{topic}/$state", state, retain: true, qos: 1) unless state == :init
|
@@ -35,6 +35,10 @@ module MQTT
|
|
35
35
|
self.clear_topics if clear_topics
|
36
36
|
end
|
37
37
|
|
38
|
+
def inspect
|
39
|
+
"#<MQTT::Homie::Device #{topic} name=#{name.inspect}, $state=#{state.inspect}>"
|
40
|
+
end
|
41
|
+
|
38
42
|
def device
|
39
43
|
self
|
40
44
|
end
|
@@ -61,6 +65,7 @@ module MQTT
|
|
61
65
|
|
62
66
|
def remove_node(id)
|
63
67
|
return false unless (node = @nodes[id])
|
68
|
+
|
64
69
|
init do
|
65
70
|
node.unpublish
|
66
71
|
@nodes.delete(id)
|
@@ -77,11 +82,15 @@ module MQTT
|
|
77
82
|
@nodes.each_value(&block)
|
78
83
|
end
|
79
84
|
|
85
|
+
def count
|
86
|
+
@nodes.count
|
87
|
+
end
|
88
|
+
|
80
89
|
def publish
|
81
90
|
return if @published
|
82
91
|
|
83
92
|
mqtt.batch_publish do
|
84
|
-
mqtt.publish("#{topic}/$homie",
|
93
|
+
mqtt.publish("#{topic}/$homie", VERSION, retain: true, qos: 1)
|
85
94
|
mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
|
86
95
|
mqtt.publish("#{topic}/$state", @state.to_s, retain: true, qos: 1)
|
87
96
|
|
@@ -106,6 +115,9 @@ module MQTT
|
|
106
115
|
|
107
116
|
mqtt.publish("#{topic}/$nodes", @nodes.keys.join(","), retain: true, qos: 1)
|
108
117
|
@nodes.each_value(&:publish)
|
118
|
+
|
119
|
+
yield if block_given?
|
120
|
+
|
109
121
|
mqtt.publish("#{topic}/$state", (@state = :ready).to_s, retain: true, qos: 1)
|
110
122
|
end
|
111
123
|
|
@@ -126,17 +138,15 @@ module MQTT
|
|
126
138
|
end
|
127
139
|
|
128
140
|
def init
|
129
|
-
if state == :init
|
130
|
-
return yield state
|
131
|
-
end
|
141
|
+
return yield state if state == :init
|
132
142
|
|
133
143
|
prior_state = state
|
134
|
-
mqtt.publish("#{topic}/$state", (state = :init).to_s, retain: true, qos: 1)
|
144
|
+
mqtt.publish("#{topic}/$state", (self.state = :init).to_s, retain: true, qos: 1)
|
135
145
|
result = nil
|
136
146
|
mqtt.batch_publish do
|
137
147
|
result = yield prior_state
|
138
148
|
end
|
139
|
-
mqtt.publish("#{topic}/$state", (state = :ready).to_s, retain: true, qos: 1)
|
149
|
+
mqtt.publish("#{topic}/$state", (self.state = :ready).to_s, retain: true, qos: 1)
|
140
150
|
result
|
141
151
|
end
|
142
152
|
|
@@ -145,7 +155,7 @@ module MQTT
|
|
145
155
|
|
146
156
|
@mqtt.subscribe("#{topic}/#")
|
147
157
|
@mqtt.unsubscribe("#{topic}/#", wait_for_ack: true)
|
148
|
-
|
158
|
+
until @mqtt.queue_empty?
|
149
159
|
packet = @mqtt.get
|
150
160
|
@mqtt.publish(packet.topic, retain: true, qos: 0)
|
151
161
|
end
|
data/lib/mqtt/homie/node.rb
CHANGED
@@ -13,6 +13,16 @@ module MQTT
|
|
13
13
|
@published = false
|
14
14
|
end
|
15
15
|
|
16
|
+
def inspect
|
17
|
+
"#<MQTT::Homie::Node #{topic} name=#{full_name.inspect}, type=#{type.inspect}>"
|
18
|
+
end
|
19
|
+
|
20
|
+
def full_name
|
21
|
+
return name if device.count == 1
|
22
|
+
|
23
|
+
"#{device.name} #{name}"
|
24
|
+
end
|
25
|
+
|
16
26
|
def topic
|
17
27
|
"#{device.topic}/#{id}"
|
18
28
|
end
|
@@ -21,6 +31,7 @@ module MQTT
|
|
21
31
|
device.init do |prior_state|
|
22
32
|
property = Property.new(self, *args, &block)
|
23
33
|
raise ArgumentError, "Property '#{property.id}' already exists on '#{id}'" if @properties.key?(property.id)
|
34
|
+
|
24
35
|
@properties[property.id] = property
|
25
36
|
property.publish if prior_state == :ready
|
26
37
|
property
|
@@ -29,6 +40,7 @@ module MQTT
|
|
29
40
|
|
30
41
|
def remove_property(id)
|
31
42
|
return false unless (property = @properties[id])
|
43
|
+
|
32
44
|
init do
|
33
45
|
property.unpublish
|
34
46
|
@properties.delete(id)
|
@@ -73,6 +85,7 @@ module MQTT
|
|
73
85
|
|
74
86
|
def unpublish
|
75
87
|
return unless @published
|
88
|
+
|
76
89
|
@published = false
|
77
90
|
|
78
91
|
mqtt.publish("#{topic}/$name", retain: true, qos: 0)
|
data/lib/mqtt/homie/property.rb
CHANGED
@@ -6,19 +6,26 @@ module MQTT
|
|
6
6
|
attr_reader :node, :datatype, :format, :unit, :value
|
7
7
|
|
8
8
|
def initialize(node, id, name, datatype, value = nil, format: nil, retained: true, unit: nil, &block)
|
9
|
-
raise ArgumentError, "Invalid Homie datatype" unless %i[string integer float boolean enum color datetime
|
9
|
+
raise ArgumentError, "Invalid Homie datatype" unless %i[string integer float boolean enum color datetime
|
10
|
+
duration].include?(datatype)
|
10
11
|
raise ArgumentError, "retained must be boolean" unless [true, false].include?(retained)
|
12
|
+
|
11
13
|
format = format.join(",") if format.is_a?(Array) && datatype == :enum
|
12
|
-
if %i
|
14
|
+
if %i[integer float].include?(datatype) && format.is_a?(Range)
|
13
15
|
raise ArgumentError "only inclusive ranges are supported" if format.exclude_end?
|
16
|
+
|
14
17
|
format = "#{format.begin}:#{format.end}"
|
15
18
|
end
|
16
19
|
raise ArgumentError, "format must be nil or a string" unless format.nil? || format.is_a?(String)
|
17
20
|
raise ArgumentError, "unit must be nil or a string" unless unit.nil? || unit.is_a?(String)
|
18
21
|
raise ArgumentError, "format is required for enums" if datatype == :enum && format.nil?
|
19
22
|
raise ArgumentError, "format is required for colors" if datatype == :color && format.nil?
|
20
|
-
|
21
|
-
|
23
|
+
if datatype == :color && !%w[rgb hsv].include?(format.to_s)
|
24
|
+
raise ArgumentError, "format must be either rgb or hsv for colors"
|
25
|
+
end
|
26
|
+
if !value.nil? && !retained
|
27
|
+
raise ArgumentError, "an initial value cannot be provided for a non-retained property"
|
28
|
+
end
|
22
29
|
|
23
30
|
super(id, name)
|
24
31
|
|
@@ -32,6 +39,24 @@ module MQTT
|
|
32
39
|
@block = block
|
33
40
|
end
|
34
41
|
|
42
|
+
def inspect
|
43
|
+
result = +"#<MQTT::Homie::Property #{topic} name=#{full_name.inspect}, datatype=#{datatype.inspect}"
|
44
|
+
result << ", format=#{format.inspect}" if format
|
45
|
+
result << ", unit=#{unit.inspect}" if unit
|
46
|
+
result << ", settable=true" if settable?
|
47
|
+
result << if retained?
|
48
|
+
", value=#{value.inspect}"
|
49
|
+
else
|
50
|
+
", retained=false"
|
51
|
+
end
|
52
|
+
result << ">"
|
53
|
+
result.freeze
|
54
|
+
end
|
55
|
+
|
56
|
+
def full_name
|
57
|
+
"#{node.full_name} #{name}"
|
58
|
+
end
|
59
|
+
|
35
60
|
def device
|
36
61
|
node.device
|
37
62
|
end
|
@@ -49,39 +74,41 @@ module MQTT
|
|
49
74
|
end
|
50
75
|
|
51
76
|
def value=(value)
|
52
|
-
if @value
|
53
|
-
|
54
|
-
|
55
|
-
|
77
|
+
return if @value == value
|
78
|
+
|
79
|
+
@value = value if retained?
|
80
|
+
publish_value if @published
|
56
81
|
end
|
57
82
|
|
58
83
|
def unit=(unit)
|
59
|
-
if unit
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
84
|
+
return if unit == @unit
|
85
|
+
|
86
|
+
@unit = unit
|
87
|
+
return unless @published
|
88
|
+
|
89
|
+
device.init do
|
90
|
+
mqtt.publish("#{topic}/$unit", unit.to_s, retain: true, qos: 1)
|
66
91
|
end
|
67
92
|
end
|
68
93
|
|
69
94
|
def format=(format)
|
70
|
-
if format
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
95
|
+
return if format == @format
|
96
|
+
|
97
|
+
@format = format
|
98
|
+
return unless @published
|
99
|
+
|
100
|
+
device.init do
|
101
|
+
mqtt.publish("#{topic}/$format", format.to_s, retain: true, qos: 1)
|
77
102
|
end
|
78
103
|
end
|
79
104
|
|
80
105
|
def range
|
106
|
+
return nil unless format
|
107
|
+
|
81
108
|
case datatype
|
82
|
-
when :enum
|
83
|
-
when :integer
|
84
|
-
when :float
|
109
|
+
when :enum then format.split(",")
|
110
|
+
when :integer then Range.new(*format.split(":").map(&:to_i))
|
111
|
+
when :float then Range.new(*format.split(":").map(&:to_f))
|
85
112
|
else; raise MethodNotImplemented
|
86
113
|
end
|
87
114
|
end
|
@@ -89,24 +116,29 @@ module MQTT
|
|
89
116
|
def set(value)
|
90
117
|
case datatype
|
91
118
|
when :boolean
|
92
|
-
return unless %w
|
93
|
-
|
119
|
+
return unless %w[true false].include?(value)
|
120
|
+
|
121
|
+
value = value == "true"
|
94
122
|
when :integer
|
95
|
-
return unless
|
123
|
+
return unless /^-?\d+$/.match?(value)
|
124
|
+
|
96
125
|
value = value.to_i
|
97
|
-
return
|
126
|
+
return if format && !range.include?(value)
|
98
127
|
when :float
|
99
|
-
return unless
|
128
|
+
return unless /^-?(?:\d+|\d+\.|\.\d+|\d+\.\d+)(?:[eE]-?\d+)?$/.match?(value)
|
129
|
+
|
100
130
|
value = value.to_f
|
101
|
-
return
|
131
|
+
return if format && !range.include?(value)
|
102
132
|
when :enum
|
103
133
|
return unless range.include?(value)
|
104
134
|
when :color
|
105
|
-
return unless
|
106
|
-
|
107
|
-
|
135
|
+
return unless /^\d{1,3},\d{1,3},\d{1,3}$/.match?(value)
|
136
|
+
|
137
|
+
value = value.split(",").map(&:to_i)
|
138
|
+
case format
|
139
|
+
when "rgb"
|
108
140
|
return if value.max > 255
|
109
|
-
|
141
|
+
when "hsv"
|
110
142
|
return if value.first > 360 || value[1..2].max > 100
|
111
143
|
end
|
112
144
|
when :datetime
|
@@ -153,6 +185,7 @@ module MQTT
|
|
153
185
|
|
154
186
|
def unpublish
|
155
187
|
return unless @published
|
188
|
+
|
156
189
|
@published = false
|
157
190
|
|
158
191
|
mqtt.publish("#{topic}/$name", retain: true, qos: 0)
|
data/lib/mqtt/homie/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: homie-mqtt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mqtt-ccutrer
|
@@ -72,6 +72,48 @@ dependencies:
|
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: '13.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rubocop
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.23'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.23'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rubocop-performance
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.12'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.12'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rubocop-rake
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.6'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0.6'
|
75
117
|
description:
|
76
118
|
email: cody@cutrer.com'
|
77
119
|
executables: []
|
@@ -87,7 +129,8 @@ files:
|
|
87
129
|
homepage: https://github.com/ccutrer/homie-mqtt
|
88
130
|
licenses:
|
89
131
|
- MIT
|
90
|
-
metadata:
|
132
|
+
metadata:
|
133
|
+
rubygems_mfa_required: 'true'
|
91
134
|
post_install_message:
|
92
135
|
rdoc_options: []
|
93
136
|
require_paths:
|
@@ -96,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
139
|
requirements:
|
97
140
|
- - ">="
|
98
141
|
- !ruby/object:Gem::Version
|
99
|
-
version: '
|
142
|
+
version: '2.5'
|
100
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
144
|
requirements:
|
102
145
|
- - ">="
|