milight-v6 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5e7c03ca492bd7f803408e17d36df53b078bd9ac
4
- data.tar.gz: d0e7adb41c0bc5ff1c649ca93c69849addd86e8d
2
+ SHA256:
3
+ metadata.gz: b43dde6c8b92454a3ebb63b83678cc709d959f3cd2aab090e4b52bc5e6ad4202
4
+ data.tar.gz: 2565cbe4cfc87572d8eb73393f3dc5459b9e6894c8ab0f2f081ac1e42c5c2937
5
5
  SHA512:
6
- metadata.gz: f2480b71b8e1e6c31d7d41dd14052d856d5dfbe8f473e2250fdd533b2a404b096831e86601f6e9d520b69dc805e3fd73c2b93b80b83a5362fca6588efd135ad9
7
- data.tar.gz: aa7c8814f18470f8623358b9d5366770159564a982c8f58e9ca2a0d14c558f87cc6e90e9c210d9f1b8979212cfa1d7d9f005c5e884b29ec4f4879df920391f20
6
+ metadata.gz: 7fbfbc54f039ea09e879c72b8b6dd450633bbf3f91ba98f9cdaa9c93f44b0da7a510b1bcd6fc37120d2b01e3ecb3d7bc9fd468d102108355abd16e6936edc99b
7
+ data.tar.gz: 6d7faba2778786f8b5942837cc211e53d1fab2547a1cd7fd822431b6e993031ae4105a52968d1dea766ead271f49e904ee11d195a95634477fdcfd2580138db9
data/.rubocop.yml CHANGED
@@ -1,9 +1,8 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ TargetRubyVersion: 2.4
3
3
  DisplayCopNames: true
4
4
  DisplayStyleGuide: true
5
5
  Exclude:
6
- - 'lib/milight/v6/api/version.rb'
7
6
  - 'vendor/**/*'
8
7
 
9
8
  Metrics/BlockLength:
@@ -13,10 +12,10 @@ Metrics/BlockLength:
13
12
  Metrics/LineLength:
14
13
  Enabled: false
15
14
 
16
- Style/ConditionalAssignment:
15
+ Metrics/MethodLength:
17
16
  Enabled: false
18
17
 
19
- Style/Documentation:
18
+ Style/ConditionalAssignment:
20
19
  Enabled: false
21
20
 
22
21
  Style/For:
data/.travis.yml CHANGED
@@ -1,10 +1,11 @@
1
- language: ruby
2
1
  sudo: false
2
+ language: ruby
3
3
  cache: bundler
4
4
  before_install:
5
5
  - gem update --system
6
- - gem install bundler
7
6
  rvm:
8
- - 2.3.8
9
- - 2.4.5
10
- - 2.5.3
7
+ - 2.4.10
8
+ - 2.5.9
9
+ - 2.6.7
10
+ - 2.7.3
11
+ - 3.0.1
data/Gemfile.lock CHANGED
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- milight-v6 (0.1.1)
4
+ milight-v6 (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.3.0)
10
- diff-lcs (1.3)
10
+ diff-lcs (1.4.4)
11
11
  parallel (1.12.1)
12
12
  parser (2.4.0.2)
13
13
  ast (~> 2.3)
14
14
  powerpack (0.1.1)
15
15
  rainbow (3.0.0)
16
- rake (10.5.0)
17
- rspec (3.7.0)
18
- rspec-core (~> 3.7.0)
19
- rspec-expectations (~> 3.7.0)
20
- rspec-mocks (~> 3.7.0)
21
- rspec-core (3.7.0)
22
- rspec-support (~> 3.7.0)
23
- rspec-expectations (3.7.0)
16
+ rake (13.0.3)
17
+ rspec (3.10.0)
18
+ rspec-core (~> 3.10.0)
19
+ rspec-expectations (~> 3.10.0)
20
+ rspec-mocks (~> 3.10.0)
21
+ rspec-core (3.10.1)
22
+ rspec-support (~> 3.10.0)
23
+ rspec-expectations (3.10.1)
24
24
  diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.7.0)
