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.
@@ -1,108 +1,108 @@
1
- require "sys/uname"
2
- require "socket"
3
-
4
- module MQTT
5
- module Homie
6
- class Device < HomieObject
7
- class << self
8
- include Network
9
- end
10
-
11
- HOMIE_VERSION = "3.0.1"
12
- DEFAULT_STAT_REFRESH = 60 # seconds
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
53
-
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
61
-
62
- def initialize(options = {})
63
- super(options)
64
- @stats = Statistics.new(options)
65
- @fw = Firmware.new(subhash(options, "fw_"))
66
-
67
- @use_stats = options.include?(:use_stats) ? options[:use_stats] : true
68
- @use_fw = options.include?(:use_fw) ? options[:use_fw] : true
69
- end
70
-
71
- def node(id)
72
- @nodes.find { |i| i.id == id }
73
- end
74
-
75
- # device attributes must be sent when connection to broker is established or re-established
76
- # homie/device_id/
77
- def homie_attributes
78
- data = super.merge({
79
- "$homie" => HOMIE_VERSION,
80
- })
81
-
82
- data.merge!({
83
- "$fw/name" => @fw.name,
84
- "$fw/version" => @fw.version,
85
- }) if @use_fw
86
-
87
- @nodes.each do |node|
88
- node.homie_attributes.each do |k, v|
89
- data[node.topic + "/" + k] = v
90
- end
91
- end
92
- data
93
- end
94
-
95
- private
96
-
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
105
- end
106
- end
107
- end
108
- end
1
+ require "sys/uname"
2
+ require "socket"
3
+
4
+ module MQTT
5
+ module Homie
6
+ class Device < HomieObject
7
+ class << self
8
+ include Network
9
+ end
10
+
11
+ HOMIE_VERSION = "3.0.1"
12
+ DEFAULT_STAT_REFRESH = 60 # seconds
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
53
+
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
61
+
62
+ def initialize(options = {})
63
+ super(options)
64
+ @stats = Statistics.new(options)
65
+ @fw = Firmware.new(subhash(options, "fw_"))
66
+
67
+ @use_stats = options.include?(:use_stats) ? options[:use_stats] : true
68
+ @use_fw = options.include?(:use_fw) ? options[:use_fw] : true
69
+ end
70
+
71
+ def node(id)
72
+ @nodes.find { |i| i.id == id }
73
+ end
74
+
75
+ # device attributes must be sent when connection to broker is established or re-established
76
+ # homie/device_id/
77
+ def homie_attributes
78
+ data = super.merge({
79
+ "$homie" => HOMIE_VERSION,
80
+ })
81
+
82
+ data.merge!({
83
+ "$fw/name" => @fw.name,
84
+ "$fw/version" => @fw.version,
85
+ }) if @use_fw
86
+
87
+ @nodes.each do |node|
88
+ node.homie_attributes.each do |k, v|
89
+ data[node.topic + "/" + k] = v
90
+ end
91
+ end
92
+ data
93
+ end
94
+
95
+ private
96
+
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
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,38 +1,38 @@
1
- module MQTT
2
- module Homie
3
- class DeviceBuilder
4
- def initialize(options = {})
5
- @nodes = []
6
- @device_options = options
7
- end
8
-
9
- # create device and return it
10
- def build
11
- build_node if @node_data
12
- MQTT::Homie::Device.new(@device_options.merge(nodes: @nodes))
13
- end
14
-
15
- def node(options = {})
16
- raise "node key/value list expected" unless options.kind_of?(Hash)
17
- build_node if @node_data
18
- @node_data = options
19
- @properties = []
20
- self
21
- end
22
-
23
- def property(options = {})
24
- raise "property key/value list expected" unless options.kind_of?(Hash)
25
- @properties << MQTT::Homie::Property.new(options)
26
- self
27
- end
28
-
29
- private
30
-
31
- def build_node
32
- @nodes << MQTT::Homie::Node.new(@node_data.merge(properties: @properties))
33
- @node_data = nil
34
- @propertes = nil
35
- end
36
- end
37
- end
38
- end
1
+ module MQTT
2
+ module Homie
3
+ class DeviceBuilder
4
+ def initialize(options = {})
5
+ @nodes = []
6
+ @device_options = options
7
+ end
8
+
9
+ # create device and return it
10
+ def build
11
+ build_node if @node_data
12
+ MQTT::Homie::Device.new(@device_options.merge(nodes: @nodes))
13
+ end
14
+
15
+ def node(options = {})
16
+ raise "node key/value list expected" unless options.kind_of?(Hash)
17
+ build_node if @node_data
18
+ @node_data = options
19
+ @properties = []
20
+ self
21
+ end
22
+
23
+ def property(options = {})
24
+ raise "property key/value list expected" unless options.kind_of?(Hash)
25
+ @properties << MQTT::Homie::Property.new(options)
26
+ self
27
+ end
28
+
29
+ private
30
+
31
+ def build_node
32
+ @nodes << MQTT::Homie::Node.new(@node_data.merge(properties: @properties))
33
+ @node_data = nil
34
+ @propertes = nil
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,125 +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
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
@@ -1,17 +1,17 @@
1
- require "observer"
2
-
3
- module MQTT
4
- module Homie
5
- class HomieObject
6
- include HomieAttribute
7
-
8
- def initialize(options = {})
9
- homie_attr_init(options)
10
- end
11
-
12
- def topic
13
- @id
14
- end
15
- end
16
- end
17
- end
1
+ require "observer"
2
+
3
+ module MQTT
4
+ module Homie
5
+ class HomieObject
6
+ include HomieAttribute
7
+
8
+ def initialize(options = {})
9
+ homie_attr_init(options)
10
+ end
11
+
12
+ def topic
13
+ @id
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,42 +1,43 @@
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
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.first
17
+ end
18
+
19
+ def interfaces
20
+ @interfaces ||= begin
21
+ interfaces = {}
22
+ Socket.getifaddrs.each do |ifaddr|
23
+ ifname = ifaddr.name
24
+ next if ifname == "lo"
25
+ next unless addr = ifaddr.addr
26
+
27
+ data = interfaces[ifname] ||= { addresses: [] }
28
+ data[:name] = ifname
29
+ data[:hwaddr] = $1 if addr.inspect.match(/hwaddr=([0-9a-fA-F:]+)/) # doesn't work on windows
30
+ if (addr.ipv4? || addr.ipv6?) && usable_address?(addr)
31
+ data[:addresses].push addr
32
+ end
33
+ end
34
+ interfaces
35
+ end
36
+ end
37
+
38
+ def usable_address?(addr)
39
+ !(addr.ipv4_loopback? || addr.ipv6_loopback? || addr.ipv4_multicast? || addr.ipv6_linklocal?)
40
+ end
41
+ end
42
+ end
43
+ end