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
data/lib/lifx.rb CHANGED
@@ -1,5 +1,19 @@
1
1
  require "lifx/version"
2
+ require "bindata"
3
+ require "bindata_ext/bool"
4
+ require "bindata_ext/record"
5
+ require "lifx/utilities"
6
+ require "lifx/logging"
7
+
8
+ require "lifx/protocol/payload"
9
+ %w(device light sensor wan wifi message).each { |f| require "lifx/protocol/#{f}" }
10
+ require "lifx/protocol/type"
11
+ require "lifx/message"
12
+ require "lifx/transport"
13
+
14
+ require "lifx/config"
15
+ require "lifx/client"
2
16
 
3
17
  module LIFX
4
- # Your code goes here...
18
+
5
19
  end
data/lifx.gemspec CHANGED
@@ -8,18 +8,22 @@ Gem::Specification.new do |spec|
8
8
  spec.version = LIFX::VERSION
9
9
  spec.authors = ["Jack Chen (chendo)"]
10
10
  spec.email = ["chendo@lifx.co"]
11
- spec.description = %q{Ruby client for LIFX}
12
- spec.summary = %q{Ruby client for LIFX}
13
- spec.homepage = ""
11
+ spec.description = %q{A Ruby gem that allows easy interaction with LIFX devices.}
12
+ spec.summary = %q{A Ruby gem that allows easy interaction with LIFX devices. Handles discovery, rate limiting, tags, gateway connections and provides an object-based API for interacting with LIFX devices. }
13
+ spec.homepage = "https://github.com/LIFX/lifx-gem"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.files = `git ls-files`.split($/)
16
+ spec.files = `git ls-files`.split($/).reject { |f| f =~ /^script\// }
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = ">= 2.1"
20
21
 
21
- spec.add_dependency 'bindata'
22
+ spec.add_dependency "bindata", "~> 2.0"
23
+ spec.add_dependency "yell", "~> 2.0"
24
+ spec.add_dependency "timers", "~> 2.0"
25
+ spec.add_dependency "configatron", "~> 3.0"
22
26
  spec.add_development_dependency "bundler", "~> 1.3"
23
- spec.add_development_dependency "rake"
24
- spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "rake", '~> 10.1'
28
+ spec.add_development_dependency "rspec", "~> 2.14"
25
29
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe Color do
5
+ let(:default_kelvin) { 3500 }
6
+ describe '.rgb' do
7
+ context 'translating from RGB' do
8
+ it 'translates red correctly' do
9
+ c = Color.rgb(255, 0, 0)
10
+ c.to_a.should == [0, 1, 1, default_kelvin]
11
+ end
12
+
13
+ it 'translates yellow correctly' do
14
+ c = Color.rgb(255, 255, 0)
15
+ c.to_a.should == [60, 1, 1, default_kelvin]
16
+ end
17
+
18
+ it 'translates green correctly' do
19
+ c = Color.rgb(0, 255, 0)
20
+ c.to_a.should == [120, 1, 1, default_kelvin]
21
+ end
22
+
23
+ it 'translates cyan correctly' do
24
+ c = Color.rgb(0, 255, 255)
25
+ c.to_a.should == [180, 1, 1, default_kelvin]
26
+ end
27
+
28
+ it 'translates blue correctly' do
29
+ c = Color.rgb(0, 0, 255)
30
+ c.to_a.should == [240, 1, 1, default_kelvin]
31
+ end
32
+
33
+ it 'translates white correctly' do
34
+ c = Color.rgb(255, 255, 255)
35
+ c.to_a.should == [0, 0, 1, default_kelvin]
36
+ end
37
+
38
+ it 'translates black correctly' do
39
+ c = Color.rgb(0, 0, 0)
40
+ c.to_a.should == [0, 0, 0, default_kelvin]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe GatewayConnection do
5
+ subject do
6
+ GatewayConnection.new
7
+ end
8
+
9
+ let(:message) { double(Message).tap { |m| m.stub(:is_a?).and_return(true); m.stub(:pack) } }
10
+ let(:ip) { '127.0.0.1' }
11
+ let(:port) { 35003 }
12
+
13
+ after do
14
+ subject.close
15
+ end
16
+
17
+ context 'write queue resiliency' do
18
+ it 'does not send if there is no available connection' do
19
+ expect(subject).to_not receive(:actually_write)
20
+ subject.write(message)
21
+ expect { subject.flush(timeout: 0.5) }.to raise_error(Timeout::Error)
22
+ end
23
+
24
+ it 'pushes message back into queue if unable to write' do
25
+ subject.connect_udp(ip, port)
26
+ expect(subject).to receive(:actually_write).and_return(false, true)
27
+ subject.write(message)
28
+ subject.flush
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe Client, integration: true do
5
+ describe '#sync' do
6
+ it 'schedules sending all messages to be executed at the same time' do
7
+
8
+ lifx.discover! do
9
+ lights.count >= 3
10
+ end
11
+
12
+ if lights.count < 3
13
+ fail "This test requires 3 or more lights tagged under Test"
14
+ end
15
+
16
+ white = LIFX::Color.white(brightness: 0.5)
17
+ lights.set_color(white, duration: 0)
18
+ sleep 1
19
+
20
+ udp = Transport::UDP.new('0.0.0.0', 56750)
21
+ msgs = []
22
+ udp.add_observer(self) do |message:, ip:, transport:|
23
+ msgs << message if message.payload.is_a?(Protocol::Light::SetWaveform)
24
+ end
25
+ udp.listen
26
+
27
+ delay = lifx.sync do
28
+ lights.each do |light|
29
+ light.pulse(LIFX::Color.hsb(rand(360), 1, 1), period: 1)
30
+ end
31
+ end
32
+
33
+ msgs.count.should == lights.count
34
+ msgs.map(&:at_time).uniq.count.should == 1
35
+
36
+ flush
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe Light, integration: true do
5
+ describe '#set_power' do
6
+ it "sets the power of the light asynchronously" do
7
+ light.set_power(0)
8
+ wait { light.off?.should == true }
9
+ light.set_power(1)
10
+ wait { light.on?.should == true }
11
+ end
12
+ end
13
+
14
+ describe '#set_power!' do
15
+ it "sets the power of the light synchronously" do
16
+ light.set_power!(0)
17
+ light.off?.should == true
18
+ light.set_power!(1)
19
+ light.on?.should == true
20
+ end
21
+ end
22
+
23
+ describe '#set_color' do
24
+ it "sets the color of the light asynchronously" do
25
+ color = Color.hsb(rand(360), rand, rand)
26
+ light.set_color(color, duration: 0)
27
+ sleep 1
28
+ light.refresh
29
+ wait { light.color.should == color }
30
+ end
31
+ end
32
+
33
+ describe '#set_label' do
34
+ it "sets the label of the light synchronously" do
35
+ label = light.label.sub(/\d+|$/, rand(100).to_s)
36
+ light.set_label(label)
37
+ light.label.should == label
38
+ end
39
+ end
40
+
41
+
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe "tags", integration: true do
5
+ it 'Clearing, setting and using tags' do
6
+ light.add_tag('Foo')
7
+ light.tags.should include('Foo')
8
+
9
+ test_tag = lights.with_tag('Foo')
10
+ test_tag.turn_on
11
+ color = Color.hsb(rand(360), 0.3, 0.3)
12
+ test_tag.set_color(color, duration: 0)
13
+ flush
14
+ sleep 1 # Set messages are scheduled 250ms if no at_time is set
15
+ # It also returns the current light state rather than the final state
16
+ light.refresh
17
+ wait { light.color.should == color }
18
+
19
+ light.remove_tag('Foo')
20
+ wait { light.tags.should_not include('Foo') }
21
+ end
22
+
23
+ it 'deletes tags when no longer assigned to a light' do
24
+ light.add_tag('TempTag')
25
+ light.remove_tag('TempTag')
26
+ lifx.unused_tags.should include('TempTag')
27
+ lifx.purge_unused_tags!
28
+ lifx.unused_tags.should be_empty
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ describe LIFX::Message do
4
+ context 'unpacking' do
5
+ let(:data) { "\x39\x00\x00\x34\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x31\x6c\x69\x66\x78\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x67\x00\x00\x00\x00\x01\x00\x00\xff\xff\xff\xff\xac\x0d\xc8\x00\x00\x00\x00\x00\x80\x3f\x00\x00\x00".b }
6
+ let(:msg) { LIFX::Message.unpack(data) }
7
+
8
+ it 'unpacks without errors' do
9
+ msg.should_not be_nil
10
+ end
11
+
12
+ it 'returns the correct frame data' do
13
+ msg.msg_size.should == 57
14
+ msg.protocol.should == 1024
15
+ msg.addressable?.should == true
16
+ end
17
+
18
+ it 'returns the correct address data' do
19
+ msg.raw_site.should == '1lifx1'
20
+ msg.raw_target.should == "\x00" * 8
21
+ end
22
+
23
+ it 'has correct ProtocolPath data' do
24
+ msg.path.should be_instance_of(LIFX::ProtocolPath)
25
+ msg.path.site_id.should == '316c69667831'
26
+ msg.path.tag_ids.should == []
27
+ msg.path.device_id.should == nil
28
+ end
29
+
30
+ it 'returns the correct metadata' do
31
+ msg.at_time.should == 0
32
+ msg.type.should == 103
33
+ end
34
+
35
+ let(:payload) { msg.payload }
36
+ it 'returns the payload' do
37
+ payload.class.should == LIFX::Protocol::Light::SetWaveform
38
+ payload.stream.should == 0
39
+ payload.transient.should be_true
40
+ payload.color.hue.should == 0
41
+ payload.color.saturation.should == 65535
42
+ payload.color.brightness.should == 65535
43
+ payload.color.kelvin.should == 3500
44
+ payload.period.should == 200
45
+ payload.cycles.should == 1.0
46
+ payload.duty_cycle.should == 0
47
+ payload.waveform.should == 0
48
+ end
49
+
50
+ it 'repacks to the same data' do
51
+ msg.pack.should == data
52
+ end
53
+ end
54
+
55
+ context 'packing' do
56
+ context 'no attributes' do
57
+ let(:msg) { LIFX::Message.new }
58
+
59
+ it 'throws an exception' do
60
+ expect { msg.pack }.to raise_error(LIFX::Message::NoPayload)
61
+ end
62
+ end
63
+
64
+ context 'no path' do
65
+ let(:msg) { LIFX::Message.new(payload: LIFX::Protocol::Device::SetPower.new) }
66
+
67
+ it 'throws an exception' do
68
+ expect { msg.pack }.to raise_error(LIFX::Message::NoPath)
69
+ end
70
+ end
71
+
72
+ context 'passed in via hash' do
73
+ let(:msg) do
74
+ LIFX::Message.new({
75
+ path: LIFX::ProtocolPath.new(tagged: false, raw_target: "abcdefgh"),
76
+ at_time: 9001,
77
+ payload: LIFX::Protocol::Wifi::SetAccessPoint.new(
78
+ interface: 1,
79
+ ssid: "who let the dogs out",
80
+ pass: "woof, woof, woof woof!",
81
+ security: 1
82
+ )
83
+ })
84
+ end
85
+
86
+ it 'sets the size' do
87
+ msg.msg_size.should == 134
88
+ end
89
+
90
+ it 'packs correctly' do
91
+ msg.pack.should == "\x86\x00\x00\x14\x00\x00\x00\x00abcdefgh\x00\x00\x00\x00\x00\x00\x00\x00)#\x00\x00\x00\x00\x00\x001\x01\x00\x00\x01who let the dogs out\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00woof, woof, woof woof!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".b
92
+ unpacked = LIFX::Message.unpack(msg.pack)
93
+ msg.protocol.should == 1024
94
+ msg.path.tagged?.should == false
95
+ msg.addressable?.should == true
96
+ msg.path.raw_target.should == 'abcdefgh'
97
+ msg.at_time.should == 9001
98
+ msg.type.should == 305
99
+ msg.payload.class.should == LIFX::Protocol::Wifi::SetAccessPoint
100
+ msg.payload.interface.should == 1
101
+ msg.payload.ssid.should == "who let the dogs out"
102
+ msg.payload.pass.should == "woof, woof, woof woof!"
103
+ msg.payload.security.should == 1
104
+ end
105
+ end
106
+
107
+ context 'packing with tags' do
108
+ let(:msg) do
109
+ LIFX::Message.new({
110
+ path: LIFX::ProtocolPath.new(tag_ids: [0,1]),
111
+ at_time: 9001,
112
+ payload: LIFX::Protocol::Device::GetTime.new
113
+ })
114
+ end
115
+
116
+ let(:unpacked) { LIFX::Message.unpack(msg.pack) }
117
+
118
+ it 'packs the tag correctly' do
119
+ msg.pack.should == "$\x00\x004\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)#\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00".b
120
+ end
121
+
122
+ it 'sets tagged' do
123
+ unpacked.path.tagged?.should == true
124
+ end
125
+
126
+ it 'sets tags' do
127
+ unpacked.path.tag_ids.should == [0, 1]
128
+ end
129
+
130
+ it 'device should be nil' do
131
+ unpacked.path.device_id.should == nil
132
+ end
133
+ end
134
+
135
+ context 'packing with device' do
136
+ let(:msg) do
137
+ LIFX::Message.new({
138
+ path: LIFX::ProtocolPath.new(device_id: '0123456789ab', site_id: '0' * 12),
139
+ at_time: 9001,
140
+ payload: LIFX::Protocol::Device::GetTime.new
141
+ })
142
+ end
143
+
144
+ let(:unpacked) { LIFX::Message.unpack(msg.pack) }
145
+
146
+ it 'packs the tag correctly' do
147
+ msg.pack.should == "$\x00\x00\x14\x00\x00\x00\x00\x01#Eg\x89\xAB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)#\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00".b
148
+ end
149
+
150
+ it 'sets tagged to false' do
151
+ unpacked.path.tagged?.should == false
152
+ end
153
+
154
+ it 'sets device' do
155
+ unpacked.path.device_id.should == '0123456789ab'
156
+ end
157
+
158
+ it 'tags should be nil' do
159
+ unpacked.path.tag_ids.should == nil
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe ProtocolPath do
5
+ describe 'initializing from raw data' do
6
+ context 'device target' do
7
+ subject do
8
+ ProtocolPath.new(raw_site: "1lifx1", raw_target: "\xAB\xCD\xEF\x12\x34\x56\x00\x00", tagged: false)
9
+ end
10
+
11
+ it 'returns site_id in hex' do
12
+ subject.site_id.should == "316c69667831"
13
+ end
14
+
15
+ it 'returns device_id in hex' do
16
+ subject.device_id.should == 'abcdef123456'
17
+ end
18
+
19
+ it 'returns tagged as false' do
20
+ subject.tagged?.should be_false
21
+ end
22
+
23
+ it 'returns nil for tag_ids' do
24
+ subject.tag_ids.should == nil
25
+ end
26
+ end
27
+
28
+ context 'tagged target' do
29
+ subject do
30
+ ProtocolPath.new(raw_site: "1lifx1", raw_target: "\x03\x00\x00\x00\x00\x00\x00\x00", tagged: true)
31
+ end
32
+
33
+ it 'returns site_id in hex' do
34
+ subject.site_id.should == "316c69667831"
35
+ end
36
+
37
+ it 'returns device_id as nil' do
38
+ subject.device_id.should == nil
39
+ end
40
+
41
+ it 'returns tagged as true' do
42
+ subject.tagged?.should be_true
43
+ end
44
+
45
+ it 'returns the tag_ids' do
46
+ subject.tag_ids.should == [0, 1]
47
+ end
48
+ end
49
+ end
50
+
51
+ describe 'initializing from strings' do
52
+ context 'device target' do
53
+ subject do
54
+ ProtocolPath.new(site_id: "316c69667831", device_id: 'abcdef123456')
55
+ end
56
+
57
+ it 'sets raw_site correctly' do
58
+ subject.raw_site.should == "1lifx1"
59
+ end
60
+
61
+ it 'sets raw_target correctly' do
62
+ subject.raw_target.should == "\xAB\xCD\xEF\x12\x34\x56\x00\x00".b
63
+ end
64
+
65
+ it 'sets tagged to false' do
66
+ subject.tagged?.should be_false
67
+ end
68
+ end
69
+
70
+ context 'tagged target' do
71
+ subject do
72
+ ProtocolPath.new(site_id: "316c69667831", tag_ids: [0, 1])
73
+ end
74
+
75
+ it 'sets raw_site properly' do
76
+ subject.raw_site.should == "1lifx1"
77
+ end
78
+
79
+ it 'sets raw_target correctly' do
80
+ subject.raw_target.should == "\x03\x00\x00\x00\x00\x00\x00\x00".b
81
+ end
82
+
83
+ it 'returns tagged as true' do
84
+ subject.tagged?.should be_true
85
+ end
86
+ end
87
+
88
+ context 'tagged target with no site' do
89
+ subject do
90
+ ProtocolPath.new(tagged: true)
91
+ end
92
+
93
+ it 'raw_site should be null string' do
94
+ subject.raw_site.should == "\x00\x00\x00\x00\x00\x00".b
95
+ end
96
+
97
+ it 'sets raw_target correctly' do
98
+ subject.raw_target.should == "\x00\x00\x00\x00\x00\x00\x00\x00".b
99
+ end
100
+
101
+ it 'returns tagged as true' do
102
+ subject.tagged?.should be_true
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ module LIFX
4
+ describe RoutingManager do
5
+ describe '#tags_for_device_id' do
6
+ subject do
7
+ RoutingManager.new(context: double)
8
+ end
9
+
10
+ before do
11
+ subject.tag_table.update_table(site_id: 'site', tag_id: 0, label: 'Some label')
12
+ subject.tag_table.update_table(site_id: 'site', tag_id: 1, label: 'Another label')
13
+ subject.tag_table.update_table(site_id: 'site', tag_id: 2, label: 'Much label')
14
+ subject.routing_table.update_table(site_id: 'site', device_id: 'device', tag_ids: [0,2])
15
+ end
16
+
17
+ it 'resolves tags' do
18
+ subject.tags_for_device_id('device').should == ['Some label', 'Much label']
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,52 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ require 'pry'
4
+
5
+ require 'lifx'
6
+ require 'lifx/utilities'
7
+
8
+ shared_context 'integration', integration: true do
9
+ def lifx
10
+ $lifx ||= begin
11
+ c = LIFX::Client.lan
12
+ begin
13
+ c.discover! do
14
+ c.tags.include?('Test') && c.lights.with_tag('Test').count > 0
15
+ end
16
+ rescue Timeout::Error
17
+ raise "Could not find any lights with tag Test in #{c.lights.inspect}"
18
+ end
19
+ c
20
+ end
21
+ end
22
+
23
+ def flush
24
+ lifx.flush
25
+ end
26
+
27
+ def wait(timeout: 5, retry_wait: 0.1, &block)
28
+ Timeout.timeout(timeout) do
29
+ begin
30
+ block.call
31
+ rescue RSpec::Expectations::ExpectationNotMetError
32
+ sleep(retry_wait)
33
+ retry
34
+ end
35
+ end
36
+ rescue Timeout::Error
37
+ block.call
38
+ end
39
+
40
+ let(:lights) { lifx.lights.with_tag('Test') }
41
+ let(:light) { lights.first }
42
+ end
43
+
44
+ if ENV['DEBUG']
45
+ LIFX::Config.logger = Yell.new(STDERR)
46
+ end
47
+
48
+ RSpec.configure do |config|
49
+ config.formatter = 'documentation'
50
+ config.color = true
51
+ end
52
+
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe LIFX::Transport::UDP do
4
+ let(:port) { 45828 }
5
+ subject do
6
+ LIFX::Transport::UDP.new('localhost', port)
7
+ end
8
+
9
+ describe '#write' do
10
+ let(:message) { double }
11
+ let(:payload) { double }
12
+ it 'writes a Message to specified host' do
13
+ message.should_receive(:pack).and_return(payload)
14
+ UDPSocket.any_instance.should_receive(:send).with(payload, 0, 'localhost', port)
15
+ subject.write(message)
16
+ end
17
+ end
18
+
19
+ describe '#listen' do
20
+ let(:raw_message) { 'some binary data' }
21
+ let(:message) { double }
22
+ let(:socket) { UDPSocket.new }
23
+
24
+ it 'listens to the specified socket data, unpacks it and notifies observers' do
25
+ messages = []
26
+ subject.add_observer(self) do |message:, ip:, transport:|
27
+ messages << message
28
+ end
29
+ subject.listen
30
+
31
+ LIFX::Message.should_receive(:unpack).with(raw_message).and_return(message)
32
+ socket.send(raw_message, 0, 'localhost', port)
33
+ sleep 0.01
34
+ messages.should include(message)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe LIFX::Transport do
4
+ # Transport handles communicating to the bulbs
5
+ # UDP, TCP, Cloud
6
+
7
+ describe 'initialize' do
8
+ it 'takes an host and port' do
9
+ transport = LIFX::Transport.new('127.0.0.1', 31337)
10
+ transport.host.should == '127.0.0.1'
11
+ transport.port.should == 31337
12
+ end
13
+ end
14
+ end