26
- rspec-mocks (3.7.0)
25
+ rspec-support (~> 3.10.0)
26
+ rspec-mocks (3.10.2)
27
27
  diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.7.0)
29
- rspec-support (3.7.0)
28
+ rspec-support (~> 3.10.0)
29
+ rspec-support (3.10.2)
30
30
  rubocop (0.52.1)
31
31
  parallel (~> 1.10)
32
32
  parser (>= 2.4.0.2, < 3.0)
@@ -35,17 +35,17 @@ GEM
35
35
  ruby-progressbar (~> 1.7)
36
36
  unicode-display_width (~> 1.0, >= 1.0.1)
37
37
  ruby-progressbar (1.9.0)
38
- unicode-display_width (1.3.0)
38
+ unicode-display_width (1.7.0)
39
39
 
40
40
  PLATFORMS
41
41
  ruby
42
42
 
43
43
  DEPENDENCIES
44
- bundler (~> 1.16)
44
+ bundler (~> 2.0)
45
45
  milight-v6!
46
- rake (~> 10.0)
46
+ rake (~> 13.0)
47
47
  rspec (~> 3.0)
48
48
  rubocop (~> 0.52.1)
49
49
 
50
50
  BUNDLED WITH
51
- 1.16.1
51
+ 2.2.15
data/README.md CHANGED
@@ -4,7 +4,10 @@
4
4
  [![Build Status](https://travis-ci.org/ppostma/milight-v6-api.svg?branch=master)](https://travis-ci.org/ppostma/milight-v6-api)
5
5
  [![Code Climate](https://codeclimate.com/github/ppostma/milight-v6-api/badges/gpa.svg)](https://codeclimate.com/github/ppostma/milight-v6-api)
6
6
 
7
- This gem provides a Ruby API for the Milight Wifi Bridge (or Wifi iBOX controller) version 6.
7
+ This gem provides a Ruby API for the Mi-Light Wifi Bridge using protocol version 6.
8
+
9
+ Supported devices are the Mi-Light WiFi iBox models 1 and 2. The [esp8266_milight_hub](https://github.com/sidoh/esp8266_milight_hub) should also work, but I haven't tested this yet.
10
+ The bridges sold under the brand MiBoxer (such as model WL-Box1) are not supported by this gem.
8
11
 
9
12
  ## Installation
10
13
 
@@ -24,20 +27,46 @@ Or install it yourself as:
24
27
 
25
28
  ## Usage
26
29
 
30
+ ### Connecting to a Mi-Light controller
31
+
32
+ Connect to a Mi-Light controller by creating an instance of `Milight::V6::Controller` and supplying the IP address of the Mi-Light Wifi Bridge. If you don't know the IP address, you can use the class method `search` to discover devices on the local network.
33
+
27
34
  ```ruby
28
35
  require "milight/v6"
29
36
 
30
37
  controller = Milight::V6::Controller.new("192.168.178.33")
38
+
39
+ controllers = Milight::V6::Controller.search
40
+ ```
41
+
42
+ ### Sending commands
43
+
44
+ First select what you want control: the bridge lamp, a specific zone or all zones. Then you can start sending commands. See [Milight::V6::All](lib/milight/v6/all.rb), [Milight::V6::Bridge](lib/milight/v6/bridge.rb) and [Milight::V6::Zone](lib/milight/v6/zone.rb) for the supported commands.
45
+
46
+ Some examples:
47
+
48
+ ```ruby
31
49
  controller.zone(1).on
32
50
 
33
- controller.zone(2).warm_light.brightness(70).on
51
+ controller.zone(2).on
52
+ controller.zone(2).warm_light.brightness(70)
34
53
 
35
- controller.zone(3).hue(Milight::V6::Color::BLUE).saturation(10).on
54
+ controller.zone(3).on
55
+ controller.zone(3).hue(Milight::V6::Color::BLUE).saturation(10)
56
+
57
+ controller.bridge.on
58
+ controller.bridge.brightness(50)
36
59
 
37
60
  controller.all.off
38
61
  ```
39
62
 
40
- See `Milight::V6::All` and `Milight::V6::Zone` for all supported commands.
63
+ The commands will be sent with an interval of 100ms, to prevent commands being dropped by the controller. You can change or disable this by setting the `wait` parameter when creating an instance of `Milight::V6::Controller`, for example:
64
+
65
+ ```ruby
66
+ controller = Milight::V6::Controller.new("192.168.178.33", wait: false) # don't delay commands
67
+
68
+ controller = Milight::V6::Controller.new("192.168.178.33", wait: 0.05) # delay commands for 50 ms
69
+ ```
41
70
 
42
71
  ## Command line
43
72
 
@@ -45,9 +74,12 @@ A command line tool is included which can be used to control the lights.
45
74
 
46
75
  Usage: milight &lt;host&gt; &lt;command&gt; [zone]
47
76
 
48
- Supported commands: on, off, link, unlink
77
+ Supported commands: search, on, off, link, unlink
78
+
79
+ Examples:
49
80
 
50
81
  ```bash
82
+ $ milight search # search for devices
51
83
  $ milight 192.168.178.33 on 1 # turn on lights for zone 1
52
84
  $ milight 192.168.178.33 off # turn off lights for all zones
53
85
  ```
data/bin/milight CHANGED
@@ -3,30 +3,45 @@
3
3
 
4
4
  require "milight/v6"
5
5
 
6
- if ARGV.length < 2
7
- puts "Usage: #{$PROGRAM_NAME} <host> <command> [zone]"
8
- exit 1
9
- end
10
-
11
6
  host = ARGV.shift
12
- command = ARGV.shift
13
- zone = ARGV.shift
14
7
 
15
- controller = Milight::V6::Controller.new(host)
8
+ if host == "search"
9
+ controllers = Milight::V6::Controller.search
16
10
 
17
- if zone.nil?
18
- lights = controller.all
11
+ if !controllers.empty?
12
+ controllers.each { |c| puts c.to_s }
13
+ else
14
+ puts "No Mi-Light devices found."
15
+ end
19
16
  else
20
- lights = controller.zone(zone.to_i)
21
- end
17
+ if ARGV.empty?
18
+ puts "Usage: #{$PROGRAM_NAME} <host> <command> [zone]"
19
+ puts " #{$PROGRAM_NAME} search"
20
+ exit 1
21
+ end
22
+
23
+ command = ARGV.shift
24
+ zone = ARGV.shift
25
+
26
+ controller = Milight::V6::Controller.new(host)
27
+
28
+ case zone
29
+ when nil
30
+ lights = controller.all
31
+ when "bridge"
32
+ lights = controller.bridge
33
+ else
34
+ lights = controller.zone(zone.to_i)
35
+ end
22
36
 
23
- case command
24
- when "link"
25
- lights.link
26
- when "unlink"
27
- lights.unlink
28
- when "off"
29
- lights.off
30
- when "on"
31
- lights.on
37
+ case command
38
+ when "link"
39
+ lights.link
40
+ when "unlink"
41
+ lights.unlink
42
+ when "off"
43
+ lights.off
44
+ when "on"
45
+ lights.on
46
+ end
32
47
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Milight
4
4
  module V6
5
+ # Commands for lamps in all zones.
5
6
  class All
6
7
  def initialize(command)
7
8
  @command = command
@@ -9,64 +10,68 @@ module Milight
9
10
 
10
11
  # Switch the lights on.
11
12
  def on
12
- @command.on(0)
13
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00])
13
14
 
14
15
  self
15
16
  end
16
17
 
17
18
  # Switch the lights off.
18
19
  def off
19
- @command.off(0)
20
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00])
20
21
 
21
22
  self
22
23
  end
23
24
 
24
25
  # Enable night light mode.
25
26
  def night_light
26
- @command.night_light(0)
27
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x04, 0x05, 0x00, 0x00, 0x00])
27
28
 
