homie-mqtt 1.0.1 → 1.2.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/mqtt/homie/base.rb +1 -1
- data/lib/mqtt/homie/device.rb +62 -25
- data/lib/mqtt/homie/node.rb +25 -11
- data/lib/mqtt/homie/property.rb +68 -12
- data/lib/mqtt/homie/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd6f3bb8678e0d56ddc578993ba46f8bafb3361e4d5f7b0d5fbede478e640edc
|
4
|
+
data.tar.gz: 4961a257ee03d29d623c009dad99f54ac44ee6701fa1364d2b83d6ce44a3898d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5aedd7bde7d6cc07a8837802eaeef6f563d4d846b912f737df888a8d2d893bbe2556191f7e2847f2a903527b5c3d7342f62d63562175eac7207dde46867877b1
|
7
|
+
data.tar.gz: dfaa32de5c1126801ceeb164fe919a35c266fec727096539e6e3e794c19d380852c0a910a090222ba5d3396edf7a5a25522d25396453c6734cf5ff33b88ff36d
|
data/lib/mqtt/homie/base.rb
CHANGED
data/lib/mqtt/homie/device.rb
CHANGED
@@ -5,9 +5,9 @@ require 'mqtt'
|
|
5
5
|
module MQTT
|
6
6
|
module Homie
|
7
7
|
class Device < Base
|
8
|
-
attr_reader :root_topic, :state, :
|
8
|
+
attr_reader :root_topic, :state, :mqtt
|
9
9
|
|
10
|
-
def initialize(id, name, root_topic: nil, mqtt: nil, &block)
|
10
|
+
def initialize(id, name, root_topic: nil, mqtt: nil, clear_topics: true, &block)
|
11
11
|
super(id, name)
|
12
12
|
@root_topic = @root_topic || "homie"
|
13
13
|
@state = :init
|
@@ -16,8 +16,20 @@ module MQTT
|
|
16
16
|
@block = block
|
17
17
|
mqtt = MQTT::Client.new(mqtt) if mqtt.is_a?(String)
|
18
18
|
@mqtt = mqtt || MQTT::Client.new
|
19
|
-
@mqtt.set_will("#{topic}/$state", "lost", true)
|
19
|
+
@mqtt.set_will("#{topic}/$state", "lost", retain: true, qos: 1)
|
20
|
+
|
21
|
+
@mqtt.on_reconnect do
|
22
|
+
each do |node|
|
23
|
+
node.each do |property|
|
24
|
+
property.subscribe
|
25
|
+
end
|
26
|
+
end
|
27
|
+
mqtt.publish("#{topic}/$state", :init, retain: true, qos: 1)
|
28
|
+
mqtt.publish("#{topic}/$state", state, retain: true, qos: 1) unless state == :init
|
29
|
+
end
|
30
|
+
|
20
31
|
@mqtt.connect
|
32
|
+
self.clear_topics if clear_topics
|
21
33
|
end
|
22
34
|
|
23
35
|
def device
|
@@ -36,46 +48,59 @@ module MQTT
|
|
36
48
|
yield node if block_given?
|
37
49
|
if prior_state == :ready
|
38
50
|
node.publish
|
39
|
-
mqtt.publish("#{topic}/$nodes", nodes.keys.join(","), true, 1)
|
51
|
+
mqtt.publish("#{topic}/$nodes", @nodes.keys.join(","), retain: true, qos: 1)
|
40
52
|
end
|
41
53
|
end
|
42
54
|
self
|
43
55
|
end
|
44
56
|
|
45
57
|
def remove_node(id)
|
46
|
-
return unless (node = nodes[id])
|
58
|
+
return unless (node = @nodes[id])
|
47
59
|
init do
|
48
60
|
node.unpublish
|
49
61
|
@nodes.delete(id)
|
50
|
-
mqtt.publish("#{topic}/$nodes", nodes.keys.join(","), true, 1) if @published
|
62
|
+
mqtt.publish("#{topic}/$nodes", @nodes.keys.join(","), retain: true, qos: 1) if @published
|
51
63
|
end
|
52
64
|
end
|
53
65
|
|
66
|
+
def [](id)
|
67
|
+
@nodes[id]
|
68
|
+
end
|
69
|
+
|
70
|
+
def each(&block)
|
71
|
+
@nodes.each_value(&block)
|
72
|
+
end
|
73
|
+
|
54
74
|
def publish
|
55
75
|
return if @published
|
56
76
|
|
57
|
-
mqtt.
|
58
|
-
|
59
|
-
|
77
|
+
mqtt.batch_publish do
|
78
|
+
mqtt.publish("#{topic}/$homie", "4.0.0", retain: true, qos: 1)
|
79
|
+
mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
|
80
|
+
mqtt.publish("#{topic}/$state", @state.to_s, retain: true, qos: 1)
|
60
81
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
node = nodes[match[:node]] if match
|
65
|
-
property = node.properties[match[:property]] if node
|
82
|
+
@subscription_thread = Thread.new do
|
83
|
+
# you'll get the exception when you call `join`
|
84
|
+
Thread.current.report_on_exception = false
|
66
85
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
mqtt.get do |packet|
|
87
|
+
match = packet.topic.match(topic_regex)
|
88
|
+
node = @nodes[match[:node]] if match
|
89
|
+
property = node[match[:property]] if node
|
71
90
|
|
72
|
-
|
91
|
+
unless property&.settable?
|
92
|
+
@block&.call(topic, packet.payload)
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
property.set(packet.payload)
|
97
|
+
end
|
73
98
|
end
|
99
|
+
|
100
|
+
mqtt.publish("#{topic}/$nodes", @nodes.keys.join(","), retain: true, qos: 1)
|
101
|
+
@nodes.each_value(&:publish)
|
102
|
+
mqtt.publish("#{topic}/$state", (@state = :ready).to_s, retain: true, qos: 1)
|
74
103
|
end
|
75
|
-
|
76
|
-
mqtt.publish("#{topic}/$nodes", nodes.keys.join(","), true, 1)
|
77
|
-
nodes.each_value(&:publish)
|
78
|
-
mqtt.publish("#{topic}/$state", (@state = :ready).to_s, true, 1)
|
79
104
|
|
80
105
|
@published = true
|
81
106
|
end
|
@@ -88,6 +113,9 @@ module MQTT
|
|
88
113
|
|
89
114
|
def join
|
90
115
|
@subscription_thread&.join
|
116
|
+
rescue => e
|
117
|
+
e.set_backtrace(e.backtrace + ["<from Homie MQTT thread>"] + caller)
|
118
|
+
raise e
|
91
119
|
end
|
92
120
|
|
93
121
|
def init
|
@@ -97,14 +125,23 @@ module MQTT
|
|
97
125
|
end
|
98
126
|
|
99
127
|
prior_state = state
|
100
|
-
mqtt.publish("#{topic}/$state", (state = :init).to_s, true, 1)
|
128
|
+
mqtt.publish("#{topic}/$state", (state = :init).to_s, retain: true, qos: 1)
|
101
129
|
yield(prior_state)
|
102
|
-
mqtt.publish("#{topic}/$state", (state = :ready).to_s, true, 1)
|
130
|
+
mqtt.publish("#{topic}/$state", (state = :ready).to_s, retain: true, qos: 1)
|
103
131
|
nil
|
104
132
|
end
|
105
133
|
|
106
134
|
private
|
107
135
|
|
136
|
+
def clear_topics
|
137
|
+
@mqtt.subscribe("#{topic}/#")
|
138
|
+
@mqtt.unsubscribe("#{topic}/#", wait_for_ack: true)
|
139
|
+
while !@mqtt.queue_empty?
|
140
|
+
packet = @mqtt.get
|
141
|
+
@mqtt.publish(packet.topic, retain: true, qos: 0)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
108
145
|
def topic_regex
|
109
146
|
@topic_regex ||= Regexp.new("^#{Regexp.escape(topic)}/(?<node>#{REGEX})/(?<property>#{REGEX})/set$")
|
110
147
|
end
|
data/lib/mqtt/homie/node.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module MQTT
|
4
4
|
module Homie
|
5
5
|
class Node < Base
|
6
|
-
attr_reader :device, :type
|
6
|
+
attr_reader :device, :type
|
7
7
|
|
8
8
|
def initialize(device, id, name, type)
|
9
9
|
super(id, name)
|
@@ -28,34 +28,48 @@ module MQTT
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def remove_property(id)
|
31
|
-
return unless (property = properties[id])
|
31
|
+
return unless (property = @properties[id])
|
32
32
|
init do
|
33
33
|
property.unpublish
|
34
34
|
@properties.delete(id)
|
35
|
-
mqtt.publish("#{topic}/$properties", properties.keys.join(","), true, 1) if @published
|
35
|
+
mqtt.publish("#{topic}/$properties", @properties.keys.join(","), retain: true, qos: 1) if @published
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def [](id)
|
40
|
+
@properties[id]
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&block)
|
44
|
+
@properties.each_value(&block)
|
45
|
+
end
|
46
|
+
|
39
47
|
def mqtt
|
40
48
|
device.mqtt
|
41
49
|
end
|
42
50
|
|
43
51
|
def publish
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
52
|
+
mqtt.batch_publish do
|
53
|
+
unless @published
|
54
|
+
mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
|
55
|
+
mqtt.publish("#{topic}/$type", @type.to_s, retain: true, qos: 1)
|
56
|
+
@published = true
|
57
|
+
end
|
58
|
+
|
59
|
+
mqtt.publish("#{topic}/$properties", @properties.keys.join(","), retain: true, qos: 1)
|
60
|
+
@properties.each_value(&:publish)
|
48
61
|
end
|
49
|
-
|
50
|
-
mqtt.publish("#{topic}/$properties", properties.keys.join(","), true, 1)
|
51
|
-
properties.each_value(&:publish)
|
52
62
|
end
|
53
63
|
|
54
64
|
def unpublish
|
55
65
|
return unless @published
|
56
66
|
@published = false
|
57
67
|
|
58
|
-
|
68
|
+
mqtt.publish("#{topic}/$name", retain: true, qos: 0)
|
69
|
+
mqtt.publish("#{topic}/$type", retain: true, qos: 0)
|
70
|
+
mqtt.publish("#{topic}/$properties", retain: true, qos: 0)
|
71
|
+
|
72
|
+
@properties.each_value(&:unpublish)
|
59
73
|
end
|
60
74
|
end
|
61
75
|
end
|
data/lib/mqtt/homie/property.rb
CHANGED
@@ -8,9 +8,19 @@ module MQTT
|
|
8
8
|
def initialize(node, id, name, datatype, value = nil, format: nil, retained: true, unit: nil, &block)
|
9
9
|
raise ArgumentError, "Invalid Homie datatype" unless %s{string integer float boolean enum color}
|
10
10
|
raise ArgumentError, "retained must be boolean" unless [true, false].include?(retained)
|
11
|
+
format = format.join(",") if format.is_a?(Array) && datatype == :enum
|
12
|
+
if %i{integer float}.include?(datatype) && format.is_a?(Range)
|
13
|
+
raise ArgumentError "only inclusive ranges are supported" if format.exclude_end?
|
14
|
+
format = "#{format.begin}:#{format.end}"
|
15
|
+
end
|
11
16
|
raise ArgumentError, "format must be nil or a string" unless format.nil? || format.is_a?(String)
|
12
17
|
raise ArgumentError, "unit must be nil or a string" unless unit.nil? || unit.is_a?(String)
|
18
|
+
raise ArgumentError, "format is required for enums" if datatype == :enum && format.nil?
|
19
|
+
raise ArgumentError, "format is required for colors" if datatype == :color && format.nil?
|
20
|
+
raise ArgumentError, "format must be either rgb or hsv for colors" if datatype == :color && !%w{rgb hsv}.include?(format.to_s)
|
21
|
+
|
13
22
|
super(id, name)
|
23
|
+
|
14
24
|
@node = node
|
15
25
|
@datatype = datatype
|
16
26
|
@format = format
|
@@ -40,7 +50,7 @@ module MQTT
|
|
40
50
|
def value=(value)
|
41
51
|
if @value != value
|
42
52
|
@value = value
|
43
|
-
mqtt.publish(topic, value.to_s, retained?, 1) if @published
|
53
|
+
mqtt.publish(topic, value.to_s, retain: retained?, qos: 1) if @published
|
44
54
|
end
|
45
55
|
end
|
46
56
|
|
@@ -49,7 +59,7 @@ module MQTT
|
|
49
59
|
@unit = unit
|
50
60
|
if @published
|
51
61
|
device.init do
|
52
|
-
mqtt.publish("#{topic}/$unit", unit.to_s, true, 1)
|
62
|
+
mqtt.publish("#{topic}/$unit", unit.to_s, retain: true, qos: 1)
|
53
63
|
end
|
54
64
|
end
|
55
65
|
end
|
@@ -60,14 +70,47 @@ module MQTT
|
|
60
70
|
@format = format
|
61
71
|
if @published
|
62
72
|
device.init do
|
63
|
-
mqtt.publish("#{topic}/$format", format.to_s, true, 1)
|
73
|
+
mqtt.publish("#{topic}/$format", format.to_s, retain: true, qos: 1)
|
64
74
|
end
|
65
75
|
end
|
66
76
|
end
|
67
77
|
end
|
68
78
|
|
79
|
+
def range
|
80
|
+
case datatype
|
81
|
+
when :enum; format.split(',')
|
82
|
+
when :integer; Range.new(*format.split(':').map(&:to_i))
|
83
|
+
when :float; Range.new(*format.split(':').map(&:to_f))
|
84
|
+
else; raise MethodNotImplemented
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
69
88
|
def set(value)
|
70
|
-
|
89
|
+
case datatype
|
90
|
+
when :boolean
|
91
|
+
return unless %w{true false}.include?(value)
|
92
|
+
value = value == 'true'
|
93
|
+
when :integer
|
94
|
+
return unless value =~ /^-?\d+$/
|
95
|
+
value = value.to_i
|
96
|
+
return unless range.include?(value) if format
|
97
|
+
when :float
|
98
|
+
return unless value =~ /^-?(?:\d+|\d+\.|\.\d+|\d+\.\d+)(?:[eE]-?\d+)?$/
|
99
|
+
value = value.to_f
|
100
|
+
return unless range.include?(value) if format
|
101
|
+
when :enum
|
102
|
+
return unless range.include?(value)
|
103
|
+
when :color
|
104
|
+
return unless value =~ /^\d{1,3},\d{1,3},\d{1,3}$/
|
105
|
+
value = value.split(',').map(&:to_i)
|
106
|
+
if format == 'rgb'
|
107
|
+
return if value.max > 255
|
108
|
+
elsif format == 'hsv'
|
109
|
+
return if value.first > 360 || value[1..2].max > 100
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
@block.arity == 2 ? @block.call(value, self) : @block.call(value)
|
71
114
|
end
|
72
115
|
|
73
116
|
def mqtt
|
@@ -77,23 +120,36 @@ module MQTT
|
|
77
120
|
def publish
|
78
121
|
return if @published
|
79
122
|
|
80
|
-
mqtt.
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
123
|
+
mqtt.batch_publish do
|
124
|
+
mqtt.publish("#{topic}/$name", name, retain: true, qos: 1)
|
125
|
+
mqtt.publish("#{topic}/$datatype", datatype.to_s, retain: true, qos: 1)
|
126
|
+
mqtt.publish("#{topic}/$format", format, retain: true, qos: 1) if format
|
127
|
+
mqtt.publish("#{topic}/$settable", "true", retain: true, qos: 1) if settable?
|
128
|
+
mqtt.publish("#{topic}/$retained", "false", retain: true, qos: 1) unless retained?
|
129
|
+
mqtt.publish("#{topic}/$unit", unit, retain: true, qos: 1) if unit
|
130
|
+
mqtt.publish(topic, value.to_s, retain: retained?, qos: 1) if value
|
131
|
+
subscribe
|
132
|
+
end
|
88
133
|
|
89
134
|
@published = true
|
90
135
|
end
|
91
136
|
|
137
|
+
def subscribe
|
138
|
+
mqtt.subscribe("#{topic}/set") if settable?
|
139
|
+
end
|
140
|
+
|
92
141
|
def unpublish
|
93
142
|
return unless @published
|
94
143
|
@published = false
|
95
144
|
|
145
|
+
mqtt.publish("#{topic}/$name", retain: true, qos: 0)
|
146
|
+
mqtt.publish("#{topic}/$datatype", retain: true, qos: 0)
|
147
|
+
mqtt.publish("#{topic}/$format", retain: true, qos: 0) if format
|
148
|
+
mqtt.publish("#{topic}/$settable", retain: true, qos: 0) if settable?
|
149
|
+
mqtt.publish("#{topic}/$retained", retain: true, qos: 0) unless retained?
|
150
|
+
mqtt.publish("#{topic}/$unit", retain: true, qos: 0) if unit
|
96
151
|
mqtt.unsubscribe("#{topic}/set") if settable?
|
152
|
+
mqtt.publish(topic, retain: retained?, qos: 0) if value && retained?
|
97
153
|
end
|
98
154
|
end
|
99
155
|
end
|
data/lib/mqtt/homie/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: homie-mqtt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.2.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:
|
11
|
+
date: 2021-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: mqtt
|
14
|
+
name: mqtt-ccutrer
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: byebug
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|