mqtt-homie 0.1.0 → 0.1.1
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/Gemfile.lock +45 -41
- data/README.md +5 -7
- data/lib/mqtt/homie.rb +29 -27
- data/lib/mqtt/homie/client.rb +15 -4
- data/lib/mqtt/homie/device.rb +73 -62
- data/lib/mqtt/homie/homie_attribute.rb +125 -0
- data/lib/mqtt/homie/homie_object.rb +2 -24
- data/lib/mqtt/homie/network.rb +42 -0
- data/lib/mqtt/homie/node.rb +5 -13
- data/lib/mqtt/homie/property.rb +12 -23
- data/lib/mqtt/homie/version.rb +5 -5
- data/mqtt-homie.gemspec +37 -36
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e325ebb1aedaf50aef04bd62e84b8cb2107561f87f8aa71a9f5f037cc4d5a006
|
4
|
+
data.tar.gz: 4847fede7e1dcb105f4a869073c0a6a807cbcc0c6927059cf6465ed0f7794831
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b705eadfb0777b0f79f15a32965419bbfcfe2695dba3101c44a61680e38a9e58bbe634673f37f347213608099f7b13e8841e592135918899255c50bef4a0604
|
7
|
+
data.tar.gz: dd9f77fc45d35d87a3d514cc18ecef672f84fb94b8a7b218e5f52dddbf2132be2f87028338cf26446bc0a45a44d6e924484cb540483bafa83c1181e2c1983859
|
data/Gemfile.lock
CHANGED
@@ -1,41 +1,45 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
mqtt-homie (0.1.0)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
rspec-
|
21
|
-
|
22
|
-
|
23
|
-
rspec-support (~> 3.8.0)
|
24
|
-
rspec-
|
25
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
-
rspec-support (~> 3.8.0)
|
27
|
-
rspec-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mqtt-homie (0.1.0)
|
5
|
+
macaddr (~> 1.1)
|
6
|
+
mqtt (~> 0.5)
|
7
|
+
sys-uname (~> 1.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
diff-lcs (1.3)
|
13
|
+
ffi (1.11.1-x64-mingw32)
|
14
|
+
macaddr (1.7.2)
|
15
|
+
systemu (~> 2.6.5)
|
16
|
+
mqtt (0.5.0)
|
17
|
+
rake (10.5.0)
|
18
|
+
rspec (3.8.0)
|
19
|
+
rspec-core (~> 3.8.0)
|
20
|
+
rspec-expectations (~> 3.8.0)
|
21
|
+
rspec-mocks (~> 3.8.0)
|
22
|
+
rspec-core (3.8.1)
|
23
|
+
rspec-support (~> 3.8.0)
|
24
|
+
rspec-expectations (3.8.4)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.8.0)
|
27
|
+
rspec-mocks (3.8.1)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.8.0)
|
30
|
+
rspec-support (3.8.2)
|
31
|
+
sys-uname (1.0.4)
|
32
|
+
ffi (>= 1.0.0)
|
33
|
+
systemu (2.6.5)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
x64-mingw32
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
bundler (~> 2.0)
|
40
|
+
mqtt-homie!
|
41
|
+
rake (~> 10.0)
|
42
|
+
rspec (~> 3.0)
|
43
|
+
|
44
|
+
BUNDLED WITH
|
45
|
+
2.0.2
|
data/README.md
CHANGED
@@ -28,13 +28,11 @@ require 'rubygems'
|
|
28
28
|
require 'mqtt/homie'
|
29
29
|
|
30
30
|
# Set up a device, with a node and properties
|
31
|
-
device = MQTT::Homie.device_builder(id: 'device', name: 'Device'
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
31
|
+
device = MQTT::Homie.device_builder(id: 'device', name: 'Device')
|
32
|
+
.node(id: "gate", name: "Front gate", type: "Gate")
|
33
|
+
.property(id: "state", name: "Gate state", enum: [:open, :closed, :opening, :closing], value: :closed)
|
34
|
+
.property(id: "position", name: "Gate position", datatype: :integer, unit: "%", value: 0)
|
35
|
+
.property(id: "command", name: "Send gate command", settable: true, enum: [:open, :close]).build
|
38
36
|
|
39
37
|
# Create a client and connect to a MQTT broker
|
40
38
|
client = MQTT::Homie::Client.new(device: device, host: 'localhost')
|
data/lib/mqtt/homie.rb
CHANGED
@@ -1,27 +1,29 @@
|
|
1
|
-
require "mqtt"
|
2
|
-
require "mqtt/homie/version"
|
3
|
-
|
4
|
-
module MQTT
|
5
|
-
module Homie
|
6
|
-
class Error < StandardError; end
|
7
|
-
|
8
|
-
class << self
|
9
|
-
attr_accessor :logger
|
10
|
-
|
11
|
-
def debug(message)
|
12
|
-
logger.debug(message) if logger
|
13
|
-
end
|
14
|
-
|
15
|
-
def device_builder(options = {})
|
16
|
-
MQTT::Homie::DeviceBuilder.new(options)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
require "mqtt/homie/
|
23
|
-
require "mqtt/homie/
|
24
|
-
require "mqtt/homie/
|
25
|
-
require "mqtt/homie/
|
26
|
-
require "mqtt/homie/
|
27
|
-
require "mqtt/homie/
|
1
|
+
require "mqtt"
|
2
|
+
require "mqtt/homie/version"
|
3
|
+
|
4
|
+
module MQTT
|
5
|
+
module Homie
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :logger
|
10
|
+
|
11
|
+
def debug(message)
|
12
|
+
logger.debug(message) if logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def device_builder(options = {})
|
16
|
+
MQTT::Homie::DeviceBuilder.new(options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require "mqtt/homie/homie_attribute"
|
23
|
+
require "mqtt/homie/homie_object"
|
24
|
+
require "mqtt/homie/network"
|
25
|
+
require "mqtt/homie/property"
|
26
|
+
require "mqtt/homie/node"
|
27
|
+
require "mqtt/homie/device"
|
28
|
+
require "mqtt/homie/client"
|
29
|
+
require "mqtt/homie/device_builder"
|
data/lib/mqtt/homie/client.rb
CHANGED
@@ -12,8 +12,17 @@ module MQTT
|
|
12
12
|
@device = options[:device]
|
13
13
|
@host = options[:host]
|
14
14
|
@root_topic = options[:root_topic] || DEFAULT_ROOT_TOPIC
|
15
|
+
|
15
16
|
raise "device required" unless @device
|
16
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
|
+
|
17
26
|
# observe all node properties so we can publish values when they change
|
18
27
|
@device.nodes.each do |node|
|
19
28
|
node.properties.each do |property|
|
@@ -30,12 +39,12 @@ module MQTT
|
|
30
39
|
@client.connect
|
31
40
|
|
32
41
|
publish(@device, topic)
|
33
|
-
publish_statistics
|
42
|
+
publish_statistics if @use_stats
|
34
43
|
|
35
44
|
@threads = []
|
36
45
|
|
37
46
|
# run a thread to publish statistics
|
38
|
-
@threads << Thread.new { run_statistics }
|
47
|
+
@threads << Thread.new { run_statistics } if @use_stats
|
39
48
|
|
40
49
|
# run a thread to listen for settings
|
41
50
|
@threads << Thread.new { run_set_listener }
|
@@ -106,7 +115,9 @@ module MQTT
|
|
106
115
|
def run_statistics
|
107
116
|
while !Thread.current[:done]
|
108
117
|
publish_statistics
|
109
|
-
|
118
|
+
|
119
|
+
# halve interval, if we miss a notification then we will be marked as offline
|
120
|
+
sleep @device.stats.interval / 2
|
110
121
|
end
|
111
122
|
debug("statistics thread exiting")
|
112
123
|
end
|
@@ -121,7 +132,7 @@ module MQTT
|
|
121
132
|
end
|
122
133
|
|
123
134
|
def publish_statistics
|
124
|
-
publish(@device.
|
135
|
+
publish(@device.stats, topic + "/$stats")
|
125
136
|
end
|
126
137
|
|
127
138
|
def publish_property_value(property)
|
data/lib/mqtt/homie/device.rb
CHANGED
@@ -1,31 +1,71 @@
|
|
1
1
|
require "sys/uname"
|
2
|
+
require "socket"
|
2
3
|
|
3
4
|
module MQTT
|
4
5
|
module Homie
|
5
6
|
class Device < HomieObject
|
7
|
+
class << self
|
8
|
+
include Network
|
9
|
+
end
|
10
|
+
|
6
11
|
HOMIE_VERSION = "3.0.1"
|
7
12
|
DEFAULT_STAT_REFRESH = 60 # seconds
|
8
|
-
DEFAULT_IMPLEMENTATION = "mqtt-homie"
|
13
|
+
DEFAULT_IMPLEMENTATION = "mqtt-homie-#{VERSION}"
|
14
|
+
|
15
|
+
class Firmware < HomieObject
|
16
|
+
class << self
|
17
|
+
def default_fw_name
|
18
|
+
uname.sysname rescue uname.caption rescue "Unknown"
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_fw_version
|
22
|
+
uname.release rescue uname.build_number rescue "Unknown"
|
23
|
+
end
|
24
|
+
|
25
|
+
def uname
|
26
|
+
@uname ||= Sys::Uname.uname
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
homie_attr :name, default: default_fw_name, required: true
|
31
|
+
homie_attr :version, default: default_fw_version, required: true
|
32
|
+
end
|
33
|
+
|
34
|
+
# statistics should be sent every interval seconds
|
35
|
+
# homie/device_id/$stats
|
36
|
+
class Statistics < HomieObject
|
37
|
+
homie_attr :interval, required: true, default: 60
|
38
|
+
homie_attr :boot_time, default: lambda { |i| Time.now }, hidden: true
|
39
|
+
homie_attr :signal, datatype: Integer
|
40
|
+
homie_attr :cputemp, datatype: Integer
|
41
|
+
homie_attr :cpuload, datatype: Float
|
42
|
+
homie_attr :battery, datatype: Integer
|
43
|
+
homie_attr :freeheap, datatype: Integer
|
44
|
+
homie_attr :supply, datatype: Float
|
45
|
+
|
46
|
+
def homie_attributes
|
47
|
+
super.merge("uptime" => (Time.now - @boot_time).to_i)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :fw, :stats
|
52
|
+
attr_accessor :use_stats, :use_fw
|
9
53
|
|
10
|
-
|
11
|
-
|
54
|
+
homie_id
|
55
|
+
homie_attr :name, required: true
|
56
|
+
homie_attr :state, default: :init, required: true
|
57
|
+
homie_attr :nodes, datatype: Array, default: [], immutable: true
|
58
|
+
homie_attr :localip, default: default_localip, required: true
|
59
|
+
homie_attr :mac, default: default_mac, required: true
|
60
|
+
homie_attr :implementation, default: DEFAULT_IMPLEMENTATION
|
12
61
|
|
13
62
|
def initialize(options = {})
|
14
63
|
super(options)
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@interval = set(options, :interval, default: DEFAULT_STAT_REFRESH)
|
18
|
-
@nodes = set(options, :nodes, data_type: Array, default: [])
|
19
|
-
@state = :init
|
20
|
-
@localip = set(options, :localip, default: default_localip)
|
21
|
-
@mac = set(options, :mac, default: default_mac)
|
22
|
-
@implementation = set(options, :implementation, default: DEFAULT_IMPLEMENTATION)
|
23
|
-
@fw_name = set(options, :fw_name, default: default_fw_name)
|
24
|
-
@fw_version = set(options, :fw_version, default: default_fw_version)
|
25
|
-
end
|
64
|
+
@stats = Statistics.new(options)
|
65
|
+
@fw = Firmware.new(subhash(options, "fw_"))
|
26
66
|
|
27
|
-
|
28
|
-
@
|
67
|
+
@use_stats = options.include?(:use_stats) ? options[:use_stats] : true
|
68
|
+
@use_fw = options.include?(:use_fw) ? options[:use_fw] : true
|
29
69
|
end
|
30
70
|
|
31
71
|
def node(id)
|
@@ -35,17 +75,15 @@ module MQTT
|
|
35
75
|
# device attributes must be sent when connection to broker is established or re-established
|
36
76
|
# homie/device_id/
|
37
77
|
def homie_attributes
|
38
|
-
data = {
|
78
|
+
data = super.merge({
|
39
79
|
"$homie" => HOMIE_VERSION,
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
"$fw/name" => @
|
44
|
-
"$fw/version" => @
|
45
|
-
|
46
|
-
|
47
|
-
"$state" => @state.to_s,
|
48
|
-
}
|
80
|
+
})
|
81
|
+
|
82
|
+
data.merge!({
|
83
|
+
"$fw/name" => @fw.name,
|
84
|
+
"$fw/version" => @fw.version,
|
85
|
+
}) if @use_fw
|
86
|
+
|
49
87
|
@nodes.each do |node|
|
50
88
|
node.homie_attributes.each do |k, v|
|
51
89
|
data[node.topic + "/" + k] = v
|
@@ -54,43 +92,16 @@ module MQTT
|
|
54
92
|
data
|
55
93
|
end
|
56
94
|
|
57
|
-
|
58
|
-
# homie/device_id/$stats
|
59
|
-
def statistics
|
60
|
-
{
|
61
|
-
"uptime" => (Time.now - @start_time).to_i,
|
62
|
-
#"signal" => 100, # TODO wifi signal strength
|
63
|
-
#"cputemp" => 0,
|
64
|
-
#"cpuload" => stats.load_average.one_minute,
|
65
|
-
#"battery" => 100,
|
66
|
-
#"freeheap" => stats.memory.free,
|
67
|
-
#"supply" => 5,
|
68
|
-
"interval" => @interval * 2,
|
69
|
-
}
|
70
|
-
end
|
71
|
-
|
72
|
-
def update(time, node)
|
73
|
-
# node value updated
|
74
|
-
end
|
75
|
-
|
76
|
-
def default_localip
|
77
|
-
nil # TODO
|
78
|
-
end
|
79
|
-
|
80
|
-
def default_mac
|
81
|
-
nil # TODO
|
82
|
-
end
|
83
|
-
|
84
|
-
def default_fw_name
|
85
|
-
uname.sysname rescue uname.caption rescue "Unknown"
|
86
|
-
end
|
87
|
-
|
88
|
-
def default_fw_version
|
89
|
-
uname.release rescue uname.build_number rescue "Unknown"
|
90
|
-
end
|
95
|
+
private
|
91
96
|
|
92
|
-
def
|
93
|
-
|
97
|
+
def subhash(data, prefix)
|
98
|
+
result = {}
|
99
|
+
data.each do |key, value|
|
100
|
+
next unless key.to_s.start_with?(prefix)
|
101
|
+
key = key.to_s.sub(/^#{prefix}/, "")
|
102
|
+
result[key.to_sym] = value
|
103
|
+
end
|
104
|
+
result
|
94
105
|
end
|
95
106
|
end
|
96
107
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module MQTT
|
2
|
+
module Homie
|
3
|
+
module HomieAttribute
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def homie_attr(name, options = {})
|
11
|
+
# define accessors
|
12
|
+
attr_reader name
|
13
|
+
|
14
|
+
unless options[:immutable]
|
15
|
+
define_method("#{name}=") { |value| homie_attr_set(name, value) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# record attribute data
|
19
|
+
@homie_attribute_list ||= []
|
20
|
+
@homie_attribute_list << [name.to_sym, options]
|
21
|
+
end
|
22
|
+
|
23
|
+
def homie_id
|
24
|
+
homie_attr :id, required: true, validate: lambda { |i| valid_id?(i) }, immutable: true, hidden: true
|
25
|
+
end
|
26
|
+
|
27
|
+
def homie_has_id?
|
28
|
+
!!homie_attr_list.detect { |i| i[0] == :id }
|
29
|
+
end
|
30
|
+
|
31
|
+
def homie_attr_list
|
32
|
+
@homie_attribute_list || []
|
33
|
+
end
|
34
|
+
|
35
|
+
def homie_attr_options(name)
|
36
|
+
data = homie_attr_list.find { |i| i[0] == name } || []
|
37
|
+
data[1] || {}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module InstanceMethods
|
42
|
+
|
43
|
+
# initialize all homie attributes from the given hash
|
44
|
+
def homie_attr_init(data = {})
|
45
|
+
self.class.homie_attr_list.each do |name, options|
|
46
|
+
#puts "name: #{name}, default: #{options[:default]}, options: #{options.inspect}"
|
47
|
+
value = data.include?(name) ? data[name] : options[:default]
|
48
|
+
value = value.call(self) if value.kind_of?(Proc)
|
49
|
+
homie_attr_set(name, value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# set attribute without validation
|
54
|
+
def homie_attr_set!(name, value)
|
55
|
+
instance_variable_set("@#{name}", value)
|
56
|
+
end
|
57
|
+
|
58
|
+
# set attribute with validation
|
59
|
+
def homie_attr_set(name, value)
|
60
|
+
homie_attr_validate(name, value)
|
61
|
+
homie_attr_set!(name, value)
|
62
|
+
end
|
63
|
+
|
64
|
+
def homie_attributes
|
65
|
+
data = {}
|
66
|
+
# if this object has an id, it needs a $ attribute prefix.
|
67
|
+
# otherwise assume it is a hierarchical attribute like $stats/* or $fw/*
|
68
|
+
attrib_prefix = self.class.homie_has_id? ? "$" : ""
|
69
|
+
self.class.homie_attr_list.each do |name, options|
|
70
|
+
next if options[:hidden]
|
71
|
+
key = options[:topic] || (attrib_prefix + name.to_s)
|
72
|
+
value = instance_variable_get("@#{name}")
|
73
|
+
next if value == nil
|
74
|
+
data[key] = value.kind_of?(Array) ? value.collect { |i| i.id }.join(",") : value
|
75
|
+
end
|
76
|
+
data
|
77
|
+
end
|
78
|
+
|
79
|
+
# attribute validation
|
80
|
+
def homie_attr_validate(name, value)
|
81
|
+
options = self.class.homie_attr_options(name)
|
82
|
+
|
83
|
+
if value.nil?
|
84
|
+
required = options[:required]
|
85
|
+
required = required.call(self) if required.kind_of?(Proc)
|
86
|
+
raise "#{name} is required for #{object_type} #{@id}" if required
|
87
|
+
end
|
88
|
+
|
89
|
+
datatype = options[:datatype]
|
90
|
+
if datatype && !value.nil? && !datatype_match?(datatype, value)
|
91
|
+
raise "expected #{name} to be a #{datatype} for #{object_type} #{@id}"
|
92
|
+
end
|
93
|
+
|
94
|
+
enum = options[:enum]
|
95
|
+
if enum.kind_of?(Array) && !value.nil? && !enum.include?(value.to_sym)
|
96
|
+
raise "expected #{name} (#{value}) to be one of #{enum.join(",")}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def object_type
|
103
|
+
self.class.name.split("::").last
|
104
|
+
end
|
105
|
+
|
106
|
+
def valid_id?(id)
|
107
|
+
id && id.kind_of?(String) && id.match(/^[-a-z0-9]+$/) && !id.start_with?("-")
|
108
|
+
end
|
109
|
+
|
110
|
+
def datatype_match?(datatype, value)
|
111
|
+
return value.kind_of?(datatype) if datatype.kind_of?(Class)
|
112
|
+
case datatype
|
113
|
+
when :boolean
|
114
|
+
return value == true || value == false
|
115
|
+
else
|
116
|
+
raise "unhandled datatype '#{datatype}'"
|
117
|
+
end
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
#@settable = !!set(options, :settable, default: false)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -3,37 +3,15 @@ require "observer"
|
|
3
3
|
module MQTT
|
4
4
|
module Homie
|
5
5
|
class HomieObject
|
6
|
-
|
6
|
+
include HomieAttribute
|
7
7
|
|
8
8
|
def initialize(options = {})
|
9
|
-
|
10
|
-
raise "invalid id" unless valid_id?
|
9
|
+
homie_attr_init(options)
|
11
10
|
end
|
12
11
|
|
13
12
|
def topic
|
14
13
|
@id
|
15
14
|
end
|
16
|
-
|
17
|
-
def homie_attributes
|
18
|
-
data = {}
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def valid_id?
|
24
|
-
@id && @id.kind_of?(String) && @id.match(/^[-a-z0-9]+$/) && !@id.start_with?("-")
|
25
|
-
end
|
26
|
-
|
27
|
-
def set(options = {}, name, default: nil, required: false, enum: nil, data_type: nil)
|
28
|
-
value = options.include?(name) ? options[name] : default
|
29
|
-
raise "#{name} is required for #{object_type} #{@id}" if required && value.nil?
|
30
|
-
raise "expected #{name} to be a #{data_type} for #{object_type} #{@id}" if data_type && !value.kind_of?(data_type)
|
31
|
-
value
|
32
|
-
end
|
33
|
-
|
34
|
-
def object_type
|
35
|
-
self.class.name.split("::").last
|
36
|
-
end
|
37
15
|
end
|
38
16
|
end
|
39
17
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "macaddr"
|
2
|
+
|
3
|
+
module MQTT
|
4
|
+
module Homie
|
5
|
+
module Network
|
6
|
+
def default_localip
|
7
|
+
addr = default_interface[:addresses][0] if default_interface
|
8
|
+
addr ? addr.ip_address : nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_mac
|
12
|
+
(default_interface ? default_interface[:hwaddr] : nil) || Mac.addr
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_interface
|
16
|
+
@default_interface ||= interfaces.values.find { |i| i[:default] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def interfaces
|
20
|
+
@interfaces ||= begin
|
21
|
+
interfaces = {}
|
22
|
+
found = false
|
23
|
+
Socket.getifaddrs.each do |ifaddr|
|
24
|
+
ifname = ifaddr.name
|
25
|
+
data = interfaces[ifname] ||= { addresses: [] }
|
26
|
+
next unless addr = ifaddr.addr
|
27
|
+
data[:addresses].push addr if (addr.ipv4? || addr.ipv6?) && usable_address?(addr)
|
28
|
+
data[:hwaddr] = $1 if addr.inspect.match(/hwaddr=([0-9a-fA-F:]+)/) # doesn't work on windows
|
29
|
+
data[:default] = true unless found
|
30
|
+
data[:name] = ifname
|
31
|
+
found = true
|
32
|
+
end
|
33
|
+
interfaces
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def usable_address?(addr)
|
38
|
+
!(addr.ipv4_loopback? || addr.ipv6_loopback? || addr.ipv4_multicast? || addr.ipv6_linklocal?)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/mqtt/homie/node.rb
CHANGED
@@ -1,25 +1,17 @@
|
|
1
1
|
module MQTT
|
2
2
|
module Homie
|
3
3
|
class Node < HomieObject
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
@name = set(options, :name, required: true)
|
9
|
-
@type = set(options, :type, required: true)
|
10
|
-
@properties = set(options, :properties, required: true, data_type: Array)
|
11
|
-
end
|
4
|
+
homie_id
|
5
|
+
homie_attr :name, required: true
|
6
|
+
homie_attr :type, required: true
|
7
|
+
homie_attr :properties, datatype: Array, required: true, immutable: true
|
12
8
|
|
13
9
|
def property(id)
|
14
10
|
@properties.find { |i| i.id == id }
|
15
11
|
end
|
16
12
|
|
17
13
|
def homie_attributes
|
18
|
-
data =
|
19
|
-
"$name" => @name,
|
20
|
-
"$type" => @type,
|
21
|
-
"$properties" => @properties.collect { |i| i.id }.join(","),
|
22
|
-
}
|
14
|
+
data = super
|
23
15
|
|
24
16
|
@properties.each do |p|
|
25
17
|
p.homie_attributes.each do |k, v|
|
data/lib/mqtt/homie/property.rb
CHANGED
@@ -7,31 +7,32 @@ module MQTT
|
|
7
7
|
|
8
8
|
DATA_TYPES = [:integer, :float, :boolean, :string, :enum, :color]
|
9
9
|
|
10
|
-
|
10
|
+
homie_id
|
11
|
+
homie_attr :name, default: ""
|
12
|
+
homie_attr :settable, default: false, datatype: :boolean
|
13
|
+
homie_attr :retained, default: true, datatype: :boolean
|
14
|
+
homie_attr :datatype, default: :string, enum: DATA_TYPES, datatype: Symbol
|
15
|
+
homie_attr :unit, default: ""
|
16
|
+
homie_attr :format, required: lambda { |i| [:enum, :color].include?(i.datatype) }
|
17
|
+
|
11
18
|
attr_reader :value
|
12
19
|
|
13
20
|
def initialize(options = {})
|
14
|
-
super(options)
|
15
|
-
|
16
21
|
options = options.dup
|
17
22
|
|
18
23
|
# enum shortcut
|
19
24
|
if enum = options.delete(:enum)
|
20
25
|
options[:datatype] = :enum
|
21
|
-
options[:format] = enum.collect { |i| i.to_s }.join(
|
26
|
+
options[:format] = enum.collect { |i| i.to_s }.join(",")
|
22
27
|
end
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
@retained = !!set(options, :retained, default: true)
|
27
|
-
@datatype = set(options, :datatype, default: :string, enum: DATA_TYPES).to_sym
|
28
|
-
@unit = set(options, :unit, default: "")
|
29
|
-
@format = set(options, :format, required: [:enum, :color].include?(@datatype))
|
29
|
+
super(options)
|
30
|
+
|
30
31
|
@value = options[:value]
|
31
32
|
end
|
32
33
|
|
33
34
|
def value=(value)
|
34
|
-
# TODO: check value conforms to expected datatype and format
|
35
|
+
# TODO: check value conforms to expected datatype and format?
|
35
36
|
if value != @value
|
36
37
|
@value = value
|
37
38
|
changed
|
@@ -42,18 +43,6 @@ module MQTT
|
|
42
43
|
def settable?
|
43
44
|
@settable
|
44
45
|
end
|
45
|
-
|
46
|
-
def homie_attributes
|
47
|
-
data = {
|
48
|
-
"$name" => @name,
|
49
|
-
"$settable" => @settable,
|
50
|
-
"$datatype" => @datatype,
|
51
|
-
"$unit" => @unit,
|
52
|
-
"$format" => @format,
|
53
|
-
"$retained" => @retained,
|
54
|
-
}
|
55
|
-
data
|
56
|
-
end
|
57
46
|
end
|
58
47
|
end
|
59
48
|
end
|
data/lib/mqtt/homie/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
module MQTT
|
2
|
-
module Homie
|
3
|
-
VERSION = "0.1.
|
4
|
-
end
|
5
|
-
end
|
1
|
+
module MQTT
|
2
|
+
module Homie
|
3
|
+
VERSION = "0.1.1"
|
4
|
+
end
|
5
|
+
end
|
data/mqtt-homie.gemspec
CHANGED
@@ -1,36 +1,37 @@
|
|
1
|
-
lib = File.expand_path("lib", __dir__)
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require "mqtt/homie/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "mqtt-homie"
|
7
|
-
spec.version = MQTT::Homie::VERSION
|
8
|
-
spec.authors = ["Andrew Williams"]
|
9
|
-
spec.email = ["sobakasu@gmail.com"]
|
10
|
-
|
11
|
-
spec.summary = %q{A ruby interface for creating a device conforming to the MQTT Homie convention.}
|
12
|
-
spec.homepage = "https://github.com/sobakasu/mqtt-homie"
|
13
|
-
spec.license = "MIT"
|
14
|
-
|
15
|
-
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
-
|
17
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
-
spec.metadata["source_code_uri"] = "https://github.com/sobakasu/mqtt-homie"
|
19
|
-
spec.metadata["changelog_uri"] = "https://github.com/sobakasu/mqtt-homie/CHANGELOG"
|
20
|
-
|
21
|
-
# Specify which files should be added to the gem when it is released.
|
22
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
-
end
|
26
|
-
spec.bindir = "exe"
|
27
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = ["lib"]
|
29
|
-
|
30
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
31
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
-
|
34
|
-
spec.add_dependency "mqtt", "~> 0.5"
|
35
|
-
spec.add_dependency "sys-uname", "~> 1.0"
|
36
|
-
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "mqtt/homie/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "mqtt-homie"
|
7
|
+
spec.version = MQTT::Homie::VERSION
|
8
|
+
spec.authors = ["Andrew Williams"]
|
9
|
+
spec.email = ["sobakasu@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{A ruby interface for creating a device conforming to the MQTT Homie convention.}
|
12
|
+
spec.homepage = "https://github.com/sobakasu/mqtt-homie"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/sobakasu/mqtt-homie"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/sobakasu/mqtt-homie/CHANGELOG"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
33
|
+
|
34
|
+
spec.add_dependency "mqtt", "~> 0.5"
|
35
|
+
spec.add_dependency "sys-uname", "~> 1.0"
|
36
|
+
spec.add_dependency "macaddr", "~> 1.1"
|
37
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mqtt-homie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-06-
|
11
|
+
date: 2019-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: macaddr
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.1'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.1'
|
83
97
|
description:
|
84
98
|
email:
|
85
99
|
- sobakasu@gmail.com
|
@@ -102,7 +116,9 @@ files:
|
|
102
116
|
- lib/mqtt/homie/client.rb
|
103
117
|
- lib/mqtt/homie/device.rb
|
104
118
|
- lib/mqtt/homie/device_builder.rb
|
119
|
+
- lib/mqtt/homie/homie_attribute.rb
|
105
120
|
- lib/mqtt/homie/homie_object.rb
|
121
|
+
- lib/mqtt/homie/network.rb
|
106
122
|
- lib/mqtt/homie/node.rb
|
107
123
|
- lib/mqtt/homie/property.rb
|
108
124
|
- lib/mqtt/homie/version.rb
|