milight-v6 0.1.1 → 0.2.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 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: []