lifx 0.0.1 → 0.4.0

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