28
29
  self
29
30
  end
30
31
 
31
- # Set brightness, value: 0% to 100%
32
+ # Set brightness, value: 0% to 100%.
32
33
  def brightness(value)
33
- @command.brightness(0, value)
34
+ raise ArgumentError, "Please supply a brightness value between 0-100." if value.negative? || value > 100
35
+
36
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x03, value, 0x00, 0x00, 0x00])
34
37
 
35
38
  self
36
39
  end
37
40
 
38
41
  # Set color temperature, value: 0 = 2700K, 100 = 6500K.
39
42
  def temperature(value)
40
- @command.temperature(0, value)
43
+ raise ArgumentError, "Please supply a temperature value between 0-100 (2700K to 6500K)." if value.negative? || value > 100
44
+
45
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x05, value, 0x00, 0x00, 0x00])
41
46
 
42
47
  self
43
48
  end
44
49
 
45
50
  # Set color temperature to warm light (2700K).
46
51
  def warm_light
47
- @command.temperature(0, 0)
48
-
49
- self
52
+ temperature(0)
50
53
  end
51
54
 
52
55
  # Set color temperature to white (cool) light (6500K).
53
56
  def white_light
54
- @command.temperature(0, 100)
55
-
56
- self
57
+ temperature(100)
57
58
  end
