mqtt-homie 0.1.1 → 0.1.2
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/.gitignore +2 -0
- data/README.md +67 -65
- data/lib/mqtt/homie/client.rb +173 -173
- data/lib/mqtt/homie/device.rb +108 -108
- data/lib/mqtt/homie/device_builder.rb +38 -38
- data/lib/mqtt/homie/homie_attribute.rb +125 -125
- data/lib/mqtt/homie/homie_object.rb +17 -17
- data/lib/mqtt/homie/network.rb +43 -42
- data/lib/mqtt/homie/node.rb +26 -26
- data/lib/mqtt/homie/property.rb +48 -48
- data/lib/mqtt/homie/version.rb +5 -5
- data/lib/mqtt/homie.rb +29 -29
- data/mqtt-homie.gemspec +36 -37
- metadata +25 -41
- data/Gemfile.lock +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccd07988b5eafc7e3387a4a545d986dcea8f1d1355d9b6dd48bf4de2231eda61
|
4
|
+
data.tar.gz: 3889c445feaee69b229c8fe8c08d4c619cc0a83eb389f150aba2430c406bb39b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ed023aa563488d443537cb4ec9fd1656c8b91218bee305883489cb54ed380567c164e7731f3a1a4e7795c0dcb6e1aea6b0cb0b5f5926e8db90d23f47de60aa6
|
7
|
+
data.tar.gz: f5bb67c340d10e4413a68e4517383cbbad57b84f410adcd0b1e9f9d1b5be6000da0b73c1561d7e2ebe0fe820724dd88f499dc2739e28c39a5a52fd94fd6fde7f
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,66 +1,68 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
.property(id: "
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
client
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
1
|
+
[](https://travis-ci.com/sobakasu/mqtt-homie)
|
2
|
+
|
3
|
+
# MQTT::Homie
|
4
|
+
|
5
|
+
A ruby interface for creating a device conforming to the MQTT [Homie] convention.
|
6
|
+
This gem builds upon the [ruby-mqtt] ruby gem.
|
7
|
+
|
8
|
+
The [Homie] convention defines a standardized way of how IoT devices and services announce themselves and their data to a MQTT broker.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'mqtt-homie'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install mqtt-homie
|
25
|
+
|
26
|
+
## Quick Start
|
27
|
+
|
28
|
+
~~~ ruby
|
29
|
+
require 'rubygems'
|
30
|
+
require 'mqtt/homie'
|
31
|
+
|
32
|
+
# Set up a device, with a node and properties
|
33
|
+
device = MQTT::Homie.device_builder(id: 'device', name: 'Device')
|
34
|
+
.node(id: "gate", name: "Front gate", type: "Gate")
|
35
|
+
.property(id: "state", name: "Gate state", enum: [:open, :closed, :opening, :closing], value: :closed)
|
36
|
+
.property(id: "position", name: "Gate position", datatype: :integer, unit: "%", value: 0)
|
37
|
+
.property(id: "command", name: "Send gate command", settable: true, enum: [:open, :close]).build
|
38
|
+
|
39
|
+
# Create a client and connect to a MQTT broker
|
40
|
+
client = MQTT::Homie::Client.new(device: device, host: 'localhost')
|
41
|
+
client.connect
|
42
|
+
|
43
|
+
# access nodes and properties of the device
|
44
|
+
node = device.node('gate')
|
45
|
+
state = node.property('state')
|
46
|
+
state.value = :open # publishes new state to MQTT
|
47
|
+
|
48
|
+
# listen for changes to properties via the Observer interface
|
49
|
+
node.property('command').add_observer(self)
|
50
|
+
~~~
|
51
|
+
|
52
|
+
## Overview
|
53
|
+
|
54
|
+
TODO
|
55
|
+
|
56
|
+
## License
|
57
|
+
|
58
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
59
|
+
|
60
|
+
## Code of Conduct
|
61
|
+
|
62
|
+
Everyone interacting in the Mqtt::Homie project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/mqtt-homie/blob/master/CODE_OF_CONDUCT.md).
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
[Homie]: https://homieiot.github.io/
|
67
|
+
[MQTT]: http://www.mqtt.org/
|
66
68
|
[ruby-mqtt]: https://github.com/njh/ruby-mqtt
|
data/lib/mqtt/homie/client.rb
CHANGED
@@ -1,173 +1,173 @@
|
|
1
|
-
module MQTT
|
2
|
-
module Homie
|
3
|
-
# https://homieiot.github.io/specification/
|
4
|
-
|
5
|
-
class Client
|
6
|
-
DEFAULT_ROOT_TOPIC = "homie"
|
7
|
-
|
8
|
-
attr_accessor :host, :root_topic
|
9
|
-
attr_reader :device
|
10
|
-
|
11
|
-
def initialize(options = {})
|
12
|
-
@device = options[:device]
|
13
|
-
@host = options[:host]
|
14
|
-
@root_topic = options[:root_topic] || DEFAULT_ROOT_TOPIC
|
15
|
-
|
16
|
-
raise "device required" unless @device
|
17
|
-
|
18
|
-
# next version of homie doesn't use stats or firmware details
|
19
|
-
@use_stats = true
|
20
|
-
if options[:develop]
|
21
|
-
@device.use_stats = false
|
22
|
-
@device.use_fw = false
|
23
|
-
@use_stats = false
|
24
|
-
end
|
25
|
-
|
26
|
-
# observe all node properties so we can publish values when they change
|
27
|
-
@device.nodes.each do |node|
|
28
|
-
node.properties.each do |property|
|
29
|
-
property.add_observer(self)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def connect
|
35
|
-
return if connected?
|
36
|
-
|
37
|
-
@device.state = :init
|
38
|
-
@client = create_mqtt_client
|
39
|
-
@client.connect
|
40
|
-
|
41
|
-
publish(@device, topic)
|
42
|
-
publish_statistics if @use_stats
|
43
|
-
|
44
|
-
@threads = []
|
45
|
-
|
46
|
-
# run a thread to publish statistics
|
47
|
-
@threads << Thread.new { run_statistics } if @use_stats
|
48
|
-
|
49
|
-
# run a thread to listen for settings
|
50
|
-
@threads << Thread.new { run_set_listener }
|
51
|
-
|
52
|
-
@device.state = :ready
|
53
|
-
publish_state
|
54
|
-
end
|
55
|
-
|
56
|
-
def disconnect
|
57
|
-
@device.state = :disconnected
|
58
|
-
publish_state
|
59
|
-
|
60
|
-
@client.disconnect
|
61
|
-
@client = nil
|
62
|
-
|
63
|
-
@threads.each { |i| i[:done] = true }
|
64
|
-
@threads = []
|
65
|
-
end
|
66
|
-
|
67
|
-
def topic
|
68
|
-
@root_topic + "/" + @device.id
|
69
|
-
end
|
70
|
-
|
71
|
-
def connected?
|
72
|
-
@device.state == :ready
|
73
|
-
end
|
74
|
-
|
75
|
-
def update(time, object)
|
76
|
-
if object.kind_of?(MQTT::Homie::Property)
|
77
|
-
publish_property_value(object)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def create_mqtt_client
|
84
|
-
client = ::MQTT::Client.new
|
85
|
-
client.host = @host
|
86
|
-
client.will_topic = topic + "/$state"
|
87
|
-
client.will_payload = :lost
|
88
|
-
client.will_retain = true
|
89
|
-
client
|
90
|
-
end
|
91
|
-
|
92
|
-
def run_set_listener
|
93
|
-
# subscribe to 'set' topics for all settable properties
|
94
|
-
@device.nodes.each do |node|
|
95
|
-
node.properties.each do |property|
|
96
|
-
if property.settable?
|
97
|
-
set_topic = topic + "/" + node.topic + "/" + property.topic + "/set"
|
98
|
-
debug("subscribe #{set_topic}")
|
99
|
-
@client.subscribe(set_topic) if @client
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
if @client
|
105
|
-
@client.get do |topic, message|
|
106
|
-
debug("received message: #{topic}, message: #{message}")
|
107
|
-
property = find_property_by_set_topic(topic)
|
108
|
-
property.value = message if property
|
109
|
-
break if Thread.current[:done]
|
110
|
-
end
|
111
|
-
end
|
112
|
-
debug("set listener thread exiting")
|
113
|
-
end
|
114
|
-
|
115
|
-
def run_statistics
|
116
|
-
while !Thread.current[:done]
|
117
|
-
publish_statistics
|
118
|
-
|
119
|
-
# halve interval, if we miss a notification then we will be marked as offline
|
120
|
-
sleep @device.stats.interval / 2
|
121
|
-
end
|
122
|
-
debug("statistics thread exiting")
|
123
|
-
end
|
124
|
-
|
125
|
-
def find_property_by_set_topic(set_topic)
|
126
|
-
@device.nodes.each do |node|
|
127
|
-
node.properties.each do |property|
|
128
|
-
return property if set_topic == topic + "/" + node.topic + "/" + property.topic + "/set"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
nil
|
132
|
-
end
|
133
|
-
|
134
|
-
def publish_statistics
|
135
|
-
publish(@device.stats, topic + "/$stats")
|
136
|
-
end
|
137
|
-
|
138
|
-
def publish_property_value(property)
|
139
|
-
node = @device.nodes.find { |i| i.properties.include?(property) }
|
140
|
-
data = {
|
141
|
-
property.id => property.value,
|
142
|
-
}
|
143
|
-
publish(data, topic + "/" + node.topic)
|
144
|
-
end
|
145
|
-
|
146
|
-
def publish_state
|
147
|
-
data = {
|
148
|
-
"$state" => @device.state,
|
149
|
-
}
|
150
|
-
publish(data, topic)
|
151
|
-
end
|
152
|
-
|
153
|
-
def publish(object, prefix = nil)
|
154
|
-
data = {}
|
155
|
-
if object.respond_to?(:homie_attributes)
|
156
|
-
data = object.homie_attributes
|
157
|
-
else
|
158
|
-
data = object
|
159
|
-
end
|
160
|
-
|
161
|
-
data.each do |k, v|
|
162
|
-
topic = prefix + "/" + k
|
163
|
-
debug("mqtt publish #{topic} -> #{v}")
|
164
|
-
@client.publish(topic, v, true)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def debug(message)
|
169
|
-
MQTT::Homie.debug(message)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
1
|
+
module MQTT
|
2
|
+
module Homie
|
3
|
+
# https://homieiot.github.io/specification/
|
4
|
+
|
5
|
+
class Client
|
6
|
+
DEFAULT_ROOT_TOPIC = "homie"
|
7
|
+
|
8
|
+
attr_accessor :host, :root_topic
|
9
|
+
attr_reader :device
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@device = options[:device]
|
13
|
+
@host = options[:host]
|
14
|
+
@root_topic = options[:root_topic] || DEFAULT_ROOT_TOPIC
|
15
|
+
|
16
|
+
raise "device required" unless @device
|
17
|
+
|
18
|
+
# next version of homie doesn't use stats or firmware details
|
19
|
+
@use_stats = true
|
20
|
+
if options[:develop]
|
21
|
+
@device.use_stats = false
|
22
|
+
@device.use_fw = false
|
23
|
+
@use_stats = false
|
24
|
+
end
|
25
|
+
|
26
|
+
# observe all node properties so we can publish values when they change
|
27
|
+
@device.nodes.each do |node|
|
28
|
+
node.properties.each do |property|
|
29
|
+
property.add_observer(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def connect
|
35
|
+
return if connected?
|
36
|
+
|
37
|
+
@device.state = :init
|
38
|
+
@client = create_mqtt_client
|
39
|
+
@client.connect
|
40
|
+
|
41
|
+
publish(@device, topic)
|
42
|
+
publish_statistics if @use_stats
|
43
|
+
|
44
|
+
@threads = []
|
45
|
+
|
46
|
+
# run a thread to publish statistics
|
47
|
+
@threads << Thread.new { run_statistics } if @use_stats
|
48
|
+
|
49
|
+
# run a thread to listen for settings
|
50
|
+
@threads << Thread.new { run_set_listener }
|
51
|
+
|
52
|
+
@device.state = :ready
|
53
|
+
publish_state
|
54
|
+
end
|
55
|
+
|
56
|
+
def disconnect
|
57
|
+
@device.state = :disconnected
|
58
|
+
publish_state
|
59
|
+
|
60
|
+
@client.disconnect
|
61
|
+
@client = nil
|
62
|
+
|
63
|
+
@threads.each { |i| i[:done] = true }
|
64
|
+
@threads = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def topic
|
68
|
+
@root_topic + "/" + @device.id
|
69
|
+
end
|
70
|
+
|
71
|
+
def connected?
|
72
|
+
@device.state == :ready
|
73
|
+
end
|
74
|
+
|
75
|
+
def update(time, object)
|
76
|
+
if object.kind_of?(MQTT::Homie::Property)
|
77
|
+
publish_property_value(object)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def create_mqtt_client
|
84
|
+
client = ::MQTT::Client.new
|
85
|
+
client.host = @host
|
86
|
+
client.will_topic = topic + "/$state"
|
87
|
+
client.will_payload = :lost
|
88
|
+
client.will_retain = true
|
89
|
+
client
|
90
|
+
end
|
91
|
+
|
92
|
+
def run_set_listener
|
93
|
+
# subscribe to 'set' topics for all settable properties
|
94
|
+
@device.nodes.each do |node|
|
95
|
+
node.properties.each do |property|
|
96
|
+
if property.settable?
|
97
|
+
set_topic = topic + "/" + node.topic + "/" + property.topic + "/set"
|
98
|
+
debug("subscribe #{set_topic}")
|
99
|
+
@client.subscribe(set_topic) if @client
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if @client
|
105
|
+
@client.get do |topic, message|
|
106
|
+
debug("received message: #{topic}, message: #{message}")
|
107
|
+
property = find_property_by_set_topic(topic)
|
108
|
+
property.value = message if property
|
109
|
+
break if Thread.current[:done]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
debug("set listener thread exiting")
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_statistics
|
116
|
+
while !Thread.current[:done]
|
117
|
+
publish_statistics
|
118
|
+
|
119
|
+
# halve interval, if we miss a notification then we will be marked as offline
|
120
|
+
sleep @device.stats.interval / 2
|
121
|
+
end
|
122
|
+
debug("statistics thread exiting")
|
123
|
+
end
|
124
|
+
|
125
|
+
def find_property_by_set_topic(set_topic)
|
126
|
+
@device.nodes.each do |node|
|
127
|
+
node.properties.each do |property|
|
128
|
+
return property if set_topic == topic + "/" + node.topic + "/" + property.topic + "/set"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def publish_statistics
|
135
|
+
publish(@device.stats, topic + "/$stats")
|
136
|
+
end
|
137
|
+
|
138
|
+
def publish_property_value(property)
|
139
|
+
node = @device.nodes.find { |i| i.properties.include?(property) }
|
140
|
+
data = {
|
141
|
+
property.id => property.value,
|
142
|
+
}
|
143
|
+
publish(data, topic + "/" + node.topic)
|
144
|
+
end
|
145
|
+
|
146
|
+
def publish_state
|
147
|
+
data = {
|
148
|
+
"$state" => @device.state,
|
149
|
+
}
|
150
|
+
publish(data, topic)
|
151
|
+
end
|
152
|
+
|
153
|
+
def publish(object, prefix = nil)
|
154
|
+
data = {}
|
155
|
+
if object.respond_to?(:homie_attributes)
|
156
|
+
data = object.homie_attributes
|
157
|
+
else
|
158
|
+
data = object
|
159
|
+
end
|
160
|
+
|
161
|
+
data.each do |k, v|
|
162
|
+
topic = prefix + "/" + k
|
163
|
+
debug("mqtt publish #{topic} -> #{v}")
|
164
|
+
@client.publish(topic, v, true)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def debug(message)
|
169
|
+
MQTT::Homie.debug(message)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|