lifx 0.0.1 → 0.4.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 +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
|