58
59
 
59
60
  # Set the hue, value: 0 to 255 (red).
60
61
  # See Milight::V6::Color for predefined colors.
61
62
  def hue(value)
62
- @command.hue(0, value)
63
+ raise ArgumentError, "Please supply a hue value between 0-255." if value.negative? || value > 255
64
+
65
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x01, value, value, value, value])
63
66
 
64
67
  self
65
68
  end
66
69
 
67
70
  # Set the saturation, value: 0% to 100%.
68
71
  def saturation(value)
69
- @command.saturation(0, value)
72
+ raise ArgumentError, "Please supply a saturation value between 0-100." if value.negative? || value > 100
73
+
74
+ @command.execute(0, [0x31, 0x00, 0x00, 0x08, 0x02, value, 0x00, 0x00, 0x00])
70
75
 
71
76
  self
72
77
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Milight
4
+ module V6
5
+ # Commands for the bridge lamp (iBox model 1).
6
+ class Bridge
7
+ def initialize(command)
8
+ @command = command
9
+ end
10
+
11
+ # Switch the light on.
12
+ def on
13
+ @command.execute(1, [0x31, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00])
14
+
15
+ self
16
+ end
17
+
18
+ # Switch the light off.
19
+ def off
20
+ @command.execute(1, [0x31, 0x00, 0x00, 0x00, 0x03, 0x04, 0x00, 0x00, 0x00])
21
+
22
+ self
23
+ end
24
+
25
+ # Set brightness, value: 0% to 100%.
26
+ def brightness(value)
27
+ raise ArgumentError, "Please supply a brightness value between 0-100." if value.negative? || value > 100
28
+
29
+ @command.execute(1, [0x31, 0x00, 0x00, 0x00, 0x02, value, 0x00, 0x00, 0x00])
30
+
31
+ self
32
+ end
33
+
34
+ # Set color to white light.
35
+ def white_light
36
+ @command.execute(1, [0x31, 0x00, 0x00, 0x00, 0x03, 0x05, 0x00, 0x00, 0x00])
37
+
38
+ self
39
+ end
40
+
41
+ # Set the hue, value: 0 to 255 (red).
42
+ # See Milight::V6::Color for predefined colors.
43
+ def hue(value)
44
+ raise ArgumentError, "Please supply a hue value between 0-255." if value.negative? || value > 255
45
+
46
+ @command.execute(1, [0x31, 0x00, 0x00, 0x00, 0x01, value, value, value, value])
47
+
48
+ self
49
+ end
50
+
51
+ # Wait before continuing to next command.
52
+ def wait(seconds)
53
+ sleep(seconds)
54
+
55
+ self
56
+ end
57
+ end
58
+ end
59
+ end
@@ -7,59 +7,19 @@ module Milight
7
7
  module V6
