lifx-lan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|