lifx-lan 0.1.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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/.yardopts +3 -0
- data/CHANGES.md +45 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +23 -0
- data/README.md +15 -0
- data/Rakefile +20 -0
- data/bin/lifx-snoop +50 -0
- data/examples/auto-off/auto-off.rb +34 -0
- data/examples/blink/blink.rb +19 -0
- data/examples/identify/identify.rb +69 -0
- data/examples/travis-build-light/build-light.rb +57 -0
- data/lib/bindata_ext/bool.rb +30 -0
- data/lib/bindata_ext/record.rb +11 -0
- data/lib/lifx-lan.rb +27 -0
- data/lib/lifx/lan/client.rb +149 -0
- data/lib/lifx/lan/color.rb +199 -0
- data/lib/lifx/lan/config.rb +17 -0
- data/lib/lifx/lan/firmware.rb +60 -0
- data/lib/lifx/lan/gateway_connection.rb +185 -0
- data/lib/lifx/lan/light.rb +440 -0
- data/lib/lifx/lan/light_collection.rb +111 -0
- data/lib/lifx/lan/light_target.rb +185 -0
- data/lib/lifx/lan/logging.rb +14 -0
- data/lib/lifx/lan/message.rb +168 -0
- data/lib/lifx/lan/network_context.rb +188 -0
- data/lib/lifx/lan/observable.rb +66 -0
- data/lib/lifx/lan/protocol/address.rb +25 -0
- data/lib/lifx/lan/protocol/device.rb +387 -0
- data/lib/lifx/lan/protocol/header.rb +24 -0
- data/lib/lifx/lan/protocol/light.rb +142 -0
- data/lib/lifx/lan/protocol/message.rb +19 -0
- data/lib/lifx/lan/protocol/metadata.rb +23 -0
- data/lib/lifx/lan/protocol/payload.rb +12 -0
- data/lib/lifx/lan/protocol/sensor.rb +31 -0
- data/lib/lifx/lan/protocol/type.rb +204 -0
- data/lib/lifx/lan/protocol/wan.rb +51 -0
- data/lib/lifx/lan/protocol/wifi.rb +102 -0
- data/lib/lifx/lan/protocol_path.rb +85 -0
- data/lib/lifx/lan/required_keyword_arguments.rb +12 -0
- data/lib/lifx/lan/routing_manager.rb +114 -0
- data/lib/lifx/lan/routing_table.rb +48 -0
- data/lib/lifx/lan/seen.rb +25 -0
- data/lib/lifx/lan/site.rb +97 -0
- data/lib/lifx/lan/tag_manager.rb +111 -0
- data/lib/lifx/lan/tag_table.rb +49 -0
- data/lib/lifx/lan/target.rb +24 -0
- data/lib/lifx/lan/thread.rb +13 -0
- data/lib/lifx/lan/timers.rb +29 -0
- data/lib/lifx/lan/transport.rb +46 -0
- data/lib/lifx/lan/transport/tcp.rb +91 -0
- data/lib/lifx/lan/transport/udp.rb +87 -0
- data/lib/lifx/lan/transport_manager.rb +43 -0
- data/lib/lifx/lan/transport_manager/lan.rb +169 -0
- data/lib/lifx/lan/utilities.rb +36 -0
- data/lib/lifx/lan/version.rb +5 -0
- data/lifx-lan.gemspec +26 -0
- data/spec/color_spec.rb +43 -0
- data/spec/gateway_connection_spec.rb +30 -0
- data/spec/integration/client_spec.rb +42 -0
- data/spec/integration/light_spec.rb +56 -0
- data/spec/integration/tags_spec.rb +42 -0
- data/spec/light_collection_spec.rb +37 -0
- data/spec/message_spec.rb +183 -0
- data/spec/protocol_path_spec.rb +109 -0
- data/spec/routing_manager_spec.rb +25 -0
- data/spec/routing_table_spec.rb +23 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/transport/udp_spec.rb +44 -0
- data/spec/transport_spec.rb +14 -0
- metadata +187 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module LIFX
|
2
|
+
module LAN
|
3
|
+
module Protocol
|
4
|
+
# @api private
|
5
|
+
module Wan
|
6
|
+
class ConnectPlain < Payload
|
7
|
+
endian :little
|
8
|
+
|
9
|
+
string :user, length: 32, trim_padding: true
|
10
|
+
string :pass, length: 32, trim_padding: true
|
11
|
+
end
|
12
|
+
|
13
|
+
class ConnectKey < Payload
|
14
|
+
endian :little
|
15
|
+
|
16
|
+
string :auth_key, length: 32
|
17
|
+
end
|
18
|
+
|
19
|
+
class StateConnect < Payload
|
20
|
+
endian :little
|
21
|
+
|
22
|
+
string :auth_key, length: 32
|
23
|
+
end
|
24
|
+
|
25
|
+
class Sub < Payload
|
26
|
+
endian :little
|
27
|
+
|
28
|
+
string :target, length: 8
|
29
|
+
string :site, length: 6
|
30
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
31
|
+
end
|
32
|
+
|
33
|
+
class Unsub < Payload
|
34
|
+
endian :little
|
35
|
+
|
36
|
+
string :target, length: 8
|
37
|
+
string :site, length: 6
|
38
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
39
|
+
end
|
40
|
+
|
41
|
+
class StateSub < Payload
|
42
|
+
endian :little
|
43
|
+
|
44
|
+
string :target, length: 8
|
45
|
+
string :site, length: 6
|
46
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module LIFX
|
2
|
+
module LAN
|
3
|
+
module Protocol
|
4
|
+
# @api private
|
5
|
+
module Wifi
|
6
|
+
module Interface
|
7
|
+
SOFT_AP = 1
|
8
|
+
STATION = 2
|
9
|
+
end
|
10
|
+
|
11
|
+
module Security
|
12
|
+
UNKNOWN = 0
|
13
|
+
OPEN = 1
|
14
|
+
WEP_PSK = 2
|
15
|
+
WPA_TKIP_PSK = 3
|
16
|
+
WPA_AES_PSK = 4
|
17
|
+
WPA2_AES_PSK = 5
|
18
|
+
WPA2_TKIP_PSK = 6
|
19
|
+
WPA2_MIXED_PSK = 7
|
20
|
+
end
|
21
|
+
|
22
|
+
module Status
|
23
|
+
CONNECTING = 0
|
24
|
+
CONNECTED = 1
|
25
|
+
FAILED = 2
|
26
|
+
OFF = 3
|
27
|
+
end
|
28
|
+
|
29
|
+
# Gets interface status
|
30
|
+
class Get < Payload
|
31
|
+
endian :little
|
32
|
+
|
33
|
+
uint8 :interface # Interface to introspect
|
34
|
+
end
|
35
|
+
|
36
|
+
# Switch interface on/off
|
37
|
+
class Set < Payload
|
38
|
+
endian :little
|
39
|
+
|
40
|
+
uint8 :interface # Interface to introspect
|
41
|
+
bool :active # Turns off interface if active is false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Describes interface state
|
45
|
+
class State < Payload
|
46
|
+
endian :little
|
47
|
+
|
48
|
+
uint8 :interface # Interface to introspect
|
49
|
+
uint8 :status # Interface status
|
50
|
+
uint32 :ipv4 # IPv4 address if interface is active
|
51
|
+
string :ipv6, length: 16 # IPv6 address if interface is active and is IPv6 addressable
|
52
|
+
end
|
53
|
+
|
54
|
+
# Scan for WiFi access points. Returns StateAccessPoints
|
55
|
+
class GetAccessPoints < Payload
|
56
|
+
endian :little
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# The AP scan results for an interface
|
61
|
+
class StateAccessPoints < Payload
|
62
|
+
endian :little
|
63
|
+
|
64
|
+
uint8 :interface # Interface to introspect
|
65
|
+
string :ssid, length: 32, trim_padding: true # SSID of Access Point
|
66
|
+
uint8 :security # Security mode
|
67
|
+
int16 :strength # Signal strength in dB
|
68
|
+
uint16 :channel # Frequency channel
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get WiFi access point information. Returns StateAccessPoint
|
72
|
+
class GetAccessPoint < Payload
|
73
|
+
endian :little
|
74
|
+
|
75
|
+
uint8 :interface # Interface to introspect
|
76
|
+
end
|
77
|
+
|
78
|
+
# Configure interface, should return StateAccessPoint
|
79
|
+
class SetAccessPoint < Payload
|
80
|
+
endian :little
|
81
|
+
|
82
|
+
uint8 :interface # Interface to introspect
|
83
|
+
string :ssid, length: 32, trim_padding: true # SSID of Access Point
|
84
|
+
string :pass, length: 64, trim_padding: true # Passphrase used to authenticate
|
85
|
+
uint8 :security # Security mode
|
86
|
+
end
|
87
|
+
|
88
|
+
# Interface configuration
|
89
|
+
class StateAccessPoint < Payload
|
90
|
+
endian :little
|
91
|
+
|
92
|
+
uint8 :interface # Interface to introspect
|
93
|
+
string :ssid, length: 32, trim_padding: true # SSID of Access Point
|
94
|
+
uint8 :security # Security mode
|
95
|
+
int16 :strength # Signal strength in dB
|
96
|
+
uint16 :channel # Frequency channel
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'lifx/lan/utilities'
|
2
|
+
|
3
|
+
module LIFX
|
4
|
+
module LAN
|
5
|
+
# @private
|
6
|
+
class ProtocolPath
|
7
|
+
# ProtocolPath contains all the addressable information that is required
|
8
|
+
# for the protocol.
|
9
|
+
# It handles the conversion between raw binary and hex strings
|
10
|
+
# as well as raw tags_field to tags array
|
11
|
+
include Utilities
|
12
|
+
|
13
|
+
attr_accessor :raw_site, :raw_target, :tagged
|
14
|
+
|
15
|
+
def initialize(raw_site: "\x00".b * 6, raw_target: "\x00".b * 8, tagged: false,
|
16
|
+
site_id: nil, device_id: nil, tag_ids: nil)
|
17
|
+
self.raw_site = raw_site
|
18
|
+
self.raw_target = raw_target
|
19
|
+
self.tagged = tagged
|
20
|
+
|
21
|
+
self.site_id = site_id if site_id
|
22
|
+
self.device_id = device_id if device_id
|
23
|
+
self.tag_ids = tag_ids if tag_ids
|
24
|
+
end
|
25
|
+
|
26
|
+
def site_id
|
27
|
+
raw_site.unpack('H*').join
|
28
|
+
end
|
29
|
+
|
30
|
+
def site_id=(value)
|
31
|
+
self.raw_site = [value].pack('H12').b
|
32
|
+
end
|
33
|
+
|
34
|
+
def device_id
|
35
|
+
if !tagged?
|
36
|
+
raw_target[0...6].unpack('H*').join
|
37
|
+
else
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def device_id=(value)
|
43
|
+
self.raw_target = [value].pack('H16').b
|
44
|
+
self.tagged = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def tag_ids
|
48
|
+
if tagged?
|
49
|
+
tag_ids_from_field(tags_field)
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def tag_ids=(values)
|
56
|
+
self.tags_field = values.reduce(0) do |value, tag_id|
|
57
|
+
value |= 2 ** tag_id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def tagged?
|
62
|
+
!!tagged
|
63
|
+
end
|
64
|
+
|
65
|
+
def all_sites?
|
66
|
+
site_id == "000000000000"
|
67
|
+
end
|
68
|
+
|
69
|
+
def all_tags?
|
70
|
+
tagged? && tag_ids.empty?
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def tags_field
|
76
|
+
raw_target.unpack('Q').first
|
77
|
+
end
|
78
|
+
|
79
|
+
def tags_field=(value)
|
80
|
+
self.raw_target = [value].pack('Q').b
|
81
|
+
self.tagged = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module LIFX
|
2
|
+
module LAN
|
3
|
+
module RequiredKeywordArguments
|
4
|
+
def required!(name)
|
5
|
+
backtrace = caller_locations(1).map { |c| c.to_s }
|
6
|
+
ex = ArgumentError.new("Missing required keyword argument '#{name}'")
|
7
|
+
ex.set_backtrace(backtrace)
|
8
|
+
raise ex
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'lifx/lan/routing_table'
|
2
|
+
require 'lifx/lan/tag_table'
|
3
|
+
require 'lifx/lan/utilities'
|
4
|
+
require 'weakref'
|
5
|
+
|
6
|
+
module LIFX
|
7
|
+
module LAN
|
8
|
+
# @private
|
9
|
+
class RoutingManager
|
10
|
+
include Logging
|
11
|
+
include Utilities
|
12
|
+
include RequiredKeywordArguments
|
13
|
+
|
14
|
+
# RoutingManager manages a routing table of site <-> device
|
15
|
+
# It can resolve a target to ProtocolPaths and manages the TagTable
|
16
|
+
|
17
|
+
attr_reader :context, :tag_table, :routing_table
|
18
|
+
|
19
|
+
STALE_ROUTING_TABLE_PURGE_INTERVAL = 60
|
20
|
+
|
21
|
+
def initialize(context: required!(:context))
|
22
|
+
@context = WeakRef.new(context)
|
23
|
+
@routing_table = RoutingTable.new
|
24
|
+
@tag_table = TagTable.new
|
25
|
+
@last_refresh_seen = {}
|
26
|
+
@context.timers.every(STALE_ROUTING_TABLE_PURGE_INTERVAL) do
|
27
|
+
routing_table.clear_stale_entries
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve_target(target)
|
32
|
+
if target.tag?
|
33
|
+
@tag_table.entries_with(label: target.tag).map do |entry|
|
34
|
+
ProtocolPath.new(site_id: entry.site_id, tag_ids: [entry.tag_id])
|
35
|
+
end
|
36
|
+
elsif target.broadcast?
|
37
|
+
[ProtocolPath.new(site_id: "\x00".b * 6, tag_ids: [])]
|
38
|
+
elsif target.site_id && target.device_id.nil?
|
39
|
+
[ProtocolPath.new(site_id: target.site_id, tag_ids: [])]
|
40
|
+
else
|
41
|
+
[ProtocolPath.new(site_id: target.site_id, device_id: target.device_id)]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def tags_for_device_id(device_id)
|
46
|
+
entry = @routing_table.entry_for_device_id(device_id)
|
47
|
+
return [] if entry.nil?
|
48
|
+
entry.tag_ids.map do |tag_id|
|
49
|
+
tag = @tag_table.entry_with(device_id: entry.device_id, tag_id: tag_id)
|
50
|
+
tag && tag.label
|
51
|
+
end.compact
|
52
|
+
end
|
53
|
+
|
54
|
+
def update_from_message(message)
|
55
|
+
return if message.site_id == NULL_SITE_ID
|
56
|
+
if message.tagged?
|
57
|
+
case message.payload
|
58
|
+
when Protocol::Light::Get
|
59
|
+
if message.path.all_tags?
|
60
|
+
@last_refresh_seen[message.device_id] = Time.now
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return
|
64
|
+
end
|
65
|
+
|
66
|
+
payload = message.payload
|
67
|
+
if !@routing_table.device_ids.include?(message.device_id)
|
68
|
+
# New device detected, fire refresh events
|
69
|
+
refresh_site(message.site_id, message.device_id)
|
70
|
+
end
|
71
|
+
case payload
|
72
|
+
when Protocol::Device::StateTagLabels
|
73
|
+
tag_ids = tag_ids_from_field(payload.tags)
|
74
|
+
if payload.label.empty?
|
75
|
+
tag_ids.each do |tag_id|
|
76
|
+
@tag_table.delete_entries_with(device_id: message.device_id, tag_id: tag_id)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
@tag_table.update_table(device_id: message.device_id, tag_id: tag_ids.first, label: payload.label.to_s.force_encoding('utf-8'))
|
80
|
+
end
|
81
|
+
when Protocol::Device::StateTags, Protocol::Light::State
|
82
|
+
@routing_table.update_table(site_id: message.site_id,
|
83
|
+
device_id: message.device_id,
|
84
|
+
tag_ids: tag_ids_from_field(message.payload.tags))
|
85
|
+
when Protocol::Device::StateService, Protocol::Device::StatePower
|
86
|
+
@routing_table.update_table(site_id: message.site_id, device_id: message.device_id)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
MINIMUM_REFRESH_INTERVAL = 1
|
91
|
+
def refresh(force: false)
|
92
|
+
@routing_table.device_ids.each do |device_id|
|
93
|
+
next if (seen = @last_refresh_seen[device_id]) && Time.now - seen < MINIMUM_REFRESH_INTERVAL && !force
|
94
|
+
site_id = @routing_table.site_id_for_device_id(device_id)
|
95
|
+
refresh_site(site_id, device_id)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def refresh_site(site_id, device_id)
|
100
|
+
get_lights(site_id, device_id)
|
101
|
+
get_tag_labels(site_id, device_id)
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_lights(site_id, device_id)
|
105
|
+
context.send_message(target: Target.new(site_id: site_id, device_id: device_id), payload: Protocol::Light::Get.new)
|
106
|
+
end
|
107
|
+
|
108
|
+
UINT64_MAX = 2 ** 64 - 1
|
109
|
+
def get_tag_labels(site_id, device_id)
|
110
|
+
context.send_message(target: Target.new(site_id: site_id, device_id: device_id), payload: Protocol::Device::GetTagLabels.new(tags: UINT64_MAX))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module LIFX
|
2
|
+
module LAN
|
3
|
+
# @private
|
4
|
+
class RoutingTable
|
5
|
+
include Logging
|
6
|
+
|
7
|
+
class Entry < Struct.new(:site_id, :device_id, :tag_ids, :last_seen); end
|
8
|
+
# RoutingTable stores the device <-> site mapping
|
9
|
+
def initialize(entries: {})
|
10
|
+
@device_site_mapping = entries
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_table(site_id: self.site_id, device_id: self.device_id, tag_ids: nil, last_seen: Time.now)
|
14
|
+
device_mapping = @device_site_mapping[device_id] ||= Entry.new(site_id, device_id, [])
|
15
|
+
device_mapping.site_id = site_id
|
16
|
+
device_mapping.last_seen = last_seen
|
17
|
+
device_mapping.tag_ids = tag_ids if tag_ids
|
18
|
+
end
|
19
|
+
|
20
|
+
def entry_for_device_id(device_id)
|
21
|
+
@device_site_mapping[device_id]
|
22
|
+
end
|
23
|
+
|
24
|
+
def site_id_for_device_id(device_id)
|
25
|
+
entry = entry_for_device_id(device_id)
|
26
|
+
entry ? entry.site_id : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def site_ids
|
30
|
+
entries.map(&:site_id).uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
def device_ids
|
34
|
+
entries.map(&:device_id).uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
def entries
|
38
|
+
@device_site_mapping.values
|
39
|
+
end
|
40
|
+
|
41
|
+
def clear_stale_entries(threshold: 60 * 5)
|
42
|
+
@device_site_mapping.reject! do |device_id, entry|
|
43
|
+
entry.last_seen < Time.now - threshold
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LIFX
|
2
|
+
module LAN
|
3
|
+
module Seen
|
4
|
+
# Returns the time when the device was last seen.
|
5
|
+
# @return [Time]
|
6
|
+
def last_seen
|
7
|
+
@last_seen
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the number of seconds since the device was last seen.
|
11
|
+
# If the device hasn't been seen yet, it will use Unix epoch as
|
12
|
+
# the time it was seen.
|
13
|
+
# @return [Float]
|
14
|
+
def seconds_since_seen
|
15
|
+
Time.now - (last_seen || Time.at(0))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Marks the device as being seen.
|
19
|
+
# @private
|
20
|
+
def seen!
|
21
|
+
@last_seen = Time.now
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'lifx/lan/seen'
|
2
|
+
require 'lifx/lan/timers'
|
3
|
+
require 'lifx/lan/observable'
|
4
|
+
require 'lifx/lan/gateway_connection'
|
5
|
+
|
6
|
+
module LIFX
|
7
|
+
module LAN
|
8
|
+
# @api private
|
9
|
+
class Site
|
10
|
+
include Seen
|
11
|
+
include Timers
|
12
|
+
include Logging
|
13
|
+
include Observable
|
14
|
+
include RequiredKeywordArguments
|
15
|
+
|
16
|
+
attr_reader :id, :gateways, :tag_manager
|
17
|
+
|
18
|
+
def initialize(id: required!(:id))
|
19
|
+
@id = id
|
20
|
+
@gateways = {}
|
21
|
+
@gateways_mutex = Mutex.new
|
22
|
+
@threads = []
|
23
|
+
@threads << initialize_timer_thread
|
24
|
+
initialize_stale_gateway_check
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(message)
|
28
|
+
message.path.site_id = id
|
29
|
+
@gateways.values.each do |gateway|
|
30
|
+
gateway.write(message)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_message(message, ip, transport)
|
35
|
+
logger.debug("<- #{self} #{transport}: #{message}")
|
36
|
+
payload = message.payload
|
37
|
+
case payload
|
38
|
+
when Protocol::Device::StateService
|
39
|
+
@gateways_mutex.synchronize do
|
40
|
+
@gateways[message.device_id] ||= GatewayConnection.new
|
41
|
+
@gateways[message.device_id].handle_message(message, ip, transport)
|
42
|
+
@gateways[message.device_id].add_observer(self, :message_received) do |**args|
|
43
|
+
notify_observers(:message_received, **args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
seen!
|
48
|
+
end
|
49
|
+
|
50
|
+
def flush(**options)
|
51
|
+
@gateways.values.map do |gateway|
|
52
|
+
Thread.start do
|
53
|
+
gateway.flush(**options)
|
54
|
+
end
|
55
|
+
end.each(&:join)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
%Q{#<LIFX::LAN::Site id=#{id}>}
|
60
|
+
end
|
61
|
+
alias_method :inspect, :to_s
|
62
|
+
|
63
|
+
def stop
|
64
|
+
@threads.each do |thread|
|
65
|
+
thread.abort
|
66
|
+
end
|
67
|
+
@gateways.values.each do |gateway|
|
68
|
+
gateway.close
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def observer_callback_definition
|
73
|
+
{
|
74
|
+
message_received: -> (message: nil, ip: nil, transport: nil) {}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
STALE_GATEWAY_CHECK_INTERVAL = 10
|
82
|
+
def initialize_stale_gateway_check
|
83
|
+
timers.every(STALE_GATEWAY_CHECK_INTERVAL) do
|
84
|
+
@gateways_mutex.synchronize do
|
85
|
+
stale_gateways = @gateways.select do |k, v|
|
86
|
+
!v.connected?
|
87
|
+
end
|
88
|
+
stale_gateways.each do |id, _|
|
89
|
+
logger.info("#{self}: Dropping stale gateway id #{id}")
|
90
|
+
@gateways.delete(id)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|