8
8
  # see https://github.com/Fantasmos/LimitlessLED-DevAPI
9
9
  class Command
10
- def initialize(host, port = 5987)
11
- @socket = Milight::V6::Socket.new(host, port)
10
+ attr_accessor :wait
12
11
 
13
- bridge_session
14
- end
15
-
16
- def link(zone_id)
17
- execute(zone_id, [0x3D, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
18
- end
19
-
20
- def unlink(zone_id)
21
- execute(zone_id, [0x3E, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
22
- end
23
-
24
- def on(zone_id)
25
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00])
26
- end
27
-
28
- def off(zone_id)
29
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00])
30
- end
31
-
32
- def night_light(zone_id)
33
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x05, 0x00, 0x00, 0x00])
34
- end
35
-
36
- def brightness(zone_id, value)
37
- raise ArgumentError, "Please supply a brightness value between 0-100." if value.negative? || value > 100
38
-
39
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x03, value, 0x00, 0x00, 0x00])
40
- end
41
-
42
- def temperature(zone_id, value)
43
- raise ArgumentError, "Please supply a temperature value between 0-100 (2700K to 6500K)." if value.negative? || value > 100
44
-
45
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x05, value, 0x00, 0x00, 0x00])
46
- end
47
-
48
- def hue(zone_id, value)
49
- raise ArgumentError, "Please supply a hue value between 0-255." if value.negative? || value > 255
50
-
51
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x01, value, value, value, value])
52
- end
53
-
54
- def saturation(zone_id, value)
55
- raise ArgumentError, "Please supply a saturation value between 0-100." if value.negative? || value > 100
56
-
57
- execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x02, value, 0x00, 0x00, 0x00])
12
+ def initialize(socket, wait:)
13
+ @socket = socket
14
+ @wait = wait
15
+ @last_time = 0
58
16
  end
59
17
 
60
18
  def execute(zone_id, command)
61
19
  raise ArgumentError, "Please supply a zone ID between 1-4." if zone_id.negative? || zone_id > 4
62
20
 
21
+ bridge_session
22
+
63
23
  # UDP Hex Send Format: 80 00 00 00 11 {WifiBridgeSessionID1} {WifiBridgeSessionID2} 00 {SequenceNumber} 00 {COMMAND} {ZONE NUMBER} 00 {Checksum}
64
24
  request = [0x80, 0x00, 0x00, 0x00, 0x11, @session_id1, @session_id2, 0x00, next_sequence_number, 0x00]
65
25
 
@@ -69,22 +29,25 @@ module Milight
69
29
  request << 0x00
70
30
  request << calculate_checksum(request)
71
31
 
72
- @socket.send_bytes(request)
73
- @socket.receive_bytes
32
+ send_delayed(request)
74
33
  end
75
34
 
76
35
  private
77
36
 
78
37
  def bridge_session
38
+ return if !@session_id1.nil? || !@session_id2.nil?
39
+
79
40
  # UDP.SEND hex bytes: 20 00 00 00 16 02 62 3A D5 ED A3 01 AE 08 2D 46 61 41 A7 F6 DC AF (D3 E6) 00 00 1E <-- Send this to the ip address of the wifi bridge v6
80
41
  request = [0x20, 0x00, 0x00, 0x00, 0x16, 0x02, 0x62, 0x3A, 0xD5,
81
42
  0xED, 0xA3, 0x01, 0xAE, 0x08, 0x2D, 0x46, 0x61, 0x41,
82
43
  0xA7, 0xF6, 0xDC, 0xAF, 0xD3, 0xE6, 0x00, 0x00, 0x1E]
83
44
 
84
45
  @socket.send_bytes(request)
