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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +71 -13
  6. data/Rakefile +12 -0
  7. data/bin/lifx-console +15 -0
  8. data/bin/lifx-snoop +50 -0
  9. data/examples/auto-off/Gemfile +3 -0
  10. data/examples/auto-off/auto-off.rb +35 -0
  11. data/examples/identify/Gemfile +3 -0
  12. data/examples/identify/identify.rb +70 -0
  13. data/examples/travis-build-light/Gemfile +4 -0
  14. data/examples/travis-build-light/build-light.rb +57 -0
  15. data/lib/bindata_ext/bool.rb +29 -0
  16. data/lib/bindata_ext/record.rb +11 -0
  17. data/lib/lifx/client.rb +136 -0
  18. data/lib/lifx/color.rb +190 -0
  19. data/lib/lifx/config.rb +12 -0
  20. data/lib/lifx/firmware.rb +55 -0
  21. data/lib/lifx/gateway_connection.rb +177 -0
  22. data/lib/lifx/light.rb +406 -0
  23. data/lib/lifx/light_collection.rb +105 -0
  24. data/lib/lifx/light_target.rb +189 -0
  25. data/lib/lifx/logging.rb +11 -0
  26. data/lib/lifx/message.rb +166 -0
  27. data/lib/lifx/network_context.rb +200 -0
  28. data/lib/lifx/observable.rb +46 -0
  29. data/lib/lifx/protocol/address.rb +21 -0
  30. data/lib/lifx/protocol/device.rb +225 -0
  31. data/lib/lifx/protocol/header.rb +24 -0
  32. data/lib/lifx/protocol/light.rb +110 -0
  33. data/lib/lifx/protocol/message.rb +17 -0
  34. data/lib/lifx/protocol/metadata.rb +21 -0
  35. data/lib/lifx/protocol/payload.rb +7 -0
  36. data/lib/lifx/protocol/sensor.rb +29 -0
  37. data/lib/lifx/protocol/type.rb +134 -0
  38. data/lib/lifx/protocol/wan.rb +50 -0
  39. data/lib/lifx/protocol/wifi.rb +76 -0
  40. data/lib/lifx/protocol_path.rb +84 -0
  41. data/lib/lifx/routing_manager.rb +110 -0
  42. data/lib/lifx/routing_table.rb +33 -0
  43. data/lib/lifx/seen.rb +15 -0
  44. data/lib/lifx/site.rb +89 -0
  45. data/lib/lifx/tag_manager.rb +105 -0
  46. data/lib/lifx/tag_table.rb +47 -0
  47. data/lib/lifx/target.rb +23 -0
  48. data/lib/lifx/timers.rb +18 -0
  49. data/lib/lifx/transport/tcp.rb +81 -0
  50. data/lib/lifx/transport/udp.rb +67 -0
  51. data/lib/lifx/transport.rb +41 -0
  52. data/lib/lifx/transport_manager/lan.rb +140 -0
  53. data/lib/lifx/transport_manager.rb +34 -0
  54. data/lib/lifx/utilities.rb +33 -0
  55. data/lib/lifx/version.rb +1 -1
  56. data/lib/lifx.rb +15 -1
  57. data/lifx.gemspec +11 -7
  58. data/spec/color_spec.rb +45 -0
  59. data/spec/gateway_connection_spec.rb +32 -0
  60. data/spec/integration/client_spec.rb +40 -0
  61. data/spec/integration/light_spec.rb +43 -0
  62. data/spec/integration/tags_spec.rb +31 -0
  63. data/spec/message_spec.rb +163 -0
  64. data/spec/protocol_path_spec.rb +109 -0
  65. data/spec/routing_manager_spec.rb +22 -0
  66. data/spec/spec_helper.rb +52 -0
  67. data/spec/transport/udp_spec.rb +38 -0
  68. data/spec/transport_spec.rb +14 -0
  69. 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,7 @@
1
+ module LIFX
2
+ module Protocol
3
+ # @api private
4
+ class Payload < BinData::Record
5
+ end
6
+ end
7
+ 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
data/lib/lifx/seen.rb ADDED
@@ -0,0 +1,15 @@
1
+ module LIFX
2
+ module Seen
3
+ def last_seen
4
+ @last_seen
5
+ end
6
+
7
+ def seconds_since_seen
8
+ Time.now - (last_seen || Time.at(0))
9
+ end
10
+
11
+ def seen!
12
+ @last_seen = Time.now
13
+ end
14
+ end
15
+ end