homie-mqtt 1.4.5 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|