85
- response = @socket.receive_bytes
46
+ response, _address = @socket.receive_bytes
86
47
 
87
- raise Exception, "Could not establish session with Wifi bridge." unless response
48
+ raise Exception, "Could not establish session with Wifi bridge." if response.nil?
49
+
50
+ record_last_time
88
51
 
89
52
  @session_id1 = response[19]
90
53
  @session_id2 = response[20]
@@ -106,6 +69,33 @@ module Milight
106
69
 
107
70
  checksum & 0xFF
108
71
  end
72
+
73
+ # Delay execution of commands to prevent commands being dropped by the controller.
74
+ def send_delayed(request)
75
+ delay_command
76
+
77
+ @socket.send_bytes(request)
78
+ @socket.receive_bytes
79
+
80
+ record_last_time
81
+ end
82
+
83
+ def delay_command
84
+ return unless @wait
85
+
86
+ interval = current_time - @last_time
87
+ sleep(@wait - interval) if interval < @wait
88
+ end
89
+
90
+ def record_last_time
91
+ return unless @wait
92
+
93
+ @last_time = current_time
94
+ end
95
+
96
+ def current_time
97
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
98
+ end
109
99
  end
110
100
  end
111
101
  end
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "milight/v6/command"
4
+ require "milight/v6/discover"
4
5
  require "milight/v6/all"
6
+ require "milight/v6/bridge"
5
7
  require "milight/v6/zone"
6
8
 
7
9
  module Milight
8
10
  module V6
11
+ # Controller for the Mi-Light WiFi iBox.
9
12
  class Controller
10
- def initialize(host, port = 5987)
11
- @command = Milight::V6::Command.new(host, port)
13
+ extend Milight::V6::Discover
14
+
15
+ def initialize(host = "<broadcast>", port = 5987, wait: 0.1)
16
+ @socket = Milight::V6::Socket.new(host, port)
17
+ @command = Milight::V6::Command.new(@socket, wait: wait)
12
18
  end
13
19
 
14
20
  # Select all zones.
@@ -20,6 +26,15 @@ module Milight
20
26
  def zone(zone_id)
21
27
  Milight::V6::Zone.new(@command, zone_id)
22
28
  end
29
+
30
+ # Select the bridge lamp.
31
+ def bridge
32
+ Milight::V6::Bridge.new(@command)
33
+ end
34
+
35
+ def to_s
36
+ "Mi-Light Wifi iBox Controller. IP address: #{@socket.host}"
37
+ end
23
38
  end
24
39
  end
25
40
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "milight/v6/controller"
4
+ require "milight/v6/socket"
5
+
6
+ module Milight
7
+ module V6
8
+ # Search for Mi-Light devices.
9
+ module Discover
10
+ AUTH_TOKEN = [0x39, 0x38, 0x35, 0x62, 0x31, 0x35, 0x37, 0x62, 0x66, 0x36, 0x66,
11
+ 0x63, 0x34, 0x33, 0x33, 0x36, 0x38, 0x61, 0x36, 0x33, 0x34, 0x36,
12
+ 0x37, 0x65, 0x61, 0x33, 0x62, 0x31, 0x39, 0x64, 0x30, 0x64].freeze
13
+
14
+ def search
15
+ socket = Milight::V6::Socket.new("<broadcast>", 5987)
16
+
17
+ bytes = [0x10, 0x00, 0x00, 0x00, 0x24, 0x02, 0xEE, 0x3E, 0x02] + AUTH_TOKEN
18
+ socket.send_bytes(bytes)
19
+
20
+ controllers = []
21
+
22
+ loop do
23
+ bytes, address = socket.receive_bytes
24
+ break if bytes.nil?
25
+
26
+ token = bytes[14, AUTH_TOKEN.length]
27
+ controllers << Milight::V6::Controller.new(address) if token == AUTH_TOKEN
28
+ end
29
+
30
+ socket.close
31
+
32
+ controllers
33
+ end
34
+ end
35
+ end
36
+ end
@@ -5,41 +5,65 @@ require "socket"
5
5
 
