lifx 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +1 -1
- data/README.md +71 -13
- data/Rakefile +12 -0
- data/bin/lifx-console +15 -0
- data/bin/lifx-snoop +50 -0
- data/examples/auto-off/Gemfile +3 -0
- data/examples/auto-off/auto-off.rb +35 -0
- data/examples/identify/Gemfile +3 -0
- data/examples/identify/identify.rb +70 -0
- data/examples/travis-build-light/Gemfile +4 -0
- data/examples/travis-build-light/build-light.rb +57 -0
- data/lib/bindata_ext/bool.rb +29 -0
- data/lib/bindata_ext/record.rb +11 -0
- data/lib/lifx/client.rb +136 -0
- data/lib/lifx/color.rb +190 -0
- data/lib/lifx/config.rb +12 -0
- data/lib/lifx/firmware.rb +55 -0
- data/lib/lifx/gateway_connection.rb +177 -0
- data/lib/lifx/light.rb +406 -0
- data/lib/lifx/light_collection.rb +105 -0
- data/lib/lifx/light_target.rb +189 -0
- data/lib/lifx/logging.rb +11 -0
- data/lib/lifx/message.rb +166 -0
- data/lib/lifx/network_context.rb +200 -0
- data/lib/lifx/observable.rb +46 -0
- data/lib/lifx/protocol/address.rb +21 -0
- data/lib/lifx/protocol/device.rb +225 -0
- data/lib/lifx/protocol/header.rb +24 -0
- data/lib/lifx/protocol/light.rb +110 -0
- data/lib/lifx/protocol/message.rb +17 -0
- data/lib/lifx/protocol/metadata.rb +21 -0
- data/lib/lifx/protocol/payload.rb +7 -0
- data/lib/lifx/protocol/sensor.rb +29 -0
- data/lib/lifx/protocol/type.rb +134 -0
- data/lib/lifx/protocol/wan.rb +50 -0
- data/lib/lifx/protocol/wifi.rb +76 -0
- data/lib/lifx/protocol_path.rb +84 -0
- data/lib/lifx/routing_manager.rb +110 -0
- data/lib/lifx/routing_table.rb +33 -0
- data/lib/lifx/seen.rb +15 -0
- data/lib/lifx/site.rb +89 -0
- data/lib/lifx/tag_manager.rb +105 -0
- data/lib/lifx/tag_table.rb +47 -0
- data/lib/lifx/target.rb +23 -0
- data/lib/lifx/timers.rb +18 -0
- data/lib/lifx/transport/tcp.rb +81 -0
- data/lib/lifx/transport/udp.rb +67 -0
- data/lib/lifx/transport.rb +41 -0
- data/lib/lifx/transport_manager/lan.rb +140 -0
- data/lib/lifx/transport_manager.rb +34 -0
- data/lib/lifx/utilities.rb +33 -0
- data/lib/lifx/version.rb +1 -1
- data/lib/lifx.rb +15 -1
- data/lifx.gemspec +11 -7
- data/spec/color_spec.rb +45 -0
- data/spec/gateway_connection_spec.rb +32 -0
- data/spec/integration/client_spec.rb +40 -0
- data/spec/integration/light_spec.rb +43 -0
- data/spec/integration/tags_spec.rb +31 -0
- data/spec/message_spec.rb +163 -0
- data/spec/protocol_path_spec.rb +109 -0
- data/spec/routing_manager_spec.rb +22 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/transport/udp_spec.rb +38 -0
- data/spec/transport_spec.rb +14 -0
- metadata +143 -26
@@ -0,0 +1,110 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
module Light
|
5
|
+
module Waveform
|
6
|
+
SAW = 0
|
7
|
+
SINE = 1
|
8
|
+
HALF_SINE = 2
|
9
|
+
TRIANGLE = 3
|
10
|
+
PULSE = 4
|
11
|
+
end
|
12
|
+
|
13
|
+
class Hsbk < Payload
|
14
|
+
endian :little
|
15
|
+
|
16
|
+
uint16 :hue # 0..65_535 scaled to 0° - 360°.
|
17
|
+
uint16 :saturation # 0..65_535 scaled to 0% - 100%.
|
18
|
+
uint16 :brightness # 0..65_535 scaled to 0% - 100%.
|
19
|
+
uint16 :kelvin # Explicit 2_400..10_000.
|
20
|
+
end
|
21
|
+
|
22
|
+
class Get < Payload
|
23
|
+
endian :little
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class Set < Payload
|
28
|
+
endian :little
|
29
|
+
|
30
|
+
uint8 :stream # 0 is no stream.
|
31
|
+
hsbk :color
|
32
|
+
uint32 :duration # Milliseconds.
|
33
|
+
end
|
34
|
+
|
35
|
+
class SetWaveform < Payload
|
36
|
+
endian :little
|
37
|
+
|
38
|
+
uint8 :stream # 0 is no stream.
|
39
|
+
bool :transient
|
40
|
+
hsbk :color
|
41
|
+
uint32 :period # Milliseconds per cycle.
|
42
|
+
float :cycles
|
43
|
+
int16 :duty_cycle
|
44
|
+
uint8 :waveform
|
45
|
+
end
|
46
|
+
|
47
|
+
class SetDimAbsolute < Payload
|
48
|
+
endian :little
|
49
|
+
|
50
|
+
int16 :brightness # 0 is no change.
|
51
|
+
uint32 :duration # Milliseconds.
|
52
|
+
end
|
53
|
+
|
54
|
+
class SetDimRelative < Payload
|
55
|
+
endian :little
|
56
|
+
|
57
|
+
int32 :brightness # 0 is no change.
|
58
|
+
uint32 :duration # Milliseconds.
|
59
|
+
end
|
60
|
+
|
61
|
+
class Rgbw < Payload
|
62
|
+
endian :little
|
63
|
+
|
64
|
+
uint16 :red
|
65
|
+
uint16 :green
|
66
|
+
uint16 :blue
|
67
|
+
uint16 :white
|
68
|
+
end
|
69
|
+
|
70
|
+
class SetRgbw < Payload
|
71
|
+
endian :little
|
72
|
+
|
73
|
+
rgbw :color
|
74
|
+
end
|
75
|
+
|
76
|
+
class State < Payload
|
77
|
+
endian :little
|
78
|
+
|
79
|
+
hsbk :color
|
80
|
+
int16 :dim
|
81
|
+
uint16 :power
|
82
|
+
string :label, length: 32, trim_padding: true
|
83
|
+
uint64 :tags
|
84
|
+
end
|
85
|
+
|
86
|
+
class GetRailVoltage < Payload
|
87
|
+
endian :little
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
class StateRailVoltage < Payload
|
92
|
+
endian :little
|
93
|
+
|
94
|
+
uint32 :voltage
|
95
|
+
end
|
96
|
+
|
97
|
+
class GetTemperature < Payload
|
98
|
+
endian :little
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class StateTemperature < Payload
|
103
|
+
endian :little
|
104
|
+
|
105
|
+
int16 :temperature # Deci-celsius. 25.45 celsius is 2545
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'lifx/protocol/header'
|
2
|
+
require 'lifx/protocol/address'
|
3
|
+
require 'lifx/protocol/metadata'
|
4
|
+
|
5
|
+
module LIFX
|
6
|
+
module Protocol
|
7
|
+
class Message < BinData::Record
|
8
|
+
endian :little
|
9
|
+
|
10
|
+
include HeaderFields
|
11
|
+
include AddressFields
|
12
|
+
include MetadataFields
|
13
|
+
|
14
|
+
rest :payload
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module LIFX
|
2
|
+
module Protocol
|
3
|
+
module MetadataFields
|
4
|
+
def MetadataFields.included(mod)
|
5
|
+
mod.instance_eval do
|
6
|
+
hide :_reserved3
|
7
|
+
|
8
|
+
uint64 :at_time
|
9
|
+
uint16 :type
|
10
|
+
uint16 :_reserved3
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Metadata < BinData::Record
|
16
|
+
endian :little
|
17
|
+
|
18
|
+
include MetadataFields
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
module Sensor
|
5
|
+
class GetAmbientLight < Payload
|
6
|
+
endian :little
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class StateAmbientLight < Payload
|
11
|
+
endian :little
|
12
|
+
|
13
|
+
float :lux
|
14
|
+
end
|
15
|
+
|
16
|
+
class GetDimmerVoltage < Payload
|
17
|
+
endian :little
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class StateDimmerVoltage < Payload
|
22
|
+
endian :little
|
23
|
+
|
24
|
+
uint32 :voltage
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
TYPE_ID_TO_CLASS = {
|
5
|
+
1 => Device::SetSite,
|
6
|
+
2 => Device::GetPanGateway,
|
7
|
+
3 => Device::StatePanGateway,
|
8
|
+
4 => Device::GetTime,
|
9
|
+
5 => Device::SetTime,
|
10
|
+
6 => Device::StateTime,
|
11
|
+
7 => Device::GetResetSwitch,
|
12
|
+
8 => Device::StateResetSwitch,
|
13
|
+
12 => Device::GetMeshInfo,
|
14
|
+
13 => Device::StateMeshInfo,
|
15
|
+
14 => Device::GetMeshFirmware,
|
16
|
+
15 => Device::StateMeshFirmware,
|
17
|
+
16 => Device::GetWifiInfo,
|
18
|
+
17 => Device::StateWifiInfo,
|
19
|
+
18 => Device::GetWifiFirmware,
|
20
|
+
19 => Device::StateWifiFirmware,
|
21
|
+
20 => Device::GetPower,
|
22
|
+
21 => Device::SetPower,
|
23
|
+
22 => Device::StatePower,
|
24
|
+
23 => Device::GetLabel,
|
25
|
+
24 => Device::SetLabel,
|
26
|
+
25 => Device::StateLabel,
|
27
|
+
26 => Device::GetTags,
|
28
|
+
27 => Device::SetTags,
|
29
|
+
28 => Device::StateTags,
|
30
|
+
29 => Device::GetTagLabels,
|
31
|
+
30 => Device::SetTagLabels,
|
32
|
+
31 => Device::StateTagLabels,
|
33
|
+
32 => Device::GetVersion,
|
34
|
+
33 => Device::StateVersion,
|
35
|
+
34 => Device::GetInfo,
|
36
|
+
35 => Device::StateInfo,
|
37
|
+
36 => Device::GetMcuRailVoltage,
|
38
|
+
37 => Device::StateMcuRailVoltage,
|
39
|
+
38 => Device::Reboot,
|
40
|
+
101 => Light::Get,
|
41
|
+
102 => Light::Set,
|
42
|
+
103 => Light::SetWaveform,
|
43
|
+
104 => Light::SetDimAbsolute,
|
44
|
+
105 => Light::SetDimRelative,
|
45
|
+
106 => Light::SetRgbw,
|
46
|
+
107 => Light::State,
|
47
|
+
108 => Light::GetRailVoltage,
|
48
|
+
109 => Light::StateRailVoltage,
|
49
|
+
110 => Light::GetTemperature,
|
50
|
+
111 => Light::StateTemperature,
|
51
|
+
201 => Wan::ConnectPlain,
|
52
|
+
202 => Wan::ConnectKey,
|
53
|
+
203 => Wan::StateConnect,
|
54
|
+
204 => Wan::Sub,
|
55
|
+
205 => Wan::Unsub,
|
56
|
+
206 => Wan::StateSub,
|
57
|
+
301 => Wifi::Get,
|
58
|
+
302 => Wifi::Set,
|
59
|
+
303 => Wifi::State,
|
60
|
+
304 => Wifi::GetAccessPoint,
|
61
|
+
305 => Wifi::SetAccessPoint,
|
62
|
+
306 => Wifi::StateAccessPoint,
|
63
|
+
401 => Sensor::GetAmbientLight,
|
64
|
+
402 => Sensor::StateAmbientLight,
|
65
|
+
403 => Sensor::GetDimmerVoltage,
|
66
|
+
404 => Sensor::StateDimmerVoltage,
|
67
|
+
}
|
68
|
+
|
69
|
+
CLASS_TO_TYPE_ID = {
|
70
|
+
Device::SetSite => 1,
|
71
|
+
Device::GetPanGateway => 2,
|
72
|
+
Device::StatePanGateway => 3,
|
73
|
+
Device::GetTime => 4,
|
74
|
+
Device::SetTime => 5,
|
75
|
+
Device::StateTime => 6,
|
76
|
+
Device::GetResetSwitch => 7,
|
77
|
+
Device::StateResetSwitch => 8,
|
78
|
+
Device::GetMeshInfo => 12,
|
79
|
+
Device::StateMeshInfo => 13,
|
80
|
+
Device::GetMeshFirmware => 14,
|
81
|
+
Device::StateMeshFirmware => 15,
|
82
|
+
Device::GetWifiInfo => 16,
|
83
|
+
Device::StateWifiInfo => 17,
|
84
|
+
Device::GetWifiFirmware => 18,
|
85
|
+
Device::StateWifiFirmware => 19,
|
86
|
+
Device::GetPower => 20,
|
87
|
+
Device::SetPower => 21,
|
88
|
+
Device::StatePower => 22,
|
89
|
+
Device::GetLabel => 23,
|
90
|
+
Device::SetLabel => 24,
|
91
|
+
Device::StateLabel => 25,
|
92
|
+
Device::GetTags => 26,
|
93
|
+
Device::SetTags => 27,
|
94
|
+
Device::StateTags => 28,
|
95
|
+
Device::GetTagLabels => 29,
|
96
|
+
Device::SetTagLabels => 30,
|
97
|
+
Device::StateTagLabels => 31,
|
98
|
+
Device::GetVersion => 32,
|
99
|
+
Device::StateVersion => 33,
|
100
|
+
Device::GetInfo => 34,
|
101
|
+
Device::StateInfo => 35,
|
102
|
+
Device::GetMcuRailVoltage => 36,
|
103
|
+
Device::StateMcuRailVoltage => 37,
|
104
|
+
Device::Reboot => 38,
|
105
|
+
Light::Get => 101,
|
106
|
+
Light::Set => 102,
|
107
|
+
Light::SetWaveform => 103,
|
108
|
+
Light::SetDimAbsolute => 104,
|
109
|
+
Light::SetDimRelative => 105,
|
110
|
+
Light::SetRgbw => 106,
|
111
|
+
Light::State => 107,
|
112
|
+
Light::GetRailVoltage => 108,
|
113
|
+
Light::StateRailVoltage => 109,
|
114
|
+
Light::GetTemperature => 110,
|
115
|
+
Light::StateTemperature => 111,
|
116
|
+
Wan::ConnectPlain => 201,
|
117
|
+
Wan::ConnectKey => 202,
|
118
|
+
Wan::StateConnect => 203,
|
119
|
+
Wan::Sub => 204,
|
120
|
+
Wan::Unsub => 205,
|
121
|
+
Wan::StateSub => 206,
|
122
|
+
Wifi::Get => 301,
|
123
|
+
Wifi::Set => 302,
|
124
|
+
Wifi::State => 303,
|
125
|
+
Wifi::GetAccessPoint => 304,
|
126
|
+
Wifi::SetAccessPoint => 305,
|
127
|
+
Wifi::StateAccessPoint => 306,
|
128
|
+
Sensor::GetAmbientLight => 401,
|
129
|
+
Sensor::StateAmbientLight => 402,
|
130
|
+
Sensor::GetDimmerVoltage => 403,
|
131
|
+
Sensor::StateDimmerVoltage => 404,
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
module Wan
|
5
|
+
class ConnectPlain < Payload
|
6
|
+
endian :little
|
7
|
+
|
8
|
+
string :user, length: 32, trim_padding: true
|
9
|
+
string :pass, length: 32, trim_padding: true
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConnectKey < Payload
|
13
|
+
endian :little
|
14
|
+
|
15
|
+
string :auth_key, length: 32
|
16
|
+
end
|
17
|
+
|
18
|
+
class StateConnect < Payload
|
19
|
+
endian :little
|
20
|
+
|
21
|
+
string :auth_key, length: 32
|
22
|
+
end
|
23
|
+
|
24
|
+
class Sub < Payload
|
25
|
+
endian :little
|
26
|
+
|
27
|
+
string :target, length: 8
|
28
|
+
string :site, length: 6
|
29
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
30
|
+
end
|
31
|
+
|
32
|
+
class Unsub < Payload
|
33
|
+
endian :little
|
34
|
+
|
35
|
+
string :target, length: 8
|
36
|
+
string :site, length: 6
|
37
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
38
|
+
end
|
39
|
+
|
40
|
+
class StateSub < Payload
|
41
|
+
endian :little
|
42
|
+
|
43
|
+
string :target, length: 8
|
44
|
+
string :site, length: 6
|
45
|
+
bool :device # 0 - Targets a device. 1 - Targets a tag.
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Generated code ahoy!
|
2
|
+
module LIFX
|
3
|
+
module Protocol
|
4
|
+
module Wifi
|
5
|
+
module Interface
|
6
|
+
SOFT_AP = 1
|
7
|
+
STATION = 2
|
8
|
+
end
|
9
|
+
|
10
|
+
module Security
|
11
|
+
UNKNOWN = 0
|
12
|
+
OPEN = 1
|
13
|
+
WEP_PSK = 2
|
14
|
+
WPA_TKIP_PSK = 3
|
15
|
+
WPA_AES_PSK = 4
|
16
|
+
WPA2_AES_PSK = 5
|
17
|
+
WPA2_TKIP_PSK = 6
|
18
|
+
WPA2_MIXED_PSK = 7
|
19
|
+
end
|
20
|
+
|
21
|
+
module Status
|
22
|
+
CONNECTING = 0
|
23
|
+
CONNECTED = 1
|
24
|
+
FAILED = 2
|
25
|
+
OFF = 3
|
26
|
+
end
|
27
|
+
|
28
|
+
class Get < Payload
|
29
|
+
endian :little
|
30
|
+
|
31
|
+
uint8 :interface
|
32
|
+
end
|
33
|
+
|
34
|
+
class Set < Payload
|
35
|
+
endian :little
|
36
|
+
|
37
|
+
uint8 :interface
|
38
|
+
bool :active
|
39
|
+
end
|
40
|
+
|
41
|
+
class State < Payload
|
42
|
+
endian :little
|
43
|
+
|
44
|
+
uint8 :interface
|
45
|
+
uint8 :status
|
46
|
+
uint32 :ipv4
|
47
|
+
string :ipv6, length: 16
|
48
|
+
end
|
49
|
+
|
50
|
+
class GetAccessPoint < Payload
|
51
|
+
endian :little
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class SetAccessPoint < Payload
|
56
|
+
endian :little
|
57
|
+
|
58
|
+
uint8 :interface
|
59
|
+
string :ssid, length: 32, trim_padding: true
|
60
|
+
string :pass, length: 64, trim_padding: true
|
61
|
+
uint8 :security
|
62
|
+
end
|
63
|
+
|
64
|
+
class StateAccessPoint < Payload
|
65
|
+
endian :little
|
66
|
+
|
67
|
+
uint8 :interface
|
68
|
+
string :ssid, length: 32, trim_padding: true
|
69
|
+
uint8 :security
|
70
|
+
int16 :strength
|
71
|
+
uint16 :channel
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'lifx/utilities'
|
2
|
+
|
3
|
+
module LIFX
|
4
|
+
class ProtocolPath
|
5
|
+
# ProtocolPath contains all the addressable information that is required
|
6
|
+
# for the protocol.
|
7
|
+
# It handles the conversion between raw binary and hex strings
|
8
|
+
# as well as raw tags_field to tags array
|
9
|
+
include Utilities
|
10
|
+
|
11
|
+
attr_accessor :raw_site, :raw_target, :tagged
|
12
|
+
|
13
|
+
def initialize(raw_site: "\x00".b * 6, raw_target: "\x00".b * 8, tagged: false,
|
14
|
+
site_id: nil, device_id: nil, tag_ids: nil)
|
15
|
+
self.raw_site = raw_site
|
16
|
+
self.raw_target = raw_target
|
17
|
+
self.tagged = tagged
|
18
|
+
|
19
|
+
self.site_id = site_id if site_id
|
20
|
+
self.device_id = device_id if device_id
|
21
|
+
self.tag_ids = tag_ids if tag_ids
|
22
|
+
end
|
23
|
+
|
24
|
+
def site_id
|
25
|
+
raw_site.unpack('H*').join
|
26
|
+
end
|
27
|
+
|
28
|
+
def site_id=(value)
|
29
|
+
self.raw_site = [value].pack('H12').b
|
30
|
+
end
|
31
|
+
|
32
|
+
def device_id
|
33
|
+
if !tagged?
|
34
|
+
raw_target[0...6].unpack('H*').join
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def device_id=(value)
|
41
|
+
self.raw_target = [value].pack('H16').b
|
42
|
+
self.tagged = false
|
43
|
+
end
|
44
|
+
|
45
|
+
def tag_ids
|
46
|
+
if tagged?
|
47
|
+
tag_ids_from_field(tags_field)
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def tag_ids=(values)
|
54
|
+
self.tags_field = values.reduce(0) do |value, tag_id|
|
55
|
+
value |= 2 ** tag_id
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def tagged?
|
60
|
+
!!tagged
|
61
|
+
end
|
62
|
+
|
63
|
+
def all_sites?
|
64
|
+
site_id == "000000000000"
|
65
|
+
end
|
66
|
+
|
67
|
+
def all_tags?
|
68
|
+
tagged? && tag_ids.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def tags_field
|
74
|
+
raw_target.unpack('Q').first
|
75
|
+
end
|
76
|
+
|
77
|
+
def tags_field=(value)
|
78
|
+
self.raw_target = [value].pack('Q').b
|
79
|
+
self.tagged = true
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'lifx/routing_table'
|
2
|
+
require 'lifx/tag_table'
|
3
|
+
require 'lifx/utilities'
|
4
|
+
|
5
|
+
module LIFX
|
6
|
+
class RoutingManager
|
7
|
+
include Utilities
|
8
|
+
# RoutingManager manages a routing table of site <-> device
|
9
|
+
# It can resolve a target to ProtocolPaths and manages the TagTable
|
10
|
+
|
11
|
+
attr_reader :context, :tag_table, :routing_table
|
12
|
+
|
13
|
+
def initialize(context:)
|
14
|
+
@context = context
|
15
|
+
@routing_table = RoutingTable.new
|
16
|
+
@tag_table = TagTable.new
|
17
|
+
@last_refresh_seen = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve_target(target)
|
21
|
+
if target.tag?
|
22
|
+
@tag_table.entries_with(label: target.tag).map do |entry|
|
23
|
+
ProtocolPath.new(site_id: entry.site_id, tag_ids: [entry.tag_id])
|
24
|
+
end
|
25
|
+
elsif target.broadcast?
|
26
|
+
if @routing_table.site_ids.empty?
|
27
|
+
[ProtocolPath.new(site_id: "\x00".b * 6, tag_ids: [])]
|
28
|
+
else
|
29
|
+
@routing_table.site_ids.map { |site_id| ProtocolPath.new(site_id: site_id, tag_ids: []) }
|
30
|
+
end
|
31
|
+
elsif target.site_id && target.device_id.nil?
|
32
|
+
[ProtocolPath.new(site_id: target.site_id, tag_ids: [])]
|
33
|
+
elsif target.site_id && target.device_id
|
34
|
+
[ProtocolPath.new(site_id: target.site_id, device_id: target.device_id)]
|
35
|
+
else
|
36
|
+
site_id = @routing_table.site_id_for_device_id(target.device_id)
|
37
|
+
if site_id
|
38
|
+
[ProtocolPath.new(site_id: site_id, device_id: target.device_id)]
|
39
|
+
else
|
40
|
+
@routing_table.site_ids.map { |site_id| ProtocolPath.new(site_id: site_id, device_id: target.device_id)}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def tags_for_device_id(device_id)
|
46
|
+
entry = @routing_table.entry_for_device_id(device_id)
|
47
|
+
entry.tag_ids.map do |tag_id|
|
48
|
+
tag = @tag_table.entry_with(site_id: entry.site_id, tag_id: tag_id)
|
49
|
+
tag && tag.label
|
50
|
+
end.compact
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_from_message(message)
|
54
|
+
if message.tagged?
|
55
|
+
case message.payload
|
56
|
+
when Protocol::Light::Get
|
57
|
+
if message.path.all_tags?
|
58
|
+
@last_refresh_seen[message.site_id] = Time.now
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
payload = message.payload
|
65
|
+
|
66
|
+
if !@routing_table.site_ids.include?(message.site_id)
|
67
|
+
# New site detected, fire refresh events
|
68
|
+
refresh_site(message.site_id)
|
69
|
+
end
|
70
|
+
case payload
|
71
|
+
when Protocol::Device::StateTagLabels
|
72
|
+
tag_ids = tag_ids_from_field(payload.tags)
|
73
|
+
if payload.label.empty?
|
74
|
+
tag_ids.each do |tag_id|
|
75
|
+
@tag_table.delete_entries_with(site_id: message.site_id, tag_id: tag_id)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@tag_table.update_table(site_id: message.site_id, tag_id: tag_ids.first, label: payload.label.to_s)
|
79
|
+
end
|
80
|
+
when Protocol::Device::StateTags, Protocol::Light::State
|
81
|
+
@routing_table.update_table(site_id: message.site_id,
|
82
|
+
device_id: message.device_id,
|
83
|
+
tag_ids: tag_ids_from_field(message.payload.tags))
|
84
|
+
end
|
85
|
+
@routing_table.update_table(site_id: message.site_id, device_id: message.device_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
MINIMUM_REFRESH_INTERVAL = 20
|
89
|
+
def refresh
|
90
|
+
@routing_table.site_ids.each do |site_id|
|
91
|
+
next if (seen = @last_refresh_seen[site_id]) && Time.now - seen < MINIMUM_REFRESH_INTERVAL
|
92
|
+
refresh_site(site_id)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def refresh_site(site_id)
|
97
|
+
get_lights(site_id)
|
98
|
+
get_tag_labels(site_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_lights(site_id)
|
102
|
+
context.send_message(target: Target.new(site_id: site_id), payload: Protocol::Light::Get.new)
|
103
|
+
end
|
104
|
+
|
105
|
+
UINT64_MAX = 2 ** 64 - 1
|
106
|
+
def get_tag_labels(site_id)
|
107
|
+
context.send_message(target: Target.new(site_id: site_id), payload: Protocol::Device::GetTagLabels.new(tags: UINT64_MAX))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module LIFX
|
2
|
+
class RoutingTable
|
3
|
+
class Entry < Struct.new(:site_id, :device_id, :tag_ids, :last_seen); end
|
4
|
+
# RoutingTable stores the device <-> site mapping
|
5
|
+
def initialize(entries: {})
|
6
|
+
@device_site_mapping = entries
|
7
|
+
end
|
8
|
+
|
9
|
+
def update_table(site_id:, device_id:, tag_ids: nil)
|
10
|
+
device_mapping = @device_site_mapping[device_id] ||= Entry.new(site_id, device_id, [])
|
11
|
+
device_mapping.site_id = site_id
|
12
|
+
device_mapping.last_seen = Time.now
|
13
|
+
device_mapping.tag_ids = tag_ids if tag_ids
|
14
|
+
end
|
15
|
+
|
16
|
+
def entry_for_device_id(device_id)
|
17
|
+
@device_site_mapping[device_id]
|
18
|
+
end
|
19
|
+
|
20
|
+
def site_id_for_device_id(device_id)
|
21
|
+
entry = entry_for_device_id(device_id)
|
22
|
+
entry ? entry.site_id : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def site_ids
|
26
|
+
entries.map(&:site_id).uniq
|
27
|
+
end
|
28
|
+
|
29
|
+
def entries
|
30
|
+
@device_site_mapping.values
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|