6
6
  module Milight
7
7
  module V6
8
+ # Send and receive UDP packets.
8
9
  class Socket
9
10
  READ_TIMEOUT = 5
10
11
 
11
- def initialize(host, port)
12
- @socket = UDPSocket.new
13
- @socket.connect(host, port)
12
+ attr_reader :host, :port
14
13
 
15
- @logger = Logger.new(STDOUT)
16
- @logger.level = Logger::INFO if ENV["MILIGHT_DEBUG"] != "1"
14
+ def initialize(host, port)
15
+ @host = host
16
+ @port = port
17
17
  end
18
18
 
19
19
  def send_bytes(bytes)
20
- @logger.debug("Sending: #{format_bytes_as_hex(bytes)}")
20
+ logger.debug("Sending: #{format_bytes_as_hex(bytes)}")
21
21
 
22
- @socket.send(bytes.pack('C*'), 0)
22
+ socket.send(bytes.pack('C*'), 0, @host, @port)
23
23
  end
24
24
 
25
25
  def receive_bytes
26
- response = @socket.recvfrom_nonblock(128).first
26
+ response, address = socket.recvfrom_nonblock(128)
27
27
  bytes = response.unpack('C*')
28
28
 
29
- @logger.debug("Received: #{format_bytes_as_hex(bytes)}")
29
+ logger.debug("Received: #{format_bytes_as_hex(bytes)}")
30
30
 
31
- bytes
31
+ [bytes, address.last]
32
32
  rescue IO::WaitReadable
33
- ready = IO.select([@socket], nil, nil, READ_TIMEOUT)
33
+ ready = IO.select([socket], nil, nil, READ_TIMEOUT)
34
34
  retry if ready
35
35
 
36
- return false
36
+ return nil
37
+ end
38
+
39
+ def close
40
+ socket.close
37
41
  end
38
42
 
39
43
  private
40
44
 
45
+ def socket
46
+ @socket ||= begin
47
+ socket = UDPSocket.new
48
+
49
+ if @host == "<broadcast>" || @host == "255.255.255.255"
50
+ socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BROADCAST, true)
51
+ end
52
+
53
+ socket
54
+ end
55
+ end
56
+
57
+ def logger
58
+ @logger ||= begin
59
+ logger = Logger.new(STDOUT)
60
+ logger.level = Logger::INFO if ENV["MILIGHT_DEBUG"] != "1"
61
+ logger
62
+ end
63
+ end
64
+
41
65
  def format_bytes_as_hex(bytes)
42
- bytes.map { |s| format("0x%02X", s) }
66
+ bytes.map { |s| format("0x%02X", s) }.join(", ")
43
67
  end
44
68
  end
45
69
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Milight
4
4
  module V6
5
- VERSION = "0.1.1"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Milight
4
4
  module V6
5
+ # Commands for lamps in a specific zone.
5
6
  class Zone
6
7
  attr_reader :zone_id
7
8
 
@@ -12,78 +13,82 @@ module Milight
12
13
 
13
14
  # Link/sync light bulbs.
14
15
  def link
15
- @command.link(zone_id)
16
+ @command.execute(zone_id, [0x3D, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
16
17
 
17
18
  self
18
19
  end
19
20
 
20
21
  # Unlink/clear light bulbs.
21
22
  def unlink
22
- @command.unlink(zone_id)
23
+ @command.execute(zone_id, [0x3E, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00])
23
24
 
24
25
  self
25
26
  end
26
27
 
27
28
  # Switch the lights on.
28
29
  def on
29
- @command.on(zone_id)
30
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00])
30
31
 
31
32
  self
32
33
  end
33
34
 
34
35
  # Switch the lights off.
35
36
  def off
36
- @command.off(zone_id)
37
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00])
37
38
 
38
39
  self
39
40
  end
40
41
 
41
42
  # Enable night light mode.
42
43
  def night_light
43
- @command.night_light(zone_id)
44
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x04, 0x05, 0x00, 0x00, 0x00])
44
45
 
45
46
  self
46
47
  end
47
48
 
48
- # Set brightness, value: 0% to 100%
49
+ # Set brightness, value: 0% to 100%.
49
50
  def brightness(value)
50
- @command.brightness(zone_id, value)
51
+ raise ArgumentError, "Please supply a brightness value between 0-100." if value.negative? || value > 100
52
+
53
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x03, value, 0x00, 0x00, 0x00])
51
54
 
52
55
  self
53
56
  end
54
57
 
55
58
  # Set color temperature, value: 0 = 2700K, 100 = 6500K.
56
59
  def temperature(value)
57
- @command.temperature(zone_id, value)
60
+ raise ArgumentError, "Please supply a temperature value between 0-100 (2700K to 6500K)." if value.negative? || value > 100
61
+
62
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x05, value, 0x00, 0x00, 0x00])
58
63
 
59
64
  self
60
65
  end
61
66
 
62
67
  # Set color temperature to warm light (2700K).
63
68
  def warm_light
64
- @command.temperature(zone_id, 0)
65
-
66
- self
69
+ temperature(0)
67
70
  end
68
71
 
69
72
  # Set color temperature to white (cool) light (6500K).
70
73
  def white_light
71
- @command.temperature(zone_id, 100)
72
-
73
- self
74
+ temperature(100)
74
75
  end
75
76
 
76
77
  # Set the hue, value: 0 to 255 (red).
77
78
  # See Milight::V6::Color for predefined colors.
78
79
  def hue(value)
79
- @command.hue(zone_id, value)
80
+ raise ArgumentError, "Please supply a hue value between 0-255." if value.negative? || value > 255
81
+
82
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x01, value, value, value, value])
80
83
 
81
84
  self
82
85
  end
83
86
 
84
87
  # Set the saturation, value: 0% to 100%.
85
88
  def saturation(value)
86
- @command.saturation(zone_id, value)
89
+ raise ArgumentError, "Please supply a saturation value between 0-100." if value.negative? || value > 100
90
+
91
+ @command.execute(zone_id, [0x31, 0x00, 0x00, 0x08, 0x02, value, 0x00, 0x00, 0x00])
87
92
 
88
93
  self
89
94
  end
data/milight-v6.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = ["milight"]
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_development_dependency "bundler", "~> 1.16"
25
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "bundler", "~> 2.0"
25
+ spec.add_development_dependency "rake", "~> 13.0"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: milight-v6
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Postma
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-01 00:00:00.000000000 Z
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.16'
19
+ version: '2.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.16'
26
+ version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -75,9 +75,11 @@ files:
75
75
  - bin/setup
76
76
  - lib/milight/v6.rb
77
77
  - lib/milight/v6/all.rb
78
+ - lib/milight/v6/bridge.rb
78
79
  - lib/milight/v6/color.rb
79
80
  - lib/milight/v6/command.rb
80
81
  - lib/milight/v6/controller.rb
82
+ - lib/milight/v6/discover.rb
81
83
  - lib/milight/v6/exception.rb
82
84
  - lib/milight/v6/socket.rb
83
85
  - lib/milight/v6/version.rb
@@ -87,7 +89,7 @@ homepage: https://github.com/ppostma/milight-v6-api
87
89
  licenses:
88
90
  - MIT
89
91
  metadata: {}
90
- post_install_message:
92
+ post_install_message:
91
93
  rdoc_options: []
92
94
  require_paths:
93
95
  - lib
@@ -102,9 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
104
  - !ruby/object:Gem::Version
103
105
  version: '0'
104
106
  requirements: []
105
- rubyforge_project:
106
- rubygems_version: 2.6.14
107
- signing_key:
107
+ rubygems_version: 3.1.4
108
+ signing_key:
108
109
  specification_version: 4
109
110
  summary: Ruby API for the Milight Wifi Bridge v6
110
111
  test_files: []